{"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"}, "4078a52888bb3af1c45b7ec5c79fa3561a99c5ef": {"t": 1786068289.5926583, "n": 352408, "h": "efc1201e29c0b447", "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\tif connections := slngSTTConnections(cfg.STTModelEndpoints); len(connections) > 0 {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTConnections(connections...))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t\t}\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\tsttOpts = append(sttOpts, slngSTTModelOptions(cfg.STTModelOptions)...)\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\tttsOpts = append(ttsOpts, slngTTSModelOptions(cfg.TTSModelOptions)...)\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\tif connections := slngSTTConnections(cfg.STTModelEndpoints); len(connections) > 0 {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTConnections(connections...))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t\t}\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\tsttOpts = append(sttOpts, slngSTTModelOptions(cfg.STTModelOptions)...)\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\tttsOpts = append(ttsOpts, slngTTSModelOptions(cfg.TTSModelOptions)...)\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 slngSTTModelOptions(options map[string]any) []slng.STTOption {\n\tsttOpts := []slng.STTOption{}\n\tif apiKey, ok := modelOptionStringValue(options, \"provider_api_key\"); ok {\n\t\tsttOpts = append(sttOpts, slng.WithSTTProviderAPIKey(apiKey))\n\t}\n\tif worldPart := modelOptionString(options, \"world_part_override\"); worldPart != \"\" {\n\t\tsttOpts = append(sttOpts, slng.WithSTTWorldPartOverride(worldPart))\n\t}\n\tif agentID, ok := modelOptionStringValue(options, \"external_agent_id\"); ok {\n\t\tsttOpts = append(sttOpts, slng.WithSTTExternalAgentID(agentID))\n\t}\n\tif sessionID, ok := modelOptionStringValue(options, \"external_session_id\"); ok {\n\t\tsttOpts = append(sttOpts, slng.WithSTTExternalSessionID(sessionID))\n\t}\n\tif cooldown, ok := slngModelOptionDuration(options, \"fallback_recovery_cooldown_s\"); ok {\n\t\tsttOpts = append(sttOpts, slng.WithSTTFallbackRecoveryCooldown(cooldown))\n\t}\n\tif providerOptions := slngProviderModelOptions(options); len(providerOptions) > 0 {\n\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(providerOptions))\n\t}\n\treturn sttOpts\n}\n\nfunc slngTTSModelOptions(options map[string]any) []slng.TTSOption {\n\tttsOpts := []slng.TTSOption{}\n\tif apiKey, ok := modelOptionStringValue(options, \"provider_api_key\"); ok {\n\t\tttsOpts = append(ttsOpts, slng.WithTTSProviderAPIKey(apiKey))\n\t}\n\tif worldPart := modelOptionString(options, \"world_part_override\"); worldPart != \"\" {\n\t\tttsOpts = append(ttsOpts, slng.WithTTSWorldPartOverride(worldPart))\n\t}\n\tif agentID, ok := modelOptionStringValue(options, \"external_agent_id\"); ok {\n\t\tttsOpts = append(ttsOpts, slng.WithTTSExternalAgentID(agentID))\n\t}\n\tif sessionID, ok := modelOptionStringValue(options, \"external_session_id\"); ok {\n\t\tttsOpts = append(ttsOpts, slng.WithTTSExternalSessionID(sessionID))\n\t}\n\tif cooldown, ok := slngModelOptionDuration(options, \"fallback_recovery_cooldown_s\"); ok {\n\t\tttsOpts = append(ttsOpts, slng.WithTTSFallbackRecoveryCooldown(cooldown))\n\t}\n\tif timeout, ok := slngModelOptionDuration(options, \"first_audio_timeout_s\"); ok {\n\t\tttsOpts = append(ttsOpts, slng.WithTTSFirstAudioTimeout(timeout))\n\t}\n\tif warmStandby := modelOptionBool(options, \"warm_standby_enabled\"); warmStandby != nil {\n\t\tttsOpts = append(ttsOpts, slng.WithTTSWarmStandby(*warmStandby))\n\t}\n\tmode, modeSet := modelOptionStringValue(options, \"text_chunking\")\n\tphraseMaxChars, phraseMaxCharsSet := modelOptionIntValue(options, \"phrase_max_chars\")\n\tif modeSet || phraseMaxCharsSet {\n\t\tif !modeSet {\n\t\t\tmode = string(slng.TTSChunkingWord)\n\t\t}\n\t\tif !phraseMaxCharsSet {\n\t\t\tphraseMaxChars = 60\n\t\t}\n\t\tttsOpts = append(ttsOpts, slng.WithTTSTextChunking(slng.TTSChunkingMode(mode), phraseMaxChars))\n\t}\n\tif providerOptions := slngProviderModelOptions(options); len(providerOptions) > 0 {\n\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(providerOptions))\n\t}\n\treturn ttsOpts\n}\n\nfunc slngSTTConnections(endpoints []string) []slng.STTConnectionConfig {\n\tconnections := make([]slng.STTConnectionConfig, 0, len(endpoints))\n\tfor _, endpoint := range endpoints {\n\t\ttrimmed := strings.TrimSpace(endpoint)\n\t\tif (!strings.HasPrefix(trimmed, \"ws://\") && !strings.HasPrefix(trimmed, \"wss://\")) ||\n\t\t\t!strings.Contains(trimmed, \"/v1/bridges/unmute/stt/\") {\n\t\t\treturn nil\n\t\t}\n\t\tconnections = append(connections, slng.STTConnectionConfig{Endpoint: trimmed})\n\t}\n\treturn connections\n}\n\nfunc slngModelOptionDuration(options map[string]any, key string) (time.Duration, bool) {\n\tif seconds := modelOptionFloat(options, key); seconds != nil {\n\t\treturn time.Duration(*seconds * float64(time.Second)), true\n\t}\n\tif seconds, ok := modelOptionIntValue(options, key); ok {\n\t\treturn time.Duration(seconds) * time.Second, true\n\t}\n\treturn 0, false\n}\n\nfunc slngProviderModelOptions(options map[string]any) map[string]any {\n\tfiltered := make(map[string]any, len(options))\n\tfor key, value := range options {\n\t\tswitch key {\n\t\tcase \"provider_api_key\", \"world_part_override\", \"external_agent_id\", \"external_session_id\", \"fallback_recovery_cooldown_s\", \"first_audio_timeout_s\", \"warm_standby_enabled\", \"text_chunking\", \"phrase_max_chars\":\n\t\t\tcontinue\n\t\t}\n\t\tfiltered[key] = value\n\t}\n\tif len(filtered) == 0 {\n\t\treturn nil\n\t}\n\treturn filtered\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"}}, "adapter/openai/llm_request_test.go": {"4078a52888bb3af1c45b7ec5c79fa3561a99c5ef": {"t": 1786068296.6233702, "n": 146055, "h": "7a98ce43e3601c28", "c": "package openai\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/cavos-io/rtp-agent/core/llm\"\n\topenaisdk \"github.com/sashabaranov/go-openai\"\n)\n\nfunc mustNewOpenAILLMWithConfig(t *testing.T, config openaisdk.ClientConfig, model string) *OpenAILLM {\n\tt.Helper()\n\tprovider, err := newOpenAILLMWithConfigAndModel(config, model)\n\tif err != nil {\n\t\tt.Fatalf(\"newOpenAILLMWithConfigAndModel error = %v\", err)\n\t}\n\treturn provider\n}\n\nfunc startOpenAIChat(t *testing.T, model *OpenAILLM, ctx context.Context, chatCtx *llm.ChatContext, opts ...llm.ChatOption) (llm.LLMStream, error) {\n\tt.Helper()\n\tstream, err := model.Chat(ctx, chatCtx, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t_, err = stream.Next()\n\tif errors.Is(err, io.EOF) {\n\t\terr = nil\n\t}\n\treturn stream, err\n}\n\nfunc assertOpenAIToolChoiceAutoOmittedWithoutTools(t *testing.T, model *OpenAILLM, requestBody string) {\n\tt.Helper()\n\tif model.toolChoice != \"auto\" {\n\t\tt.Fatalf(\"toolChoice = %#v, want reference default auto\", model.toolChoice)\n\t}\n\tif strings.Contains(requestBody, `\"tool_choice\"`) {\n\t\tt.Fatalf(\"request body = %s, want tool_choice omitted when no tools are provided\", requestBody)\n\t}\n}\n\ntype requestTestTool struct{}\n\nfunc (requestTestTool) ID() string { return \"lookup\" }\nfunc (requestTestTool) Name() string { return \"lookup\" }\nfunc (requestTestTool) Description() string { return \"look up information\" }\nfunc (requestTestTool) Parameters() map[string]any {\n\treturn map[string]any{\n\t\t\"type\": \"object\",\n\t\t\"additionalProperties\": false,\n\t\t\"properties\": map[string]any{\n\t\t\t\"query\": map[string]any{\"type\": \"string\"},\n\t\t},\n\t\t\"required\": []string{\"query\"},\n\t}\n}\nfunc (requestTestTool) Execute(context.Context, string) (string, error) { return \"\", nil }\n\ntype requestOptionalSchemaTool struct{}\n\nfunc (requestOptionalSchemaTool) ID() string { return \"end_call\" }\nfunc (requestOptionalSchemaTool) Name() string { return \"end_call\" }\nfunc (requestOptionalSchemaTool) Description() string { return \"end call\" }\nfunc (requestOptionalSchemaTool) Parameters() map[string]any {\n\treturn map[string]any{\n\t\t\"type\": \"object\",\n\t\t\"properties\": map[string]any{\n\t\t\t\"variant\": map[string]any{\n\t\t\t\t\"type\": \"string\",\n\t\t\t\t\"description\": \"Optional variant name.\",\n\t\t\t},\n\t\t},\n\t\t\"required\": []string{},\n\t}\n}\nfunc (requestOptionalSchemaTool) Execute(context.Context, string) (string, error) { return \"\", nil }\n\ntype requestNoArgSchemaTool struct{}\n\nfunc (requestNoArgSchemaTool) ID() string { return \"transfer_call\" }\nfunc (requestNoArgSchemaTool) Name() string { return \"transfer_call\" }\nfunc (requestNoArgSchemaTool) Description() string { return \"transfer call\" }\nfunc (requestNoArgSchemaTool) Parameters() map[string]any {\n\treturn map[string]any{\n\t\t\"type\": \"object\",\n\t\t\"properties\": map[string]any{},\n\t}\n}\nfunc (requestNoArgSchemaTool) Execute(context.Context, string) (string, error) { return \"\", nil }\n\ntype requestUnionSchemaTool struct{}\n\nfunc (requestUnionSchemaTool) ID() string { return \"lookup\" }\nfunc (requestUnionSchemaTool) Name() string { return \"lookup\" }\nfunc (requestUnionSchemaTool) Description() string { return \"look up information\" }\nfunc (requestUnionSchemaTool) Parameters() map[string]any {\n\treturn map[string]any{\n\t\t\"type\": \"object\",\n\t\t\"properties\": map[string]any{\n\t\t\t\"query\": map[string]any{\n\t\t\t\t\"oneOf\": []any{\n\t\t\t\t\tmap[string]any{\"type\": \"string\"},\n\t\t\t\t\tmap[string]any{},\n\t\t\t\t\tmap[string]any{\"type\": \"integer\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"required\": []string{\"query\"},\n\t}\n}\nfunc (requestUnionSchemaTool) Execute(context.Context, string) (string, error) { return \"\", nil }\n\ntype requestOptionalUnionSchemaTool struct{}\n\nfunc (requestOptionalUnionSchemaTool) ID() string { return \"lookup\" }\nfunc (requestOptionalUnionSchemaTool) Name() string { return \"lookup\" }\nfunc (requestOptionalUnionSchemaTool) Description() string { return \"look up information\" }\nfunc (requestOptionalUnionSchemaTool) Parameters() map[string]any {\n\treturn map[string]any{\n\t\t\"type\": \"object\",\n\t\t\"properties\": map[string]any{\n\t\t\t\"query\": map[string]any{\n\t\t\t\t\"oneOf\": []any{\n\t\t\t\t\tmap[string]any{\"type\": \"string\"},\n\t\t\t\t\tmap[string]any{\"type\": \"integer\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"required\": []string{},\n\t}\n}\nfunc (requestOptionalUnionSchemaTool) Execute(context.Context, string) (string, error) {\n\treturn \"\", nil\n}\n\ntype requestNullableUnionSchemaTool struct{}\n\nfunc (requestNullableUnionSchemaTool) ID() string { return \"lookup\" }\nfunc (requestNullableUnionSchemaTool) Name() string { return \"lookup\" }\nfunc (requestNullableUnionSchemaTool) Description() string { return \"look up information\" }\nfunc (requestNullableUnionSchemaTool) Parameters() map[string]any {\n\treturn map[string]any{\n\t\t\"type\": \"object\",\n\t\t\"properties\": map[string]any{\n\t\t\t\"mode\": map[string]any{\n\t\t\t\t\"anyOf\": []any{\n\t\t\t\t\tmap[string]any{\n\t\t\t\t\t\t\"type\": \"string\",\n\t\t\t\t\t\t\"enum\": []any{\"fast\", \"safe\"},\n\t\t\t\t\t},\n\t\t\t\t\tmap[string]any{\"type\": \"null\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"required\": []string{\"mode\"},\n\t}\n}\nfunc (requestNullableUnionSchemaTool) Execute(context.Context, string) (string, error) {\n\treturn \"\", nil\n}\n\ntype requestDefsSchemaTool struct{}\n\nfunc (requestDefsSchemaTool) ID() string { return \"lookup\" }\nfunc (requestDefsSchemaTool) Name() string { return \"lookup\" }\nfunc (requestDefsSchemaTool) Description() string { return \"look up information\" }\nfunc (requestDefsSchemaTool) Parameters() map[string]any {\n\treturn map[string]any{\n\t\t\"type\": \"object\",\n\t\t\"properties\": map[string]any{\n\t\t\t\"payload\": map[string]any{\"$ref\": \"#/$defs/payload\"},\n\t\t},\n\t\t\"required\": []string{\"payload\"},\n\t\t\"$defs\": map[string]any{\n\t\t\t\"payload\": map[string]any{\n\t\t\t\t\"type\": \"object\",\n\t\t\t\t\"properties\": map[string]any{\n\t\t\t\t\t\"query\": map[string]any{\"type\": \"string\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n}\nfunc (requestDefsSchemaTool) Execute(context.Context, string) (string, error) { return \"\", nil }\n\ntype requestRefSiblingSchemaTool struct{}\n\nfunc (requestRefSiblingSchemaTool) ID() string { return \"lookup\" }\nfunc (requestRefSiblingSchemaTool) Name() string { return \"lookup\" }\nfunc (requestRefSiblingSchemaTool) Description() string { return \"look up information\" }\nfunc (requestRefSiblingSchemaTool) Parameters() map[string]any {\n\treturn map[string]any{\n\t\t\"type\": \"object\",\n\t\t\"properties\": map[string]any{\n\t\t\t\"payload\": map[string]any{\n\t\t\t\t\"$ref\": \"#/$defs/payload\",\n\t\t\t\t\"description\": \"caller payload\",\n\t\t\t},\n\t\t},\n\t\t\"required\": []string{\"payload\"},\n\t\t\"$defs\": map[string]any{\n\t\t\t\"payload\": map[string]any{\n\t\t\t\t\"type\": \"object\",\n\t\t\t\t\"properties\": map[string]any{\n\t\t\t\t\t\"query\": map[string]any{\"type\": \"string\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n}\nfunc (requestRefSiblingSchemaTool) Execute(context.Context, string) (string, error) { return \"\", nil }\n\ntype requestAllOfDefaultSchemaTool struct{}\n\nfunc (requestAllOfDefaultSchemaTool) ID() string { return \"lookup\" }\nfunc (requestAllOfDefaultSchemaTool) Name() string { return \"lookup\" }\nfunc (requestAllOfDefaultSchemaTool) Description() string { return \"look up information\" }\nfunc (requestAllOfDefaultSchemaTool) Parameters() map[string]any {\n\treturn map[string]any{\n\t\t\"type\": \"object\",\n\t\t\"properties\": map[string]any{\n\t\t\t\"mode\": map[string]any{\n\t\t\t\t\"allOf\": []any{\n\t\t\t\t\tmap[string]any{\"type\": \"string\"},\n\t\t\t\t},\n\t\t\t\t\"default\": \"fast\",\n\t\t\t},\n\t\t},\n\t\t\"required\": []string{\"mode\"},\n\t}\n}\nfunc (requestAllOfDefaultSchemaTool) Execute(context.Context, string) (string, error) {\n\treturn \"\", nil\n}\n\nfunc TestNewOpenAILLMUsesEnvironmentAPIKeyAndReferenceDefaultModel(t *testing.T) {\n\tt.Setenv(openAIAPIKeyEnv, \"env-key\")\n\n\tmodel, err := NewOpenAILLM(\"\", \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"NewOpenAILLM error = %v, want env fallback\", err)\n\t}\n\n\tif model.Model() != defaultOpenAILLMModel {\n\t\tt.Fatalf(\"Model = %q, want reference default\", model.Model())\n\t}\n}\n\nfunc TestNewOpenAILLMUsesEnvironmentBaseURL(t *testing.T) {\n\tt.Setenv(openAIBaseURLEnv, \"https://env.openai.test/v1\")\n\n\tmodel, err := NewOpenAILLM(\"test-key\", \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"NewOpenAILLM error = %v\", err)\n\t}\n\n\tif got := model.Provider(); got != \"env.openai.test\" {\n\t\tt.Fatalf(\"Provider() = %q, want OPENAI_BASE_URL host\", got)\n\t}\n}\n\nfunc TestNewOpenAILLMRequiresAPIKey(t *testing.T) {\n\tt.Setenv(openAIAPIKeyEnv, \"\")\n\n\t_, err := NewOpenAILLM(\"\", \"\")\n\tif err == nil {\n\t\tt.Fatal(\"NewOpenAILLM error = nil, want missing API key error\")\n\t}\n\tif got, want := err.Error(), openAIAPIKeyRequiredMessage; got != want {\n\t\tt.Fatalf(\"NewOpenAILLM error = %q, want %q\", got, want)\n\t}\n}\n\nfunc TestNewAzureOpenAILLMRoutesDeploymentAndKeepsModelMetadata(t *testing.T) {\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\n\tmodel, err := NewAzureOpenAILLM(\n\t\t\"gpt-4o\",\n\t\t\"https://resource.openai.azure.com\",\n\t\t\"chat-deployment\",\n\t\t\"2024-06-01\",\n\t\t\"azure-key\",\n\t\t\"\",\n\t\twithOpenAILLMHTTPClient(capture),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"NewAzureOpenAILLM error = %v\", err)\n\t}\n\n\t_, _ = startOpenAIChat(t, model, context.Background(), llm.NewChatContext(), llm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}))\n\n\tif model.Model() != \"gpt-4o\" {\n\t\tt.Fatalf(\"Model = %q, want reference model metadata\", model.Model())\n\t}\n\tif got := model.Provider(); got != \"resource.openai.azure.com\" {\n\t\tt.Fatalf(\"Provider() = %q, want Azure endpoint host\", got)\n\t}\n\tif !strings.Contains(capture.requestURL, \"/openai/deployments/chat-deployment/chat/completions\") {\n\t\tt.Fatalf(\"request URL = %s, want Azure deployment route\", capture.requestURL)\n\t}\n\tif !strings.Contains(capture.requestURL, \"api-version=2024-06-01\") {\n\t\tt.Fatalf(\"request URL = %s, want configured api-version\", capture.requestURL)\n\t}\n\tif capture.apiKey != \"azure-key\" {\n\t\tt.Fatalf(\"api-key header = %q, want Azure API key\", capture.apiKey)\n\t}\n\tif capture.authorization != \"\" {\n\t\tt.Fatalf(\"Authorization = %q, want no bearer token for API-key auth\", capture.authorization)\n\t}\n}\n\nfunc TestNewAzureOpenAILLMFallsBackToReferenceEnvironment(t *testing.T) {\n\tt.Setenv(\"AZURE_OPENAI_ENDPOINT\", \"https://env-resource.openai.azure.com\")\n\tt.Setenv(\"AZURE_OPENAI_API_KEY\", \"env-azure-key\")\n\tt.Setenv(\"OPENAI_API_VERSION\", \"2024-08-01-preview\")\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\n\tmodel, err := NewAzureOpenAILLM(\n\t\t\"\",\n\t\t\"\",\n\t\t\"\",\n\t\t\"\",\n\t\t\"\",\n\t\t\"\",\n\t\twithOpenAILLMHTTPClient(capture),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"NewAzureOpenAILLM error = %v\", err)\n\t}\n\n\t_, _ = startOpenAIChat(t, model, context.Background(), llm.NewChatContext(), llm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}))\n\n\tif model.Model() != defaultAzureOpenAILLMModel {\n\t\tt.Fatalf(\"Model = %q, want Azure reference default model\", model.Model())\n\t}\n\tif !strings.Contains(capture.requestURL, \"/openai/deployments/\"+defaultAzureOpenAILLMModel+\"/chat/completions\") {\n\t\tt.Fatalf(\"request URL = %s, want Azure reference default model as deployment\", capture.requestURL)\n\t}\n\tif strings.Contains(capture.requestURL, \"/openai/deployments/\"+defaultOpenAILLMModel+\"/chat/completions\") {\n\t\tt.Fatalf(\"request URL = %s, want Azure reference default not global OpenAI default\", capture.requestURL)\n\t}\n\tif !strings.Contains(capture.requestURL, \"api-version=2024-08-01-preview\") {\n\t\tt.Fatalf(\"request URL = %s, want env api-version\", capture.requestURL)\n\t}\n\tif capture.apiKey != \"env-azure-key\" {\n\t\tt.Fatalf(\"api-key header = %q, want env Azure API key\", capture.apiKey)\n\t}\n}\n\nfunc TestNewAzureOpenAILLMFallsBackToReferenceOrganizationProjectEnvironment(t *testing.T) {\n\tt.Setenv(\"AZURE_OPENAI_ENDPOINT\", \"https://env-resource.openai.azure.com\")\n\tt.Setenv(\"AZURE_OPENAI_API_KEY\", \"env-azure-key\")\n\tt.Setenv(\"OPENAI_ORG_ID\", \"org-123\")\n\tt.Setenv(\"OPENAI_PROJECT_ID\", \"proj-456\")\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\n\tmodel, err := NewAzureOpenAILLM(\n\t\t\"gpt-4o\",\n\t\t\"\",\n\t\t\"\",\n\t\t\"\",\n\t\t\"\",\n\t\t\"\",\n\t\twithOpenAILLMHTTPClient(capture),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"NewAzureOpenAILLM error = %v\", err)\n\t}\n\n\t_, _ = startOpenAIChat(t, model, context.Background(), llm.NewChatContext(), llm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}))\n\n\tif got := capture.header.Get(\"OpenAI-Organization\"); got != \"org-123\" {\n\t\tt.Fatalf(\"OpenAI-Organization header = %q, want env organization\", got)\n\t}\n\tif got := capture.header.Get(\"OpenAI-Project\"); got != \"proj-456\" {\n\t\tt.Fatalf(\"OpenAI-Project header = %q, want env project\", got)\n\t}\n}\n\nfunc TestNewAzureOpenAILLMUsesExplicitOrganizationProjectOptions(t *testing.T) {\n\tt.Setenv(\"OPENAI_ORG_ID\", \"env-org\")\n\tt.Setenv(\"OPENAI_PROJECT_ID\", \"env-project\")\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\n\tmodel, err := NewAzureOpenAILLM(\n\t\t\"gpt-4o\",\n\t\t\"https://resource.openai.azure.com\",\n\t\t\"chat-deployment\",\n\t\t\"2024-06-01\",\n\t\t\"azure-key\",\n\t\t\"\",\n\t\twithOpenAILLMHTTPClient(capture),\n\t\tWithOpenAILLMOrganization(\"explicit-org\"),\n\t\tWithOpenAILLMProject(\"explicit-project\"),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"NewAzureOpenAILLM error = %v\", err)\n\t}\n\n\t_, _ = startOpenAIChat(t, model, context.Background(), llm.NewChatContext(), llm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}))\n\n\tif got := capture.header.Get(\"OpenAI-Organization\"); got != \"explicit-org\" {\n\t\tt.Fatalf(\"OpenAI-Organization header = %q, want explicit organization\", got)\n\t}\n\tif got := capture.header.Get(\"OpenAI-Project\"); got != \"explicit-project\" {\n\t\tt.Fatalf(\"OpenAI-Project header = %q, want explicit project\", got)\n\t}\n}\n\nfunc TestNewAzureOpenAILLMDoesNotRetryByDefault(t *testing.T) {\n\tcapture := &sequenceHTTPClient{responses: []*http.Response{\n\t\topenAITestResponse(http.StatusTooManyRequests, `{\"error\":{\"message\":\"rate limit\",\"type\":\"rate_limit\",\"code\":\"rate_limit\"}}`),\n\t\topenAITestResponse(http.StatusOK, \"data: [DONE]\\n\\n\"),\n\t}}\n\n\tmodel, err := NewAzureOpenAILLM(\n\t\t\"gpt-4o\",\n\t\t\"https://resource.openai.azure.com\",\n\t\t\"chat-deployment\",\n\t\t\"2024-06-01\",\n\t\t\"azure-key\",\n\t\t\"\",\n\t\twithOpenAILLMHTTPClient(capture),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"NewAzureOpenAILLM error = %v\", err)\n\t}\n\n\tstream, err := startOpenAIChat(t, model, context.Background(), llm.NewChatContext())\n\tif stream != nil {\n\t\t_ = stream.Close()\n\t}\n\n\tvar statusErr *llm.APIStatusError\n\tif !errors.As(err, &statusErr) {\n\t\tt.Fatalf(\"Chat error = %T %v, want first Azure status error without retry\", err, err)\n\t}\n\tif capture.calls != 1 {\n\t\tt.Fatalf(\"HTTP calls = %d, want no default retry for Azure responses LLM\", capture.calls)\n\t}\n}\n\nfunc TestNewAzureOpenAILLMRequiresEndpoint(t *testing.T) {\n\tt.Setenv(\"AZURE_OPENAI_ENDPOINT\", \"\")\n\tt.Setenv(\"AZURE_OPENAI_API_KEY\", \"key\")\n\n\t_, err := NewAzureOpenAILLM(\"gpt-4o\", \"\", \"\", \"\", \"\", \"\")\n\tif err == nil || !strings.Contains(err.Error(), \"AZURE_OPENAI_ENDPOINT\") {\n\t\tt.Fatalf(\"NewAzureOpenAILLM error = %v, want missing endpoint error\", err)\n\t}\n}\n\nfunc TestNewAzureOpenAILLMUsesEntraTokenWhenAPIKeyEmpty(t *testing.T) {\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\n\tmodel, err := NewAzureOpenAILLM(\n\t\t\"gpt-4o\",\n\t\t\"https://resource.openai.azure.com\",\n\t\t\"chat-deployment\",\n\t\t\"2024-06-01\",\n\t\t\"\",\n\t\t\"entra-token\",\n\t\twithOpenAILLMHTTPClient(capture),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"NewAzureOpenAILLM error = %v\", err)\n\t}\n\n\t_, _ = startOpenAIChat(t, model, context.Background(), llm.NewChatContext(), llm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}))\n\n\tif capture.apiKey != \"\" {\n\t\tt.Fatalf(\"api-key header = %q, want removed for Entra token auth\", capture.apiKey)\n\t}\n\tif capture.authorization != \"Bearer entra-token\" {\n\t\tt.Fatalf(\"Authorization = %q, want Entra bearer token\", capture.authorization)\n\t}\n}\n\nfunc TestNewAzureOpenAILLMUsesReferenceEntraTokenProvider(t *testing.T) {\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\ttokenCalls := 0\n\n\tmodel, err := NewAzureOpenAILLM(\n\t\t\"gpt-4o\",\n\t\t\"https://resource.openai.azure.com\",\n\t\t\"chat-deployment\",\n\t\t\"2024-06-01\",\n\t\t\"\",\n\t\t\"\",\n\t\twithOpenAILLMHTTPClient(capture),\n\t\tWithOpenAILLMAzureADTokenProvider(func(context.Context) (string, error) {\n\t\t\ttokenCalls++\n\t\t\treturn \"provider-token\", nil\n\t\t}),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"NewAzureOpenAILLM error = %v\", err)\n\t}\n\n\t_, _ = startOpenAIChat(t, model, context.Background(), llm.NewChatContext(), llm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}))\n\n\tif tokenCalls != 1 {\n\t\tt.Fatalf(\"token provider calls = %d, want 1\", tokenCalls)\n\t}\n\tif capture.apiKey != \"\" {\n\t\tt.Fatalf(\"api-key header = %q, want removed for Entra token provider auth\", capture.apiKey)\n\t}\n\tif capture.authorization != \"Bearer provider-token\" {\n\t\tt.Fatalf(\"Authorization = %q, want provider bearer token\", capture.authorization)\n\t}\n}\n\nfunc TestNewAzureOpenAILLMUsesReferenceBaseURLOption(t *testing.T) {\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\n\tmodel, err := NewAzureOpenAILLM(\n\t\t\"gpt-4o\",\n\t\t\"https://resource.openai.azure.com\",\n\t\t\"chat-deployment\",\n\t\t\"2024-06-01\",\n\t\t\"azure-key\",\n\t\t\"\",\n\t\twithOpenAILLMHTTPClient(capture),\n\t\tWithOpenAILLMAzureBaseURL(\"https://gateway.openai.azure.test/custom\"),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"NewAzureOpenAILLM error = %v\", err)\n\t}\n\n\t_, _ = startOpenAIChat(t, model, context.Background(), llm.NewChatContext(), llm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}))\n\n\tif got := model.Provider(); got != \"gateway.openai.azure.test\" {\n\t\tt.Fatalf(\"Provider() = %q, want base_url host\", got)\n\t}\n\tif !strings.HasPrefix(capture.requestURL, \"https://gateway.openai.azure.test/custom/openai/deployments/chat-deployment/chat/completions\") {\n\t\tt.Fatalf(\"request URL = %s, want reference base_url route\", capture.requestURL)\n\t}\n}\n\nfunc TestNewAzureOpenAILLMUsesReferenceBaseURLWithoutEndpoint(t *testing.T) {\n\tt.Setenv(\"AZURE_OPENAI_ENDPOINT\", \"\")\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\n\tmodel, err := NewAzureOpenAILLM(\n\t\t\"gpt-4o\",\n\t\t\"\",\n\t\t\"chat-deployment\",\n\t\t\"2024-06-01\",\n\t\t\"azure-key\",\n\t\t\"\",\n\t\twithOpenAILLMHTTPClient(capture),\n\t\tWithOpenAILLMAzureBaseURL(\"https://gateway.openai.azure.test/custom\"),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"NewAzureOpenAILLM error = %v\", err)\n\t}\n\n\t_, _ = startOpenAIChat(t, model, context.Background(), llm.NewChatContext(), llm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}))\n\n\tif !strings.HasPrefix(capture.requestURL, \"https://gateway.openai.azure.test/custom/openai/deployments/chat-deployment/chat/completions\") {\n\t\tt.Fatalf(\"request URL = %s, want reference base_url route\", capture.requestURL)\n\t}\n}\n\nfunc TestNewAzureOpenAILLMUsesReferenceTimeoutOption(t *testing.T) {\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\n\tmodel, err := NewAzureOpenAILLM(\n\t\t\"gpt-4o\",\n\t\t\"https://resource.openai.azure.com\",\n\t\t\"chat-deployment\",\n\t\t\"2024-06-01\",\n\t\t\"azure-key\",\n\t\t\"\",\n\t\twithOpenAILLMHTTPClient(capture),\n\t\tWithOpenAILLMAzureTimeout(75*time.Millisecond),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"NewAzureOpenAILLM error = %v\", err)\n\t}\n\n\t_, _ = startOpenAIChat(t, model, context.Background(), llm.NewChatContext())\n\n\tif !capture.hasDeadline {\n\t\tt.Fatal(\"request context has no deadline, want Azure constructor timeout deadline\")\n\t}\n\tif capture.remaining <= 0 || capture.remaining > 75*time.Millisecond {\n\t\tt.Fatalf(\"request context deadline remaining = %v, want bounded by Azure constructor timeout\", capture.remaining)\n\t}\n}\n\nfunc TestNewAzureOpenAILLMUsesReferenceReasoningObject(t *testing.T) {\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\n\tmodel, err := NewAzureOpenAILLM(\n\t\t\"gpt-5\",\n\t\t\"https://resource.openai.azure.com\",\n\t\t\"chat-deployment\",\n\t\t\"2024-06-01\",\n\t\t\"azure-key\",\n\t\t\"\",\n\t\twithOpenAILLMHTTPClient(capture),\n\t\tWithOpenAILLMReasoning(map[string]any{\n\t\t\t\"effort\": \"low\",\n\t\t\t\"summary\": \"auto\",\n\t\t}),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"NewAzureOpenAILLM error = %v\", err)\n\t}\n\n\t_, _ = startOpenAIChat(t, model, context.Background(), llm.NewChatContext(), llm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}))\n\n\tif !strings.Contains(capture.requestBody, `\"reasoning\":{\"effort\":\"low\",\"summary\":\"auto\"}`) {\n\t\tt.Fatalf(\"request body = %s, want reference reasoning object\", capture.requestBody)\n\t}\n}\n\nfunc TestNewAzureOpenAILLMReasoningObjectOmitsDefaultEffort(t *testing.T) {\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\n\tmodel, err := NewAzureOpenAILLM(\n\t\t\"gpt-5\",\n\t\t\"https://resource.openai.azure.com\",\n\t\t\"chat-deployment\",\n\t\t\"2024-06-01\",\n\t\t\"azure-key\",\n\t\t\"\",\n\t\twithOpenAILLMHTTPClient(capture),\n\t\tWithOpenAILLMReasoning(map[string]any{\"effort\": \"minimal\"}),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"NewAzureOpenAILLM error = %v\", err)\n\t}\n\n\t_, _ = startOpenAIChat(t, model, context.Background(), llm.NewChatContext(), llm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}))\n\n\tif strings.Contains(capture.requestBody, `\"reasoning_effort\"`) {\n\t\tt.Fatalf(\"request body = %s, want no reasoning_effort when reference reasoning object is configured\", capture.requestBody)\n\t}\n}\n\nfunc TestNewOVHCloudOpenAILLMDefaultsMatchReference(t *testing.T) {\n\tt.Setenv(ovhcloudAPIKeyEnv, \"env-ovh-key\")\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\n\tmodel, err := NewOVHCloudOpenAILLM(\"\", \"\", withOpenAILLMHTTPClient(capture))\n\tif err != nil {\n\t\tt.Fatalf(\"NewOVHCloudOpenAILLM error = %v\", err)\n\t}\n\n\t_, _ = startOpenAIChat(t, model, context.Background(), llm.NewChatContext(), llm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}))\n\n\tif model.Model() != \"gpt-oss-120b\" {\n\t\tt.Fatalf(\"Model = %q, want gpt-oss-120b\", model.Model())\n\t}\n\tif model.Provider() != \"oai.endpoints.kepler.ai.cloud.ovh.net\" {\n\t\tt.Fatalf(\"Provider() = %q, want OVHcloud endpoint host\", model.Provider())\n\t}\n\tif capture.authorization != \"Bearer env-ovh-key\" {\n\t\tt.Fatalf(\"Authorization = %q, want OVHcloud bearer key\", capture.authorization)\n\t}\n\tif !strings.Contains(capture.requestURL, \"/v1/chat/completions\") {\n\t\tt.Fatalf(\"request URL = %s, want OpenAI-compatible chat completions route\", capture.requestURL)\n\t}\n\tif !strings.Contains(capture.requestBody, `\"model\":\"gpt-oss-120b\"`) {\n\t\tt.Fatalf(\"request body = %s, want default OVHcloud model\", capture.requestBody)\n\t}\n\tassertOpenAIToolChoiceAutoOmittedWithoutTools(t, model, capture.requestBody)\n}\n\nfunc TestNewOVHCloudOpenAILLMRequiresAPIKey(t *testing.T) {\n\tt.Setenv(ovhcloudAPIKeyEnv, \"\")\n\n\t_, err := NewOVHCloudOpenAILLM(\"\", \"\")\n\tif err == nil || err.Error() != \"OVHcloud AI Endpoints API key is required, either as argument or set OVHCLOUD_API_KEY environmental variable\" {\n\t\tt.Fatalf(\"NewOVHCloudOpenAILLM error = %v, want OVHcloud API key required\", err)\n\t}\n}\n\nfunc TestNewDeepSeekOpenAILLMDefaultsMatchReference(t *testing.T) {\n\tt.Setenv(deepSeekAPIKeyEnv, \"env-deepseek-key\")\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\n\tmodel, err := NewDeepSeekOpenAILLM(\"\", \"\", withOpenAILLMHTTPClient(capture))\n\tif err != nil {\n\t\tt.Fatalf(\"NewDeepSeekOpenAILLM error = %v\", err)\n\t}\n\n\t_, _ = startOpenAIChat(t, model, context.Background(), llm.NewChatContext(), llm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}))\n\n\tif model.Model() != \"deepseek-chat\" {\n\t\tt.Fatalf(\"Model = %q, want deepseek-chat\", model.Model())\n\t}\n\tif model.Provider() != \"api.deepseek.com\" {\n\t\tt.Fatalf(\"Provider() = %q, want DeepSeek endpoint host\", model.Provider())\n\t}\n\tif capture.authorization != \"Bearer env-deepseek-key\" {\n\t\tt.Fatalf(\"Authorization = %q, want DeepSeek bearer key\", capture.authorization)\n\t}\n\tif !strings.Contains(capture.requestURL, \"/v1/chat/completions\") {\n\t\tt.Fatalf(\"request URL = %s, want OpenAI-compatible chat completions route\", capture.requestURL)\n\t}\n\tif !strings.Contains(capture.requestBody, `\"model\":\"deepseek-chat\"`) {\n\t\tt.Fatalf(\"request body = %s, want default DeepSeek model\", capture.requestBody)\n\t}\n\tassertOpenAIToolChoiceAutoOmittedWithoutTools(t, model, capture.requestBody)\n}\n\nfunc TestNewDeepSeekOpenAILLMRequiresAPIKey(t *testing.T) {\n\tt.Setenv(deepSeekAPIKeyEnv, \"\")\n\n\t_, err := NewDeepSeekOpenAILLM(\"\", \"\")\n\tif err == nil || err.Error() != \"DeepSeek API key is required, either as argument or set DEEPSEEK_API_KEY environmental variable\" {\n\t\tt.Fatalf(\"NewDeepSeekOpenAILLM error = %v, want DeepSeek API key required\", err)\n\t}\n}\n\nfunc TestNewFireworksOpenAILLMDefaultsMatchReference(t *testing.T) {\n\tt.Setenv(fireworksAPIKeyEnv, \"env-fireworks-key\")\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\n\tmodel, err := NewFireworksOpenAILLM(\"\", \"\", withOpenAILLMHTTPClient(capture))\n\tif err != nil {\n\t\tt.Fatalf(\"NewFireworksOpenAILLM error = %v\", err)\n\t}\n\n\t_, _ = startOpenAIChat(t, model, context.Background(), llm.NewChatContext(), llm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}))\n\n\tif model.Model() != \"accounts/fireworks/models/llama-v3p3-70b-instruct\" {\n\t\tt.Fatalf(\"Model = %q, want Fireworks reference model\", model.Model())\n\t}\n\tif model.Provider() != \"api.fireworks.ai\" {\n\t\tt.Fatalf(\"Provider() = %q, want Fireworks endpoint host\", model.Provider())\n\t}\n\tif capture.authorization != \"Bearer env-fireworks-key\" {\n\t\tt.Fatalf(\"Authorization = %q, want Fireworks bearer key\", capture.authorization)\n\t}\n\tif !strings.Contains(capture.requestURL, \"/inference/v1/chat/completions\") {\n\t\tt.Fatalf(\"request URL = %s, want Fireworks OpenAI-compatible chat completions route\", capture.requestURL)\n\t}\n\tif !strings.Contains(capture.requestBody, `\"model\":\"accounts/fireworks/models/llama-v3p3-70b-instruct\"`) {\n\t\tt.Fatalf(\"request body = %s, want default Fireworks model\", capture.requestBody)\n\t}\n\tassertOpenAIToolChoiceAutoOmittedWithoutTools(t, model, capture.requestBody)\n}\n\nfunc TestNewFireworksOpenAILLMRequiresAPIKey(t *testing.T) {\n\tt.Setenv(fireworksAPIKeyEnv, \"\")\n\n\t_, err := NewFireworksOpenAILLM(\"\", \"\")\n\tif err == nil || err.Error() != \"fireworks API key is required, either as argument or set FIREWORKS_API_KEY environmental variable\" {\n\t\tt.Fatalf(\"NewFireworksOpenAILLM error = %v, want Fireworks API key required\", err)\n\t}\n}\n\nfunc TestNewPerplexityOpenAILLMDefaultsMatchReference(t *testing.T) {\n\tt.Setenv(perplexityAPIKeyEnv, \"env-perplexity-key\")\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\n\tmodel, err := NewPerplexityOpenAILLM(\"\", \"\", withOpenAILLMHTTPClient(capture))\n\tif err != nil {\n\t\tt.Fatalf(\"NewPerplexityOpenAILLM error = %v\", err)\n\t}\n\n\t_, _ = startOpenAIChat(t, model, context.Background(), llm.NewChatContext(), llm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}))\n\n\tif model.Model() != \"llama-3.1-sonar-small-128k-chat\" {\n\t\tt.Fatalf(\"Model = %q, want Perplexity reference model\", model.Model())\n\t}\n\tif model.Provider() != \"api.perplexity.ai\" {\n\t\tt.Fatalf(\"Provider() = %q, want Perplexity endpoint host\", model.Provider())\n\t}\n\tif capture.authorization != \"Bearer env-perplexity-key\" {\n\t\tt.Fatalf(\"Authorization = %q, want Perplexity bearer key\", capture.authorization)\n\t}\n\tif !strings.Contains(capture.requestURL, \"/chat/completions\") {\n\t\tt.Fatalf(\"request URL = %s, want Perplexity OpenAI-compatible chat completions route\", capture.requestURL)\n\t}\n\tif !strings.Contains(capture.requestBody, `\"model\":\"llama-3.1-sonar-small-128k-chat\"`) {\n\t\tt.Fatalf(\"request body = %s, want default Perplexity model\", capture.requestBody)\n\t}\n\tassertOpenAIToolChoiceAutoOmittedWithoutTools(t, model, capture.requestBody)\n}\n\nfunc TestNewPerplexityOpenAILLMRequiresAPIKey(t *testing.T) {\n\tt.Setenv(perplexityAPIKeyEnv, \"\")\n\n\t_, err := NewPerplexityOpenAILLM(\"\", \"\")\n\tif err == nil || err.Error() != \"perplexity AI API key is required, either as argument or set PERPLEXITY_API_KEY environmental variable\" {\n\t\tt.Fatalf(\"NewPerplexityOpenAILLM error = %v, want Perplexity API key required\", err)\n\t}\n}\n\nfunc TestNewTogetherOpenAILLMDefaultsMatchReference(t *testing.T) {\n\tt.Setenv(togetherAPIKeyEnv, \"env-together-key\")\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\n\tmodel, err := NewTogetherOpenAILLM(\"\", \"\", withOpenAILLMHTTPClient(capture))\n\tif err != nil {\n\t\tt.Fatalf(\"NewTogetherOpenAILLM error = %v\", err)\n\t}\n\n\t_, _ = startOpenAIChat(t, model, context.Background(), llm.NewChatContext(), llm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}))\n\n\tif model.Model() != \"meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo\" {\n\t\tt.Fatalf(\"Model = %q, want Together reference model\", model.Model())\n\t}\n\tif model.Provider() != \"api.together.xyz\" {\n\t\tt.Fatalf(\"Provider() = %q, want Together endpoint host\", model.Provider())\n\t}\n\tif capture.authorization != \"Bearer env-together-key\" {\n\t\tt.Fatalf(\"Authorization = %q, want Together bearer key\", capture.authorization)\n\t}\n\tif !strings.Contains(capture.requestURL, \"/v1/chat/completions\") {\n\t\tt.Fatalf(\"request URL = %s, want Together OpenAI-compatible chat completions route\", capture.requestURL)\n\t}\n\tif !strings.Contains(capture.requestBody, `\"model\":\"meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo\"`) {\n\t\tt.Fatalf(\"request body = %s, want default Together model\", capture.requestBody)\n\t}\n\tassertOpenAIToolChoiceAutoOmittedWithoutTools(t, model, capture.requestBody)\n}\n\nfunc TestNewTogetherOpenAILLMRequiresAPIKey(t *testing.T) {\n\tt.Setenv(togetherAPIKeyEnv, \"\")\n\n\t_, err := NewTogetherOpenAILLM(\"\", \"\")\n\tif err == nil || err.Error() != \"together AI API key is required, either as argument or set TOGETHER_API_KEY environmental variable\" {\n\t\tt.Fatalf(\"NewTogetherOpenAILLM error = %v, want Together API key required\", err)\n\t}\n}\n\nfunc TestNewTelnyxOpenAILLMDefaultsMatchReference(t *testing.T) {\n\tt.Setenv(telnyxAPIKeyEnv, \"env-telnyx-key\")\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\n\tmodel, err := NewTelnyxOpenAILLM(\"\", \"\", withOpenAILLMHTTPClient(capture))\n\tif err != nil {\n\t\tt.Fatalf(\"NewTelnyxOpenAILLM error = %v\", err)\n\t}\n\n\t_, _ = startOpenAIChat(t, model, context.Background(), llm.NewChatContext(), llm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}))\n\n\tif model.Model() != \"meta-llama/Meta-Llama-3.1-70B-Instruct\" {\n\t\tt.Fatalf(\"Model = %q, want Telnyx reference model\", model.Model())\n\t}\n\tif model.Provider() != \"api.telnyx.com\" {\n\t\tt.Fatalf(\"Provider() = %q, want Telnyx endpoint host\", model.Provider())\n\t}\n\tif capture.authorization != \"Bearer env-telnyx-key\" {\n\t\tt.Fatalf(\"Authorization = %q, want Telnyx bearer key\", capture.authorization)\n\t}\n\tif !strings.Contains(capture.requestURL, \"/v2/ai/chat/completions\") {\n\t\tt.Fatalf(\"request URL = %s, want Telnyx OpenAI-compatible chat completions route\", capture.requestURL)\n\t}\n\tif !strings.Contains(capture.requestBody, `\"model\":\"meta-llama/Meta-Llama-3.1-70B-Instruct\"`) {\n\t\tt.Fatalf(\"request body = %s, want default Telnyx model\", capture.requestBody)\n\t}\n\tassertOpenAIToolChoiceAutoOmittedWithoutTools(t, model, capture.requestBody)\n}\n\nfunc TestNewTelnyxOpenAILLMRequiresAPIKey(t *testing.T) {\n\tt.Setenv(telnyxAPIKeyEnv, \"\")\n\n\t_, err := NewTelnyxOpenAILLM(\"\", \"\")\n\tif err == nil || err.Error() != \"telnyx AI API key is required, either as argument or set TELNYX_API_KEY environmental variable\" {\n\t\tt.Fatalf(\"NewTelnyxOpenAILLM error = %v, want Telnyx API key required\", err)\n\t}\n}\n\nfunc TestNewNebiusOpenAILLMDefaultsMatchReference(t *testing.T) {\n\tt.Setenv(nebiusAPIKeyEnv, \"env-nebius-key\")\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\n\tmodel, err := NewNebiusOpenAILLM(\"\", \"\", withOpenAILLMHTTPClient(capture))\n\tif err != nil {\n\t\tt.Fatalf(\"NewNebiusOpenAILLM error = %v\", err)\n\t}\n\n\t_, _ = startOpenAIChat(t, model, context.Background(), llm.NewChatContext(), llm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}))\n\n\tif model.Model() != \"meta-llama/Meta-Llama-3.1-70B-Instruct\" {\n\t\tt.Fatalf(\"Model = %q, want Nebius reference model\", model.Model())\n\t}\n\tif model.Provider() != \"api.studio.nebius.com\" {\n\t\tt.Fatalf(\"Provider() = %q, want Nebius endpoint host\", model.Provider())\n\t}\n\tif capture.authorization != \"Bearer env-nebius-key\" {\n\t\tt.Fatalf(\"Authorization = %q, want Nebius bearer key\", capture.authorization)\n\t}\n\tif !strings.Contains(capture.requestURL, \"/v1/chat/completions\") {\n\t\tt.Fatalf(\"request URL = %s, want Nebius OpenAI-compatible chat completions route\", capture.requestURL)\n\t}\n\tif !strings.Contains(capture.requestBody, `\"model\":\"meta-llama/Meta-Llama-3.1-70B-Instruct\"`) {\n\t\tt.Fatalf(\"request body = %s, want default Nebius model\", capture.requestBody)\n\t}\n\tassertOpenAIToolChoiceAutoOmittedWithoutTools(t, model, capture.requestBody)\n}\n\nfunc TestNewNebiusOpenAILLMRequiresAPIKey(t *testing.T) {\n\tt.Setenv(nebiusAPIKeyEnv, \"\")\n\n\t_, err := NewNebiusOpenAILLM(\"\", \"\")\n\tif err == nil || err.Error() != \"nebius API key is required, either as argument or set NEBIUS_API_KEY environmental variable\" {\n\t\tt.Fatalf(\"NewNebiusOpenAILLM error = %v, want Nebius API key required\", err)\n\t}\n}\n\nfunc TestNewLettaOpenAILLMDefaultsMatchReference(t *testing.T) {\n\tt.Setenv(lettaAPIKeyEnv, \"env-letta-key\")\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\n\tmodel, err := NewLettaOpenAILLM(\"agent-123\", \"\", \"\", withOpenAILLMHTTPClient(capture))\n\tif err != nil {\n\t\tt.Fatalf(\"NewLettaOpenAILLM error = %v\", err)\n\t}\n\n\t_, _ = startOpenAIChat(t, model, context.Background(), llm.NewChatContext(), llm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}))\n\n\tif model.Model() != \"agent-123\" {\n\t\tt.Fatalf(\"Model = %q, want Letta agent id\", model.Model())\n\t}\n\tif model.Provider() != \"api.letta.com\" {\n\t\tt.Fatalf(\"Provider() = %q, want Letta endpoint host\", model.Provider())\n\t}\n\tif capture.authorization != \"Bearer env-letta-key\" {\n\t\tt.Fatalf(\"Authorization = %q, want Letta bearer key\", capture.authorization)\n\t}\n\tif capture.requestURL != \"https://api.letta.com/v1/chat/completions\" {\n\t\tt.Fatalf(\"request URL = %s, want Letta chat completions endpoint without duplicated path\", capture.requestURL)\n\t}\n\tif !strings.Contains(capture.requestBody, `\"model\":\"agent-123\"`) {\n\t\tt.Fatalf(\"request body = %s, want Letta agent id as model\", capture.requestBody)\n\t}\n}\n\nfunc TestNewLettaOpenAILLMRequiresAPIKey(t *testing.T) {\n\tt.Setenv(lettaAPIKeyEnv, \"\")\n\n\t_, err := NewLettaOpenAILLM(\"agent-123\", \"\", \"\")\n\tif err == nil || err.Error() != \"letta API key is required, either as argument or set LETTA_API_KEY environmental variable\" {\n\t\tt.Fatalf(\"NewLettaOpenAILLM error = %v, want Letta API key required\", err)\n\t}\n}\n\nfunc TestNewLettaOpenAILLMValidatesBaseURL(t *testing.T) {\n\tt.Setenv(lettaAPIKeyEnv, \"env-letta-key\")\n\n\t_, err := NewLettaOpenAILLM(\"agent-123\", \"ftp://api.letta.com/v1/chat/completions\", \"\")\n\tif err == nil || err.Error() != \"invalid URL scheme: \\\"ftp\\\"; must be \\\"http\\\" or \\\"https\\\"\" {\n\t\tt.Fatalf(\"NewLettaOpenAILLM invalid scheme error = %v\", err)\n\t}\n\n\t_, err = NewLettaOpenAILLM(\"agent-123\", \"https:///v1/chat/completions\", \"\")\n\tif err == nil || err.Error() != \"URL \\\"https:///v1/chat/completions\\\" is missing a network location (e.g., domain name)\" {\n\t\tt.Fatalf(\"NewLettaOpenAILLM missing host error = %v\", err)\n\t}\n}\n\nfunc TestNewOllamaOpenAILLMDefaultsMatchReference(t *testing.T) {\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\n\tmodel := NewOllamaOpenAILLM(\"\", withOpenAILLMHTTPClient(capture))\n\n\t_, _ = startOpenAIChat(t, model, context.Background(), llm.NewChatContext(), llm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}))\n\n\tif model.Model() != \"llama3.1\" {\n\t\tt.Fatalf(\"Model = %q, want llama3.1\", model.Model())\n\t}\n\tif model.Provider() != \"localhost:11434\" {\n\t\tt.Fatalf(\"Provider() = %q, want local Ollama endpoint host\", model.Provider())\n\t}\n\tif capture.authorization != \"Bearer ollama\" {\n\t\tt.Fatalf(\"Authorization = %q, want reference ollama API key\", capture.authorization)\n\t}\n\tif !strings.Contains(capture.requestURL, \"http://localhost:11434/v1/chat/completions\") {\n\t\tt.Fatalf(\"request URL = %s, want local Ollama chat completions route\", capture.requestURL)\n\t}\n\tif !strings.Contains(capture.requestBody, `\"model\":\"llama3.1\"`) {\n\t\tt.Fatalf(\"request body = %s, want default Ollama model\", capture.requestBody)\n\t}\n\tassertOpenAIToolChoiceAutoOmittedWithoutTools(t, model, capture.requestBody)\n}\n\nfunc TestNewCometAPIOpenAILLMDefaultsMatchReference(t *testing.T) {\n\tt.Setenv(cometAPIKeyEnv, \"env-comet-key\")\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\n\tmodel, err := NewCometAPIOpenAILLM(\"\", \"\", withOpenAILLMHTTPClient(capture))\n\tif err != nil {\n\t\tt.Fatalf(\"NewCometAPIOpenAILLM error = %v\", err)\n\t}\n\n\t_, _ = startOpenAIChat(t, model, context.Background(), llm.NewChatContext(), llm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}))\n\n\tif model.Model() != \"gpt-5-chat-latest\" {\n\t\tt.Fatalf(\"Model = %q, want gpt-5-chat-latest\", model.Model())\n\t}\n\tif model.Provider() != \"api.cometapi.com\" {\n\t\tt.Fatalf(\"Provider() = %q, want CometAPI endpoint host\", model.Provider())\n\t}\n\tif capture.authorization != \"Bearer env-comet-key\" {\n\t\tt.Fatalf(\"Authorization = %q, want CometAPI bearer key\", capture.authorization)\n\t}\n\tif !strings.Contains(capture.requestURL, \"/v1/chat/completions\") {\n\t\tt.Fatalf(\"request URL = %s, want OpenAI-compatible chat completions route\", capture.requestURL)\n\t}\n\tif !strings.Contains(capture.requestBody, `\"model\":\"gpt-5-chat-latest\"`) {\n\t\tt.Fatalf(\"request body = %s, want default CometAPI model\", capture.requestBody)\n\t}\n\tassertOpenAIToolChoiceAutoOmittedWithoutTools(t, model, capture.requestBody)\n}\n\nfunc TestNewCometAPIOpenAILLMRequiresAPIKey(t *testing.T) {\n\tt.Setenv(cometAPIKeyEnv, \"\")\n\n\t_, err := NewCometAPIOpenAILLM(\"\", \"\")\n\tif err == nil || err.Error() != \"CometAPI API key is required, either as argument or set COMETAPI_API_KEY environmental variable\" {\n\t\tt.Fatalf(\"NewCometAPIOpenAILLM error = %v, want CometAPI API key required\", err)\n\t}\n}\n\nfunc TestNewOctoAIOpenAILLMDefaultsMatchReference(t *testing.T) {\n\tt.Setenv(octoAIAPIKeyEnv, \"env-octo-key\")\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\n\tmodel, err := NewOctoAIOpenAILLM(\"\", \"\", withOpenAILLMHTTPClient(capture))\n\tif err != nil {\n\t\tt.Fatalf(\"NewOctoAIOpenAILLM error = %v\", err)\n\t}\n\n\t_, _ = startOpenAIChat(t, model, context.Background(), llm.NewChatContext(), llm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}))\n\n\tif model.Model() != \"llama-2-13b-chat\" {\n\t\tt.Fatalf(\"Model = %q, want llama-2-13b-chat\", model.Model())\n\t}\n\tif model.Provider() != \"text.octoai.run\" {\n\t\tt.Fatalf(\"Provider() = %q, want OctoAI endpoint host\", model.Provider())\n\t}\n\tif capture.authorization != \"Bearer env-octo-key\" {\n\t\tt.Fatalf(\"Authorization = %q, want OctoAI bearer key\", capture.authorization)\n\t}\n\tif !strings.Contains(capture.requestURL, \"/v1/chat/completions\") {\n\t\tt.Fatalf(\"request URL = %s, want OpenAI-compatible chat completions route\", capture.requestURL)\n\t}\n\tif !strings.Contains(capture.requestBody, `\"model\":\"llama-2-13b-chat\"`) {\n\t\tt.Fatalf(\"request body = %s, want default OctoAI model\", capture.requestBody)\n\t}\n\tassertOpenAIToolChoiceAutoOmittedWithoutTools(t, model, capture.requestBody)\n}\n\nfunc TestNewOctoAIOpenAILLMRequiresAPIKey(t *testing.T) {\n\tt.Setenv(octoAIAPIKeyEnv, \"\")\n\n\t_, err := NewOctoAIOpenAILLM(\"\", \"\")\n\tif err == nil || err.Error() != \"OctoAI API key is required, either as argument or set OCTOAI_TOKEN environmental variable\" {\n\t\tt.Fatalf(\"NewOctoAIOpenAILLM error = %v, want OctoAI API key required\", err)\n\t}\n}\n\nfunc TestNewSambaNovaOpenAILLMDefaultsMatchReference(t *testing.T) {\n\tt.Setenv(sambaNovaAPIKeyEnv, \"env-sambanova-key\")\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\n\tmodel, err := NewSambaNovaOpenAILLM(\"\", \"\", withOpenAILLMHTTPClient(capture))\n\tif err != nil {\n\t\tt.Fatalf(\"NewSambaNovaOpenAILLM error = %v\", err)\n\t}\n\n\t_, _ = startOpenAIChat(t, model, context.Background(), llm.NewChatContext(), llm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}))\n\n\tif model.Model() != \"DeepSeek-R1-0528\" {\n\t\tt.Fatalf(\"Model = %q, want DeepSeek-R1-0528\", model.Model())\n\t}\n\tif model.Provider() != \"api.sambanova.ai\" {\n\t\tt.Fatalf(\"Provider() = %q, want SambaNova endpoint host\", model.Provider())\n\t}\n\tif capture.authorization != \"Bearer env-sambanova-key\" {\n\t\tt.Fatalf(\"Authorization = %q, want SambaNova bearer key\", capture.authorization)\n\t}\n\tif !strings.Contains(capture.requestURL, \"/v1/chat/completions\") {\n\t\tt.Fatalf(\"request URL = %s, want OpenAI-compatible chat completions route\", capture.requestURL)\n\t}\n\tif !strings.Contains(capture.requestBody, `\"model\":\"DeepSeek-R1-0528\"`) {\n\t\tt.Fatalf(\"request body = %s, want default SambaNova model\", capture.requestBody)\n\t}\n\tassertOpenAIToolChoiceAutoOmittedWithoutTools(t, model, capture.requestBody)\n}\n\nfunc TestNewSambaNovaOpenAILLMOmitsStrictToolSchema(t *testing.T) {\n\tt.Setenv(sambaNovaAPIKeyEnv, \"env-sambanova-key\")\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\tmodel, err := NewSambaNovaOpenAILLM(\"\", \"\", withOpenAILLMHTTPClient(capture))\n\tif err != nil {\n\t\tt.Fatalf(\"NewSambaNovaOpenAILLM error = %v\", err)\n\t}\n\n\t_, _ = startOpenAIChat(t, model,\n\t\tcontext.Background(),\n\t\tllm.NewChatContext(),\n\t\tllm.WithTools([]llm.Tool{requestTestTool{}}),\n\t\tllm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}),\n\t)\n\n\tvar body map[string]any\n\tif err := json.Unmarshal([]byte(capture.requestBody), &body); err != nil {\n\t\tt.Fatalf(\"decode request body: %v\", err)\n\t}\n\ttools := body[\"tools\"].([]any)\n\tfunction := tools[0].(map[string]any)[\"function\"].(map[string]any)\n\tif _, ok := function[\"strict\"]; ok {\n\t\tt.Fatalf(\"strict = %#v, want omitted for SambaNova legacy tool schema; body %s\", function[\"strict\"], capture.requestBody)\n\t}\n}\n\nfunc TestNewSambaNovaOpenAILLMRequiresAPIKey(t *testing.T) {\n\tt.Setenv(sambaNovaAPIKeyEnv, \"\")\n\n\t_, err := NewSambaNovaOpenAILLM(\"\", \"\")\n\tif err == nil || err.Error() != \"SambaNova API key is required, either as argument or set SAMBANOVA_API_KEY environment variable\" {\n\t\tt.Fatalf(\"NewSambaNovaOpenAILLM error = %v, want SambaNova API key required\", err)\n\t}\n}\n\nfunc TestNewCerebrasOpenAILLMDefaultsMatchReference(t *testing.T) {\n\tt.Setenv(cerebrasAPIKeyEnv, \"env-cerebras-key\")\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\n\tmodel, err := NewCerebrasOpenAILLM(\"\", \"\", withOpenAILLMHTTPClient(capture))\n\tif err != nil {\n\t\tt.Fatalf(\"NewCerebrasOpenAILLM error = %v\", err)\n\t}\n\n\t_, _ = startOpenAIChat(t, model, context.Background(), llm.NewChatContext(), llm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}))\n\n\tif model.Model() != \"llama-4-scout-17b-16e-instruct\" {\n\t\tt.Fatalf(\"Model = %q, want llama-4-scout-17b-16e-instruct\", model.Model())\n\t}\n\tif model.Provider() != \"api.cerebras.ai\" {\n\t\tt.Fatalf(\"Provider() = %q, want Cerebras endpoint host\", model.Provider())\n\t}\n\tif capture.authorization != \"Bearer env-cerebras-key\" {\n\t\tt.Fatalf(\"Authorization = %q, want Cerebras bearer key\", capture.authorization)\n\t}\n\tif !strings.Contains(capture.requestURL, \"/v1/chat/completions\") {\n\t\tt.Fatalf(\"request URL = %s, want OpenAI-compatible chat completions route\", capture.requestURL)\n\t}\n\tif !strings.Contains(capture.requestBody, `\"model\":\"llama-4-scout-17b-16e-instruct\"`) {\n\t\tt.Fatalf(\"request body = %s, want default Cerebras model\", capture.requestBody)\n\t}\n}\n\nfunc TestNewCerebrasOpenAILLMOmitsStrictToolSchema(t *testing.T) {\n\tt.Setenv(cerebrasAPIKeyEnv, \"env-cerebras-key\")\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\tmodel, err := NewCerebrasOpenAILLM(\"\", \"\", withOpenAILLMHTTPClient(capture))\n\tif err != nil {\n\t\tt.Fatalf(\"NewCerebrasOpenAILLM error = %v\", err)\n\t}\n\n\t_, _ = startOpenAIChat(t, model,\n\t\tcontext.Background(),\n\t\tllm.NewChatContext(),\n\t\tllm.WithTools([]llm.Tool{requestTestTool{}}),\n\t\tllm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}),\n\t)\n\n\tvar body map[string]any\n\tif err := json.Unmarshal([]byte(capture.requestBody), &body); err != nil {\n\t\tt.Fatalf(\"decode request body: %v\", err)\n\t}\n\ttools := body[\"tools\"].([]any)\n\tfunction := tools[0].(map[string]any)[\"function\"].(map[string]any)\n\tif _, ok := function[\"strict\"]; ok {\n\t\tt.Fatalf(\"strict = %#v, want omitted for Cerebras legacy tool schema; body %s\", function[\"strict\"], capture.requestBody)\n\t}\n}\n\nfunc TestNewCerebrasOpenAILLMRequiresAPIKey(t *testing.T) {\n\tt.Setenv(cerebrasAPIKeyEnv, \"\")\n\n\t_, err := NewCerebrasOpenAILLM(\"\", \"\")\n\tif err == nil || err.Error() != \"cerebras API key is required, either as argument or set CEREBRAS_API_KEY environment variable\" {\n\t\tt.Fatalf(\"NewCerebrasOpenAILLM error = %v, want Cerebras API key required\", err)\n\t}\n}\n\nfunc TestNewXAIOpenAILLMDefaultsMatchReference(t *testing.T) {\n\tt.Setenv(xAIAPIKeyEnv, \"env-xai-key\")\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\n\tmodel, err := NewXAIOpenAILLM(\"\", \"\", withOpenAILLMHTTPClient(capture))\n\tif err != nil {\n\t\tt.Fatalf(\"NewXAIOpenAILLM error = %v\", err)\n\t}\n\n\t_, _ = startOpenAIChat(t, model, context.Background(), llm.NewChatContext(), llm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}))\n\n\tif model.Model() != \"grok-3-fast\" {\n\t\tt.Fatalf(\"Model = %q, want grok-3-fast\", model.Model())\n\t}\n\tif model.Provider() != \"api.x.ai\" {\n\t\tt.Fatalf(\"Provider() = %q, want xAI endpoint host\", model.Provider())\n\t}\n\tif capture.authorization != \"Bearer env-xai-key\" {\n\t\tt.Fatalf(\"Authorization = %q, want xAI bearer key\", capture.authorization)\n\t}\n\tif !strings.Contains(capture.requestURL, \"/v1/chat/completions\") {\n\t\tt.Fatalf(\"request URL = %s, want OpenAI-compatible chat completions route\", capture.requestURL)\n\t}\n\tif !strings.Contains(capture.requestBody, `\"model\":\"grok-3-fast\"`) {\n\t\tt.Fatalf(\"request body = %s, want default xAI model\", capture.requestBody)\n\t}\n\tassertOpenAIToolChoiceAutoOmittedWithoutTools(t, model, capture.requestBody)\n}\n\nfunc TestNewXAIOpenAILLMRequiresAPIKey(t *testing.T) {\n\tt.Setenv(xAIAPIKeyEnv, \"\")\n\n\t_, err := NewXAIOpenAILLM(\"\", \"\")\n\tif err == nil || err.Error() != \"XAI API key is required, either as argument or set XAI_API_KEY environmental variable\" {\n\t\tt.Fatalf(\"NewXAIOpenAILLM error = %v, want xAI API key required\", err)\n\t}\n}\n\nfunc TestNewOpenAILLMChatUsesConfiguredKeyAndDefaultModel(t *testing.T) {\n\tt.Setenv(openAIAPIKeyEnv, \"env-key\")\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\tconfig := openaisdk.DefaultConfig(\"env-key\")\n\tconfig.HTTPClient = capture\n\n\tmodel, err := newOpenAILLMWithConfigAndModel(config, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"newOpenAILLMWithConfigAndModel error = %v\", err)\n\t}\n\n\t_, _ = startOpenAIChat(t, model, context.Background(), llm.NewChatContext(), llm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}))\n\n\tif capture.requestBody == \"\" || !strings.Contains(capture.requestBody, `\"model\":\"gpt-4.1\"`) {\n\t\tt.Fatalf(\"request body = %s, want default model\", capture.requestBody)\n\t}\n\tif capture.authorization != \"Bearer env-key\" {\n\t\tt.Fatalf(\"Authorization = %q, want bearer env key\", capture.authorization)\n\t}\n}\n\nfunc TestOpenAIChatOmitsDefaultParallelToolCalls(t *testing.T) {\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\tconfig := openaisdk.DefaultConfig(\"test-key\")\n\tconfig.HTTPClient = capture\n\tmodel := mustNewOpenAILLMWithConfig(t, config, \"gpt-4o\")\n\n\t_, _ = startOpenAIChat(t, model, context.Background(), llm.NewChatContext(), llm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}))\n\n\tif strings.Contains(capture.requestBody, `\"parallel_tool_calls\"`) {\n\t\tt.Fatalf(\"request body = %s, want default parallel_tool_calls omitted\", capture.requestBody)\n\t}\n}\n\nfunc TestOpenAIChatSerializesExplicitParallelToolCalls(t *testing.T) {\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\tconfig := openaisdk.DefaultConfig(\"test-key\")\n\tconfig.HTTPClient = capture\n\tmodel := mustNewOpenAILLMWithConfig(t, config, \"gpt-4o\")\n\n\t_, _ = startOpenAIChat(t, model,\n\t\tcontext.Background(),\n\t\tllm.NewChatContext(),\n\t\tllm.WithParallelToolCalls(false),\n\t\tllm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}),\n\t)\n\n\tif !strings.Contains(capture.requestBody, `\"parallel_tool_calls\":false`) {\n\t\tt.Fatalf(\"request body = %s, want explicit parallel_tool_calls false\", capture.requestBody)\n\t}\n}\n\nfunc TestOpenAIChatAppliesProviderParallelToolCalls(t *testing.T) {\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\tmodel := NewOpenAILLMWithBaseURLAndHTTPClient(\n\t\t\"test-key\",\n\t\t\"gpt-4o\",\n\t\t\"https://openai.test/v1\",\n\t\tcapture,\n\t\tWithOpenAILLMParallelToolCalls(false),\n\t)\n\n\t_, _ = startOpenAIChat(t, model, context.Background(), llm.NewChatContext(), llm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}))\n\n\tif !strings.Contains(capture.requestBody, `\"parallel_tool_calls\":false`) {\n\t\tt.Fatalf(\"request body = %s, want provider parallel_tool_calls false\", capture.requestBody)\n\t}\n}\n\nfunc TestOpenAIChatAppliesProviderToolChoice(t *testing.T) {\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\tmodel := NewOpenAILLMWithBaseURLAndHTTPClient(\n\t\t\"test-key\",\n\t\t\"gpt-4o\",\n\t\t\"https://openai.test/v1\",\n\t\tcapture,\n\t\tWithOpenAILLMToolChoice(\"none\"),\n\t)\n\n\t_, _ = startOpenAIChat(t, model, context.Background(), llm.NewChatContext(),\n\t\tllm.WithTools([]llm.Tool{requestTestTool{}}),\n\t\tllm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}),\n\t)\n\n\tif !strings.Contains(capture.requestBody, `\"tool_choice\":\"none\"`) {\n\t\tt.Fatalf(\"request body = %s, want provider tool_choice none\", capture.requestBody)\n\t}\n}\n\nfunc TestOpenAIChatAppliesProviderTemperature(t *testing.T) {\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\tmodel := NewOpenAILLMWithBaseURLAndHTTPClient(\n\t\t\"test-key\",\n\t\t\"gpt-4o\",\n\t\t\"https://openai.test/v1\",\n\t\tcapture,\n\t\tWithOpenAILLMTemperature(0.3),\n\t)\n\n\t_, _ = startOpenAIChat(t, model, context.Background(), llm.NewChatContext(), llm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}))\n\n\tif !strings.Contains(capture.requestBody, `\"temperature\":0.3`) {\n\t\tt.Fatalf(\"request body = %s, want provider temperature\", capture.requestBody)\n\t}\n}\n\nfunc TestOpenAIChatProviderTypedOptionsOverrideGenericExtraParams(t *testing.T) {\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\tmodel := NewOpenAILLMWithBaseURLAndHTTPClient(\n\t\t\"test-key\",\n\t\t\"gpt-4o\",\n\t\t\"https://openai.test/v1\",\n\t\tcapture,\n\t\tWithOpenAILLMTemperature(0.3),\n\t\tWithOpenAILLMExtraParams(map[string]any{\n\t\t\t\"temperature\": 0.9,\n\t\t\t\"metadata\": map[string]any{\"trace\": \"abc\"},\n\t\t}),\n\t)\n\n\t_, _ = startOpenAIChat(t, model, context.Background(), llm.NewChatContext(), llm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}))\n\n\tif !strings.Contains(capture.requestBody, `\"temperature\":0.3`) {\n\t\tt.Fatalf(\"request body = %s, want typed provider temperature\", capture.requestBody)\n\t}\n\tif strings.Contains(capture.requestBody, `\"temperature\":0.9`) {\n\t\tt.Fatalf(\"request body = %s, want typed temperature to override generic extra params\", capture.requestBody)\n\t}\n\tif !strings.Contains(capture.requestBody, `\"metadata\":{\"trace\":\"abc\"}`) {\n\t\tt.Fatalf(\"request body = %s, want generic metadata preserved\", capture.requestBody)\n\t}\n}\n\nfunc TestOpenAIChatAppliesProviderTopP(t *testing.T) {\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\tmodel := NewOpenAILLMWithBaseURLAndHTTPClient(\n\t\t\"test-key\",\n\t\t\"gpt-4o\",\n\t\t\"https://openai.test/v1\",\n\t\tcapture,\n\t\tWithOpenAILLMTopP(0.4),\n\t)\n\n\t_, _ = startOpenAIChat(t, model, context.Background(), llm.NewChatContext(), llm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}))\n\n\tif !strings.Contains(capture.requestBody, `\"top_p\":0.4`) {\n\t\tt.Fatalf(\"request body = %s, want provider top_p\", capture.requestBody)\n\t}\n}\n\nfunc TestOpenAIChatAppliesProviderMaxCompletionTokens(t *testing.T) {\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\tmodel := NewOpenAILLMWithBaseURLAndHTTPClient(\n\t\t\"test-key\",\n\t\t\"gpt-4o\",\n\t\t\"https://openai.test/v1\",\n\t\tcapture,\n\t\tWithOpenAILLMMaxCompletionTokens(256),\n\t)\n\n\t_, _ = startOpenAIChat(t, model, context.Background(), llm.NewChatContext(), llm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}))\n\n\tif !strings.Contains(capture.requestBody, `\"max_completion_tokens\":256`) {\n\t\tt.Fatalf(\"request body = %s, want provider max_completion_tokens\", capture.requestBody)\n\t}\n}\n\nfunc TestOpenAIChatAppliesProviderStore(t *testing.T) {\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\tmodel := NewOpenAILLMWithBaseURLAndHTTPClient(\n\t\t\"test-key\",\n\t\t\"gpt-4o\",\n\t\t\"https://openai.test/v1\",\n\t\tcapture,\n\t\tWithOpenAILLMStore(true),\n\t)\n\n\t_, _ = startOpenAIChat(t, model, context.Background(), llm.NewChatContext(), llm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}))\n\n\tif !strings.Contains(capture.requestBody, `\"store\":true`) {\n\t\tt.Fatalf(\"request body = %s, want provider store true\", capture.requestBody)\n\t}\n}\n\nfunc TestOpenAIChatAppliesProviderServiceTier(t *testing.T) {\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\tmodel := NewOpenAILLMWithBaseURLAndHTTPClient(\n\t\t\"test-key\",\n\t\t\"gpt-4o\",\n\t\t\"https://openai.test/v1\",\n\t\tcapture,\n\t\tWithOpenAILLMServiceTier(\"priority\"),\n\t)\n\n\t_, _ = startOpenAIChat(t, model, context.Background(), llm.NewChatContext(), llm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}))\n\n\tif !strings.Contains(capture.requestBody, `\"service_tier\":\"priority\"`) {\n\t\tt.Fatalf(\"request body = %s, want provider service_tier\", capture.requestBody)\n\t}\n}\n\nfunc TestOpenAIChatAppliesProviderSafetyIdentifier(t *testing.T) {\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\tmodel := NewOpenAILLMWithBaseURLAndHTTPClient(\n\t\t\"test-key\",\n\t\t\"gpt-4o\",\n\t\t\"https://openai.test/v1\",\n\t\tcapture,\n\t\tWithOpenAILLMSafetyIdentifier(\"hashed-user\"),\n\t)\n\n\t_, _ = startOpenAIChat(t, model, context.Background(), llm.NewChatContext(), llm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}))\n\n\tif !strings.Contains(capture.requestBody, `\"safety_identifier\":\"hashed-user\"`) {\n\t\tt.Fatalf(\"request body = %s, want provider safety_identifier\", capture.requestBody)\n\t}\n}\n\nfunc TestOpenAIChatAppliesProviderExtraHeaders(t *testing.T) {\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\tmodel := NewOpenAILLMWithBaseURLAndHTTPClient(\n\t\t\"test-key\",\n\t\t\"gpt-4o\",\n\t\t\"https://openai.test/v1\",\n\t\tcapture,\n\t\tWithOpenAILLMExtraHeaders(map[string]string{\n\t\t\t\"X-Request-Group\": \"gold\",\n\t\t}),\n\t)\n\n\t_, _ = startOpenAIChat(t, model, context.Background(), llm.NewChatContext(), llm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}))\n\n\tif got := capture.header.Get(\"X-Request-Group\"); got != \"gold\" {\n\t\tt.Fatalf(\"X-Request-Group = %q, want provider extra header\", got)\n\t}\n}\n\nfunc TestOpenAIChatAppliesProviderExtraQuery(t *testing.T) {\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\tmodel := NewOpenAILLMWithBaseURLAndHTTPClient(\n\t\t\"test-key\",\n\t\t\"gpt-4o\",\n\t\t\"https://openai.test/v1\",\n\t\tcapture,\n\t\tWithOpenAILLMExtraQuery(map[string]string{\n\t\t\t\"api-version\": \"preview\",\n\t\t}),\n\t)\n\n\t_, _ = startOpenAIChat(t, model, context.Background(), llm.NewChatContext(), llm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}))\n\n\trequestURL, err := url.Parse(capture.requestURL)\n\tif err != nil {\n\t\tt.Fatalf(\"request URL parse error = %v\", err)\n\t}\n\tif got := requestURL.Query().Get(\"api-version\"); got != \"preview\" {\n\t\tt.Fatalf(\"api-version query = %q, want provider extra query\", got)\n\t}\n}\n\nfunc TestOpenAIChatAppliesCallExtraHeaders(t *testing.T) {\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\tmodel := NewOpenAILLMWithBaseURLAndHTTPClient(\n\t\t\"test-key\",\n\t\t\"gpt-4o\",\n\t\t\"https://openai.test/v1\",\n\t\tcapture,\n\t)\n\n\t_, _ = startOpenAIChat(t, model,\n\t\tcontext.Background(),\n\t\tllm.NewChatContext(),\n\t\tllm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}),\n\t\tllm.WithExtraParams(map[string]any{\n\t\t\t\"extra_headers\": map[string]any{\n\t\t\t\t\"X-Request-Group\": \"silver\",\n\t\t\t},\n\t\t}),\n\t)\n\n\tif got := capture.header.Get(\"X-Request-Group\"); got != \"silver\" {\n\t\tt.Fatalf(\"X-Request-Group = %q, want call extra header\", got)\n\t}\n}\n\nfunc TestOpenAIChatAppliesCallExtraQuery(t *testing.T) {\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\tmodel := NewOpenAILLMWithBaseURLAndHTTPClient(\n\t\t\"test-key\",\n\t\t\"gpt-4o\",\n\t\t\"https://openai.test/v1\",\n\t\tcapture,\n\t)\n\n\t_, _ = startOpenAIChat(t, model,\n\t\tcontext.Background(),\n\t\tllm.NewChatContext(),\n\t\tllm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}),\n\t\tllm.WithExtraParams(map[string]any{\n\t\t\t\"extra_query\": map[string]any{\n\t\t\t\t\"api-version\": \"call-preview\",\n\t\t\t},\n\t\t}),\n\t)\n\n\trequestURL, err := url.Parse(capture.requestURL)\n\tif err != nil {\n\t\tt.Fatalf(\"request URL parse error = %v\", err)\n\t}\n\tif got := requestURL.Query().Get(\"api-version\"); got != \"call-preview\" {\n\t\tt.Fatalf(\"api-version query = %q, want call extra query\", got)\n\t}\n}\n\nfunc TestOpenAIChatProviderExtraHeadersOverrideCallExtraHeaders(t *testing.T) {\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\tmodel := NewOpenAILLMWithBaseURLAndHTTPClient(\n\t\t\"test-key\",\n\t\t\"gpt-4o\",\n\t\t\"https://openai.test/v1\",\n\t\tcapture,\n\t\tWithOpenAILLMExtraHeaders(map[string]string{\n\t\t\t\"X-Request-Group\": \"provider\",\n\t\t}),\n\t)\n\n\t_, _ = startOpenAIChat(t, model,\n\t\tcontext.Background(),\n\t\tllm.NewChatContext(),\n\t\tllm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}),\n\t\tllm.WithExtraParams(map[string]any{\n\t\t\t\"extra_headers\": map[string]any{\n\t\t\t\t\"X-Request-Group\": \"call\",\n\t\t\t\t\"X-Trace-ID\": \"trace-123\",\n\t\t\t},\n\t\t}),\n\t)\n\n\tif got := capture.header.Get(\"X-Request-Group\"); got != \"provider\" {\n\t\tt.Fatalf(\"X-Request-Group = %q, want provider extra header override\", got)\n\t}\n\tif got := capture.header.Get(\"X-Trace-ID\"); got != \"trace-123\" {\n\t\tt.Fatalf(\"X-Trace-ID = %q, want call-only extra header preserved\", got)\n\t}\n}\n\nfunc TestOpenAIChatProviderExtraQueryOverridesCallExtraQuery(t *testing.T) {\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\tmodel := NewOpenAILLMWithBaseURLAndHTTPClient(\n\t\t\"test-key\",\n\t\t\"gpt-4o\",\n\t\t\"https://openai.test/v1\",\n\t\tcapture,\n\t\tWithOpenAILLMExtraQuery(map[string]string{\n\t\t\t\"api-version\": \"provider-preview\",\n\t\t}),\n\t)\n\n\t_, _ = startOpenAIChat(t, model,\n\t\tcontext.Background(),\n\t\tllm.NewChatContext(),\n\t\tllm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}),\n\t\tllm.WithExtraParams(map[string]any{\n\t\t\t\"extra_query\": map[string]any{\n\t\t\t\t\"api-version\": \"call-preview\",\n\t\t\t\t\"trace\": \"trace-123\",\n\t\t\t},\n\t\t}),\n\t)\n\n\trequestURL, err := url.Parse(capture.requestURL)\n\tif err != nil {\n\t\tt.Fatalf(\"request URL parse error = %v\", err)\n\t}\n\tif got := requestURL.Query().Get(\"api-version\"); got != \"provider-preview\" {\n\t\tt.Fatalf(\"api-version query = %q, want provider extra query override\", got)\n\t}\n\tif got := requestURL.Query().Get(\"trace\"); got != \"trace-123\" {\n\t\tt.Fatalf(\"trace query = %q, want call-only extra query preserved\", got)\n\t}\n}\n\nfunc TestOpenAIChatAppliesProviderExtraBody(t *testing.T) {\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\tmodel := NewOpenAILLMWithBaseURLAndHTTPClient(\n\t\t\"test-key\",\n\t\t\"gpt-4o\",\n\t\t\"https://openai.test/v1\",\n\t\tcapture,\n\t\tWithOpenAILLMExtraBody(map[string]any{\n\t\t\t\"prompt_cache_key\": \"room-123\",\n\t\t}),\n\t)\n\n\t_, _ = startOpenAIChat(t, model, context.Background(), llm.NewChatContext(), llm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}))\n\n\tif !strings.Contains(capture.requestBody, `\"prompt_cache_key\":\"room-123\"`) {\n\t\tt.Fatalf(\"request body = %s, want provider extra body field\", capture.requestBody)\n\t}\n\tif strings.Contains(capture.requestBody, \"extra_body\") {\n\t\tt.Fatalf(\"request body = %s, want extra body merged into request\", capture.requestBody)\n\t}\n}\n\nfunc TestOpenAIChatAppliesProviderPromptCacheOptions(t *testing.T) {\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\tmodel := NewOpenAILLMWithBaseURLAndHTTPClient(\n\t\t\"test-key\",\n\t\t\"gpt-4o\",\n\t\t\"https://openai.test/v1\",\n\t\tcapture,\n\t\tWithOpenAILLMPromptCacheKey(\"room-123\"),\n\t\tWithOpenAILLMPromptCacheRetention(\"24h\"),\n\t)\n\n\t_, _ = startOpenAIChat(t, model, context.Background(), llm.NewChatContext(), llm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}))\n\n\tif !strings.Contains(capture.requestBody, `\"prompt_cache_key\":\"room-123\"`) {\n\t\tt.Fatalf(\"request body = %s, want provider prompt_cache_key\", capture.requestBody)\n\t}\n\tif !strings.Contains(capture.requestBody, `\"prompt_cache_retention\":\"24h\"`) {\n\t\tt.Fatalf(\"request body = %s, want provider prompt_cache_retention\", capture.requestBody)\n\t}\n}\n\nfunc TestOpenAIChatAppliesProviderUser(t *testing.T) {\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\tmodel := NewOpenAILLMWithBaseURLAndHTTPClient(\n\t\t\"test-key\",\n\t\t\"gpt-4o\",\n\t\t\"https://openai.test/v1\",\n\t\tcapture,\n\t\tWithOpenAILLMUser(\"caller-123\"),\n\t)\n\n\t_, _ = startOpenAIChat(t, model, context.Background(), llm.NewChatContext(), llm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}))\n\n\tif !strings.Contains(capture.requestBody, `\"user\":\"caller-123\"`) {\n\t\tt.Fatalf(\"request body = %s, want provider user\", capture.requestBody)\n\t}\n}\n\nfunc TestOpenAIChatAppliesProviderMetadata(t *testing.T) {\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\tmodel := NewOpenAILLMWithBaseURLAndHTTPClient(\n\t\t\"test-key\",\n\t\t\"gpt-4o\",\n\t\t\"https://openai.test/v1\",\n\t\tcapture,\n\t\tWithOpenAILLMMetadata(map[string]string{\"trace\": \"abc\"}),\n\t)\n\n\t_, _ = startOpenAIChat(t, model, context.Background(), llm.NewChatContext(), llm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}))\n\n\tif !strings.Contains(capture.requestBody, `\"metadata\":{\"trace\":\"abc\"}`) {\n\t\tt.Fatalf(\"request body = %s, want provider metadata\", capture.requestBody)\n\t}\n}\n\nfunc TestOpenAIChatAppliesProviderVerbosity(t *testing.T) {\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\tmodel := NewOpenAILLMWithBaseURLAndHTTPClient(\n\t\t\"test-key\",\n\t\t\"gpt-5\",\n\t\t\"https://openai.test/v1\",\n\t\tcapture,\n\t\tWithOpenAILLMVerbosity(\"low\"),\n\t)\n\n\t_, _ = startOpenAIChat(t, model, context.Background(), llm.NewChatContext(), llm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}))\n\n\tvar body map[string]any\n\tif err := json.Unmarshal([]byte(capture.requestBody), &body); err != nil {\n\t\tt.Fatalf(\"request body JSON error = %v; body = %s\", err, capture.requestBody)\n\t}\n\tif _, ok := body[\"verbosity\"]; ok {\n\t\tt.Fatalf(\"request body = %s, want no top-level verbosity\", capture.requestBody)\n\t}\n\ttext, ok := body[\"text\"].(map[string]any)\n\tif !ok || text[\"verbosity\"] != \"low\" {\n\t\tt.Fatalf(\"request body = %s, want reference text.verbosity\", capture.requestBody)\n\t}\n}\n\nfunc TestOpenAIChatPreservesProviderVerbosityWithExtraBody(t *testing.T) {\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\tmodel := NewOpenAILLMWithBaseURLAndHTTPClient(\n\t\t\"test-key\",\n\t\t\"gpt-5\",\n\t\t\"https://openai.test/v1\",\n\t\tcapture,\n\t\tWithOpenAILLMVerbosity(\"low\"),\n\t\tWithOpenAILLMExtraBody(map[string]any{\n\t\t\t\"text\": map[string]any{\"format\": \"plain\"},\n\t\t}),\n\t)\n\n\t_, _ = startOpenAIChat(t, model, context.Background(), llm.NewChatContext(), llm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}))\n\n\tvar body map[string]any\n\tif err := json.Unmarshal([]byte(capture.requestBody), &body); err != nil {\n\t\tt.Fatalf(\"request body JSON error = %v; body = %s\", err, capture.requestBody)\n\t}\n\ttext, ok := body[\"text\"].(map[string]any)\n\tif !ok {\n\t\tt.Fatalf(\"request body = %s, want text config\", capture.requestBody)\n\t}\n\tif text[\"format\"] != \"plain\" || text[\"verbosity\"] != \"low\" {\n\t\tt.Fatalf(\"text = %#v, want extra body format plus reference verbosity\", text)\n\t}\n}\n\nfunc TestOpenAIChatAppliesProviderReasoningEffort(t *testing.T) {\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\tmodel := NewOpenAILLMWithBaseURLAndHTTPClient(\n\t\t\"test-key\",\n\t\t\"gpt-5\",\n\t\t\"https://openai.test/v1\",\n\t\tcapture,\n\t\tWithOpenAILLMReasoningEffort(\"low\"),\n\t)\n\n\t_, _ = startOpenAIChat(t, model, context.Background(), llm.NewChatContext(), llm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}))\n\n\tif !strings.Contains(capture.requestBody, `\"reasoning_effort\":\"low\"`) {\n\t\tt.Fatalf(\"request body = %s, want provider reasoning_effort\", capture.requestBody)\n\t}\n}\n\nfunc TestOpenAIChatAppliesConnectOptionsTimeoutToRequestContext(t *testing.T) {\n\tsentinelErr := errors.New(\"stop after context capture\")\n\tcapture := &captureDeadlineHTTPClient{err: sentinelErr}\n\tconfig := openaisdk.DefaultConfig(\"test-key\")\n\tconfig.HTTPClient = capture\n\tmodel := mustNewOpenAILLMWithConfig(t, config, \"gpt-4o\")\n\n\t_, err := startOpenAIChat(t, model,\n\t\tcontext.Background(),\n\t\tllm.NewChatContext(),\n\t\tllm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0, Timeout: 75 * time.Millisecond}),\n\t)\n\n\tvar connectionErr *llm.APIConnectionError\n\tif !errors.As(err, &connectionErr) {\n\t\tt.Fatalf(\"Chat error = %T %v, want APIConnectionError\", err, err)\n\t}\n\tif !capture.hasDeadline {\n\t\tt.Fatal(\"request context has no deadline, want connect options timeout deadline\")\n\t}\n\tif capture.remaining <= 0 || capture.remaining > 75*time.Millisecond {\n\t\tt.Fatalf(\"request context deadline remaining = %v, want bounded by connect timeout\", capture.remaining)\n\t}\n}\n\nfunc TestOpenAIChatAppliesDefaultConnectOptionsTimeoutToRequestContext(t *testing.T) {\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\tconfig := openaisdk.DefaultConfig(\"test-key\")\n\tconfig.HTTPClient = capture\n\tmodel := mustNewOpenAILLMWithConfig(t, config, \"gpt-4o\")\n\n\t_, err := startOpenAIChat(t, model, context.Background(), llm.NewChatContext())\n\n\tvar statusErr *llm.APIStatusError\n\tif !errors.As(err, &statusErr) {\n\t\tt.Fatalf(\"Chat error = %T %v, want APIStatusError\", err, err)\n\t}\n\tif !capture.hasDeadline {\n\t\tt.Fatal(\"request context has no deadline, want default connect timeout deadline\")\n\t}\n\tif capture.remaining <= 0 || capture.remaining > llm.DefaultAPIConnectOptions().Timeout {\n\t\tt.Fatalf(\"request context deadline remaining = %v, want bounded by default connect timeout\", capture.remaining)\n\t}\n}\n\nfunc TestBuildOpenAIChatCompletionRequestAppliesExtraParams(t *testing.T) {\n\treq := buildOpenAIChatCompletionRequest(\"gpt-4o\", llm.NewChatContext(), &llm.ChatOptions{\n\t\tParallelToolCalls: true,\n\t\tExtraParams: map[string]any{\n\t\t\t\"temperature\": 0.7,\n\t\t\t\"top_p\": 0.8,\n\t\t\t\"presence_penalty\": 0.1,\n\t\t\t\"frequency_penalty\": 0.2,\n\t\t\t\"n\": 2,\n\t\t\t\"max_completion_tokens\": 128,\n\t\t\t\"logit_bias\": map[string]any{\"42\": 7.0},\n\t\t\t\"reasoning_effort\": \"low\",\n\t\t\t\"metadata\": map[string]any{\"trace\": \"abc\"},\n\t\t\t\"seed\": 42,\n\t\t\t\"stop\": []string{\"END\"},\n\t\t\t\"user\": \"caller-123\",\n\t\t\t\"store\": true,\n\t\t\t\"stream_options\": map[string]any{\"include_usage\": true},\n\t\t\t\"service_tier\": \"priority\",\n\t\t\t\"verbosity\": \"low\",\n\t\t\t\"safety_identifier\": \"hashed-user\",\n\t\t\t\"chat_template_kwargs\": map[string]any{\"enable_thinking\": false},\n\t\t\t\"prediction\": map[string]any{\"type\": \"content\", \"content\": \"known prefix\"},\n\t\t},\n\t})\n\n\tif req.Temperature != 0.7 {\n\t\tt.Fatalf(\"Temperature = %v, want 0.7\", req.Temperature)\n\t}\n\tif req.TopP != 0.8 {\n\t\tt.Fatalf(\"TopP = %v, want 0.8\", req.TopP)\n\t}\n\tif req.PresencePenalty != 0.1 {\n\t\tt.Fatalf(\"PresencePenalty = %v, want 0.1\", req.PresencePenalty)\n\t}\n\tif req.FrequencyPenalty != 0.2 {\n\t\tt.Fatalf(\"FrequencyPenalty = %v, want 0.2\", req.FrequencyPenalty)\n\t}\n\tif req.N != 2 {\n\t\tt.Fatalf(\"N = %d, want 2\", req.N)\n\t}\n\tif req.MaxCompletionTokens != 128 {\n\t\tt.Fatalf(\"MaxCompletionTokens = %d, want 128\", req.MaxCompletionTokens)\n\t}\n\tif req.LogitBias[\"42\"] != 7 {\n\t\tt.Fatalf(\"LogitBias = %#v, want token 42 bias 7\", req.LogitBias)\n\t}\n\tif req.ReasoningEffort != \"low\" {\n\t\tt.Fatalf(\"ReasoningEffort = %q, want low\", req.ReasoningEffort)\n\t}\n\tif req.Metadata[\"trace\"] != \"abc\" {\n\t\tt.Fatalf(\"Metadata[trace] = %q, want abc\", req.Metadata[\"trace\"])\n\t}\n\tif req.Seed == nil || *req.Seed != 42 {\n\t\tt.Fatalf(\"Seed = %#v, want 42\", req.Seed)\n\t}\n\tif len(req.Stop) != 1 || req.Stop[0] != \"END\" {\n\t\tt.Fatalf(\"Stop = %#v, want END\", req.Stop)\n\t}\n\tif req.User != \"caller-123\" {\n\t\tt.Fatalf(\"User = %q, want caller-123\", req.User)\n\t}\n\tif !req.Store {\n\t\tt.Fatal(\"Store = false, want true\")\n\t}\n\tif req.StreamOptions == nil || !req.StreamOptions.IncludeUsage {\n\t\tt.Fatalf(\"StreamOptions = %#v, want include_usage\", req.StreamOptions)\n\t}\n\tif req.ServiceTier != openaisdk.ServiceTierPriority {\n\t\tt.Fatalf(\"ServiceTier = %q, want priority\", req.ServiceTier)\n\t}\n\tif req.Verbosity != \"low\" {\n\t\tt.Fatalf(\"Verbosity = %q, want low\", req.Verbosity)\n\t}\n\tif req.SafetyIdentifier != \"hashed-user\" {\n\t\tt.Fatalf(\"SafetyIdentifier = %q, want hashed-user\", req.SafetyIdentifier)\n\t}\n\tif enabled, ok := req.ChatTemplateKwargs[\"enable_thinking\"].(bool); !ok || enabled {\n\t\tt.Fatalf(\"ChatTemplateKwargs = %#v, want enable_thinking false\", req.ChatTemplateKwargs)\n\t}\n\tif req.Prediction == nil || req.Prediction.Type != \"content\" || req.Prediction.Content != \"known prefix\" {\n\t\tt.Fatalf(\"Prediction = %#v, want content prediction\", req.Prediction)\n\t}\n}\n\nfunc TestBuildOpenAIChatCompletionRequestAppliesExtraParamParallelToolCalls(t *testing.T) {\n\treq := buildOpenAIChatCompletionRequest(\"gpt-4o\", llm.NewChatContext(), &llm.ChatOptions{\n\t\tExtraParams: map[string]any{\n\t\t\t\"parallel_tool_calls\": false,\n\t\t},\n\t})\n\n\tif got, ok := req.ParallelToolCalls.(*bool); !ok || got == nil || *got {\n\t\tt.Fatalf(\"ParallelToolCalls = %#v, want pointer to false\", req.ParallelToolCalls)\n\t}\n}\n\nfunc TestBuildOpenAIChatCompletionRequestAppliesExtraParamToolChoice(t *testing.T) {\n\treq := buildOpenAIChatCompletionRequest(\"gpt-4o\", llm.NewChatContext(), &llm.ChatOptions{\n\t\tTools: []llm.Tool{requestTestTool{}},\n\t\tExtraParams: map[string]any{\n\t\t\t\"tool_choice\": \"none\",\n\t\t},\n\t})\n\n\tif req.ToolChoice != \"none\" {\n\t\tt.Fatalf(\"ToolChoice = %#v, want none\", req.ToolChoice)\n\t}\n}\n\nfunc TestBuildOpenAIChatCompletionRequestOmitsToolChoiceWithoutTools(t *testing.T) {\n\treq := buildOpenAIChatCompletionRequest(\"gpt-4o\", llm.NewChatContext(), &llm.ChatOptions{\n\t\tToolChoice: \"required\",\n\t\tExtraParams: map[string]any{\n\t\t\t\"tool_choice\": \"none\",\n\t\t},\n\t})\n\n\tif req.ToolChoice != nil {\n\t\tt.Fatalf(\"ToolChoice = %#v, want omitted when no tools are provided\", req.ToolChoice)\n\t}\n}\n\nfunc TestBuildOpenAIChatCompletionRequestToolPolicyOverridesExtraParams(t *testing.T) {\n\treq := buildOpenAIChatCompletionRequest(\"gpt-4o\", llm.NewChatContext(), &llm.ChatOptions{\n\t\tTools: []llm.Tool{requestTestTool{}},\n\t\tParallelToolCalls: true,\n\t\tParallelToolCallsSet: true,\n\t\tToolChoice: \"required\",\n\t\tExtraParams: map[string]any{\n\t\t\t\"parallel_tool_calls\": false,\n\t\t\t\"tool_choice\": \"none\",\n\t\t\t\"metadata\": map[string]any{\n\t\t\t\t\"trace\": \"abc\",\n\t\t\t},\n\t\t},\n\t})\n\n\tif got, ok := req.ParallelToolCalls.(*bool); !ok || got == nil || !*got {\n\t\tt.Fatalf(\"ParallelToolCalls = %#v, want pointer to true\", req.ParallelToolCalls)\n\t}\n\tif req.ToolChoice != \"required\" {\n\t\tt.Fatalf(\"ToolChoice = %#v, want required\", req.ToolChoice)\n\t}\n\tif req.Metadata[\"trace\"] != \"abc\" {\n\t\tt.Fatalf(\"Metadata = %#v, want generic metadata preserved\", req.Metadata)\n\t}\n}\n\nfunc TestNewOpenAILLMWithBaseURLAndHTTPClientUsesConfiguredClient(t *testing.T) {\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\tmodel := NewOpenAILLMWithBaseURLAndHTTPClient(\"test-key\", \"gpt-4o\", \"https://openai.test/v1\", capture)\n\n\t_, err := startOpenAIChat(t, model,\n\t\tcontext.Background(),\n\t\tllm.NewChatContext(),\n\t\tllm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}),\n\t)\n\n\tvar statusErr *llm.APIStatusError\n\tif !errors.As(err, &statusErr) {\n\t\tt.Fatalf(\"Chat error = %T %v, want APIStatusError\", err, err)\n\t}\n\tif capture.authorization != \"Bearer test-key\" {\n\t\tt.Fatalf(\"Authorization = %q, want Bearer test-key\", capture.authorization)\n\t}\n}\n\nfunc TestNewOpenRouterLLMMatchesReferenceHeadersAndBody(t *testing.T) {\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\tmodel, err := NewOpenRouterLLMWithHTTPClient(\n\t\t\"test-key\",\n\t\t\"openai/gpt-4o-mini\",\n\t\tcapture,\n\t\tWithOpenRouterSiteURL(\"https://app.example\"),\n\t\tWithOpenRouterAppName(\"Cavos Agent\"),\n\t\tWithOpenRouterFallbackModels([]string{\"anthropic/claude-3.5-sonnet\"}),\n\t\tWithOpenRouterProvider(map[string]any{\"order\": []any{\"OpenAI\", \"Anthropic\"}}),\n\t\tWithOpenRouterPlugins([]map[string]any{{\"id\": \"web\", \"max_results\": 3}}),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"NewOpenRouterLLMWithHTTPClient error = %v\", err)\n\t}\n\n\t_, _ = startOpenAIChat(t, model, context.Background(), llm.NewChatContext(),\n\t\tllm.WithTools([]llm.Tool{requestTestTool{}}),\n\t\tllm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}),\n\t)\n\n\tif got := capture.header.Get(\"HTTP-Referer\"); got != \"https://app.example\" {\n\t\tt.Fatalf(\"HTTP-Referer = %q, want site URL\", got)\n\t}\n\tif got := capture.header.Get(\"X-Title\"); got != \"Cavos Agent\" {\n\t\tt.Fatalf(\"X-Title = %q, want app name\", got)\n\t}\n\tif !strings.HasPrefix(capture.requestURL, \"https://openrouter.ai/api/v1/chat/completions\") {\n\t\tt.Fatalf(\"request URL = %s, want OpenRouter chat completions endpoint\", capture.requestURL)\n\t}\n\tfor _, want := range []string{\n\t\t`\"tool_choice\":\"auto\"`,\n\t\t`\"models\":[\"openai/gpt-4o-mini\",\"anthropic/claude-3.5-sonnet\"]`,\n\t\t`\"provider\":{\"order\":[\"OpenAI\",\"Anthropic\"]}`,\n\t\t`\"plugins\":[{\"id\":\"web\",\"max_results\":3}]`,\n\t} {\n\t\tif !strings.Contains(capture.requestBody, want) {\n\t\t\tt.Fatalf(\"request body = %s, want %s\", capture.requestBody, want)\n\t\t}\n\t}\n}\n\nfunc TestNewOpenRouterLLMUsesEnvironmentKeyAndForwardedOptions(t *testing.T) {\n\tt.Setenv(openRouterAPIKeyEnv, \"env-key\")\n\tmodel, err := NewOpenRouterLLM(\n\t\t\"\",\n\t\t\"\",\n\t\tWithOpenRouterLLMOptions(\n\t\t\tWithOpenAILLMTemperature(0.2),\n\t\t\tWithOpenAILLMUser(\"caller-123\"),\n\t\t),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"NewOpenRouterLLM error = %v\", err)\n\t}\n\n\tif model.Model() != \"auto\" {\n\t\tt.Fatalf(\"Model = %q, want reference auto default\", model.Model())\n\t}\n\tif model.Provider() != \"openrouter.ai\" {\n\t\tt.Fatalf(\"Provider = %q, want OpenRouter host\", model.Provider())\n\t}\n\n\treq := buildOpenAIChatCompletionRequest(\"auto\", llm.NewChatContext(), &llm.ChatOptions{\n\t\tExtraParams: model.extraParams,\n\t\tToolChoice: model.toolChoice,\n\t\tTools: []llm.Tool{requestTestTool{}},\n\t})\n\tif req.ToolChoice != \"auto\" {\n\t\tt.Fatalf(\"ToolChoice = %#v, want reference auto default\", req.ToolChoice)\n\t}\n\tif req.Temperature != 0.2 {\n\t\tt.Fatalf(\"Temperature = %v, want forwarded option\", req.Temperature)\n\t}\n\tif req.User != \"caller-123\" {\n\t\tt.Fatalf(\"User = %q, want forwarded option\", req.User)\n\t}\n}\n\ntype captureDeadlineHTTPClient struct {\n\terr error\n\thasDeadline bool\n\tremaining time.Duration\n\tstatusCode int\n\tresponseBody string\n\theader http.Header\n\trequestURL string\n\trequestBody string\n\tauthorization string\n\tapiKey string\n\tuserAgent string\n\troomID string\n\tjobID string\n\tinferenceProvider string\n\tinferencePriority string\n}\n\nfunc (c *captureDeadlineHTTPClient) Do(req *http.Request) (*http.Response, error) {\n\tdeadline, ok := req.Context().Deadline()\n\tc.hasDeadline = ok\n\tif ok {\n\t\tc.remaining = time.Until(deadline)\n\t}\n\tc.header = req.Header.Clone()\n\tc.requestURL = req.URL.String()\n\tc.authorization = req.Header.Get(\"Authorization\")\n\tc.apiKey = req.Header.Get(openaisdk.AzureAPIKeyHeader)\n\tc.userAgent = req.Header.Get(\"User-Agent\")\n\tc.roomID = req.Header.Get(\"X-LiveKit-Room-ID\")\n\tc.jobID = req.Header.Get(\"X-LiveKit-Job-ID\")\n\tc.inferenceProvider = req.Header.Get(\"X-LiveKit-Inference-Provider\")\n\tc.inferencePriority = req.Header.Get(\"X-LiveKit-Inference-Priority\")\n\tif req.Body != nil {\n\t\tbody, _ := io.ReadAll(req.Body)\n\t\tc.requestBody = string(body)\n\t\treq.Body = io.NopCloser(strings.NewReader(c.requestBody))\n\t}\n\tif c.err != nil {\n\t\treturn nil, c.err\n\t}\n\tstatusCode := c.statusCode\n\tif statusCode == 0 {\n\t\tstatusCode = http.StatusOK\n\t}\n\theader := c.header\n\tif header == nil {\n\t\theader = make(http.Header)\n\t}\n\treturn &http.Response{\n\t\tStatusCode: statusCode,\n\t\tStatus: http.StatusText(statusCode),\n\t\tBody: io.NopCloser(strings.NewReader(c.responseBody)),\n\t\tHeader: header,\n\t\tRequest: req,\n\t}, nil\n}\n\nfunc TestOpenAIChatReturnsAPIStatusErrorOnHTTPError(t *testing.T) {\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusTooManyRequests,\n\t\tresponseBody: `{\"error\":{\"message\":\"rate limit\",\"type\":\"rate_limit\",\"code\":\"rate_limit\"}}`,\n\t}\n\tconfig := openaisdk.DefaultConfig(\"test-key\")\n\tconfig.HTTPClient = capture\n\tmodel := mustNewOpenAILLMWithConfig(t, config, \"gpt-4o\")\n\n\t_, err := startOpenAIChat(t, model,\n\t\tcontext.Background(),\n\t\tllm.NewChatContext(),\n\t\tllm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}),\n\t)\n\n\tvar statusErr *llm.APIStatusError\n\tif !errors.As(err, &statusErr) {\n\t\tt.Fatalf(\"Chat error = %T %v, want APIStatusError\", err, err)\n\t}\n\tif statusErr.Message != \"rate limit (429 Too Many Requests)\" {\n\t\tt.Fatalf(\"Message = %q, want formatted rate limit message\", statusErr.Message)\n\t}\n\tif statusErr.StatusCode != http.StatusTooManyRequests {\n\t\tt.Fatalf(\"StatusCode = %d, want 429\", statusErr.StatusCode)\n\t}\n\tif !statusErr.Retryable {\n\t\tt.Fatal(\"Retryable = false, want 429 retryable\")\n\t}\n}\n\nfunc TestOpenAIChatDefersReferenceRequestUntilNext(t *testing.T) {\n\tcapture := &sequenceHTTPClient{responses: []*http.Response{\n\t\topenAITestResponse(http.StatusBadRequest, `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\"}}`),\n\t}}\n\tconfig := openaisdk.DefaultConfig(\"test-key\")\n\tconfig.HTTPClient = capture\n\tmodel := mustNewOpenAILLMWithConfig(t, config, \"gpt-4o\")\n\n\tstream, err := model.Chat(\n\t\tcontext.Background(),\n\t\tllm.NewChatContext(),\n\t\tllm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"Chat error = %v, want lazy stream\", err)\n\t}\n\tif capture.calls != 0 {\n\t\tt.Fatalf(\"HTTP calls after Chat = %d, want 0 until Next\", capture.calls)\n\t}\n\tif err := stream.Close(); err != nil {\n\t\tt.Fatalf(\"Close before Next error = %v\", err)\n\t}\n\tif capture.calls != 0 {\n\t\tt.Fatalf(\"HTTP calls after Close before Next = %d, want 0\", capture.calls)\n\t}\n}\n\nfunc TestOpenAILLMProviderCloseClosesLazyStreamsBeforeRequest(t *testing.T) {\n\tcapture := &sequenceHTTPClient{responses: []*http.Response{\n\t\topenAITestResponse(http.StatusOK, `data: {\"id\":\"chatcmpl-openai\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"hello\"}}]}`+\"\\n\\n\"+\"data: [DONE]\\n\\n\"),\n\t}}\n\tconfig := openaisdk.DefaultConfig(\"test-key\")\n\tconfig.HTTPClient = capture\n\tmodel := mustNewOpenAILLMWithConfig(t, config, \"gpt-4o\")\n\n\tstream, err := model.Chat(\n\t\tcontext.Background(),\n\t\tllm.NewChatContext(),\n\t\tllm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"Chat error = %v, want lazy stream\", err)\n\t}\n\tif err := llm.Close(model); err != nil {\n\t\tt.Fatalf(\"Close error = %v\", err)\n\t}\n\tif chunk, err := stream.Next(); chunk != nil || !errors.Is(err, io.EOF) {\n\t\tt.Fatalf(\"Next after provider Close = (%#v, %v), want nil EOF\", chunk, err)\n\t}\n\tif capture.calls != 0 {\n\t\tt.Fatalf(\"HTTP calls after provider Close before Next = %d, want 0\", capture.calls)\n\t}\n}\n\nfunc TestOpenAIChatReturnsAPITimeoutErrorOnTransportDeadline(t *testing.T) {\n\tcapture := &captureDeadlineHTTPClient{err: context.DeadlineExceeded}\n\tconfig := openaisdk.DefaultConfig(\"test-key\")\n\tconfig.HTTPClient = capture\n\tmodel := mustNewOpenAILLMWithConfig(t, config, \"gpt-4o\")\n\n\t_, err := startOpenAIChat(t, model,\n\t\tcontext.Background(),\n\t\tllm.NewChatContext(),\n\t\tllm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}),\n\t)\n\n\tvar timeoutErr *llm.APITimeoutError\n\tif !errors.As(err, &timeoutErr) {\n\t\tt.Fatalf(\"Chat error = %T %v, want APITimeoutError\", err, err)\n\t}\n\tif timeoutErr.Message != \"Request timed out.\" {\n\t\tt.Fatalf(\"Message = %q, want default timeout message\", timeoutErr.Message)\n\t}\n\tif !timeoutErr.Retryable {\n\t\tt.Fatal(\"Retryable = false, want timeout errors retryable\")\n\t}\n}\n\nfunc TestOpenAIChatRetriesRetryableSetupAPIError(t *testing.T) {\n\tcapture := &sequenceHTTPClient{responses: []*http.Response{\n\t\topenAITestResponse(http.StatusTooManyRequests, `{\"error\":{\"message\":\"rate limit\",\"type\":\"rate_limit\",\"code\":\"rate_limit\"}}`),\n\t\topenAITestResponse(http.StatusOK, \"data: [DONE]\\n\\n\"),\n\t}}\n\tconfig := openaisdk.DefaultConfig(\"test-key\")\n\tconfig.HTTPClient = capture\n\tmodel := mustNewOpenAILLMWithConfig(t, config, \"gpt-4o\")\n\n\tstream, err := startOpenAIChat(t, model,\n\t\tcontext.Background(),\n\t\tllm.NewChatContext(),\n\t\tllm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 1}),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"Chat() error = %v, want retry success\", err)\n\t}\n\t_ = stream.Close()\n\tif capture.calls != 2 {\n\t\tt.Fatalf(\"HTTP calls = %d, want initial failure plus retry\", capture.calls)\n\t}\n}\n\nfunc TestOpenAIChatDoesNotRetryNonRetryableSetupAPIError(t *testing.T) {\n\tcapture := &sequenceHTTPClient{responses: []*http.Response{\n\t\topenAITestResponse(http.StatusBadRequest, `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`),\n\t\topenAITestResponse(http.StatusOK, \"data: [DONE]\\n\\n\"),\n\t}}\n\tconfig := openaisdk.DefaultConfig(\"test-key\")\n\tconfig.HTTPClient = capture\n\tmodel := mustNewOpenAILLMWithConfig(t, config, \"gpt-4o\")\n\n\t_, err := startOpenAIChat(t, model,\n\t\tcontext.Background(),\n\t\tllm.NewChatContext(),\n\t\tllm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 1}),\n\t)\n\n\tvar statusErr *llm.APIStatusError\n\tif !errors.As(err, &statusErr) {\n\t\tt.Fatalf(\"Chat error = %T %v, want APIStatusError\", err, err)\n\t}\n\tif capture.calls != 1 {\n\t\tt.Fatalf(\"HTTP calls = %d, want no retry for non-retryable status\", capture.calls)\n\t}\n}\n\nfunc TestOpenAIStreamReturnsAPIErrorOnErrorEvent(t *testing.T) {\n\tcapture := &sequenceHTTPClient{responses: []*http.Response{\n\t\topenAITestResponse(http.StatusOK, `data: {\"error\":{\"message\":\"stream failed\",\"type\":\"server_error\",\"code\":\"server_error\"}}`+\"\\n\\n\"),\n\t}}\n\tconfig := openaisdk.DefaultConfig(\"test-key\")\n\tconfig.HTTPClient = capture\n\tmodel := mustNewOpenAILLMWithConfig(t, config, \"gpt-4o\")\n\n\tstream, err := model.Chat(\n\t\tcontext.Background(),\n\t\tllm.NewChatContext(),\n\t\tllm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"Chat() error = %v\", err)\n\t}\n\tdefer stream.Close()\n\n\t_, err = stream.Next()\n\n\tvar apiErr *llm.APIError\n\tif !errors.As(err, &apiErr) {\n\t\tt.Fatalf(\"Next error = %T %v, want APIError\", err, err)\n\t}\n\tif apiErr.Message != \"stream failed\" {\n\t\tt.Fatalf(\"Message = %q, want OpenAI stream error message\", apiErr.Message)\n\t}\n\tbody, ok := apiErr.Body.(*openaisdk.APIError)\n\tif !ok {\n\t\tt.Fatalf(\"Body = %T %#v, want OpenAI APIError body\", apiErr.Body, apiErr.Body)\n\t}\n\tif body.Type != \"server_error\" || body.Code != \"server_error\" {\n\t\tt.Fatalf(\"Body = %#v, want server_error metadata\", body)\n\t}\n\tif !apiErr.Retryable {\n\t\tt.Fatal(\"Retryable = false, want stream API errors retryable\")\n\t}\n\tvar connectionErr *llm.APIConnectionError\n\tif errors.As(err, &connectionErr) {\n\t\tt.Fatalf(\"Next error = %T %v, want APIError not APIConnectionError\", err, err)\n\t}\n}\n\nfunc TestOpenAIStreamErrorAfterChunkIsNonRetryable(t *testing.T) {\n\tcapture := &sequenceHTTPClient{responses: []*http.Response{\n\t\topenAITestResponse(http.StatusOK,\n\t\t\t`data: {\"id\":\"chatcmpl-partial\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"hello\"}}]}`+\"\\n\\n\"+\n\t\t\t\t`data: {\"error\":{\"message\":\"stream failed after partial\",\"type\":\"server_error\",\"code\":\"server_error\"}}`+\"\\n\\n\"),\n\t}}\n\tconfig := openaisdk.DefaultConfig(\"test-key\")\n\tconfig.HTTPClient = capture\n\tmodel := mustNewOpenAILLMWithConfig(t, config, \"gpt-4o\")\n\n\tstream, err := model.Chat(\n\t\tcontext.Background(),\n\t\tllm.NewChatContext(),\n\t\tllm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"Chat() error = %v\", err)\n\t}\n\tdefer stream.Close()\n\n\tchunk, err := stream.Next()\n\tif err != nil {\n\t\tt.Fatalf(\"first Next error = %v, want content chunk\", err)\n\t}\n\tif chunk == nil || chunk.Delta == nil || chunk.Delta.Content != \"hello\" {\n\t\tt.Fatalf(\"first chunk = %#v, want hello content\", chunk)\n\t}\n\n\t_, err = stream.Next()\n\tvar apiErr *llm.APIError\n\tif !errors.As(err, &apiErr) {\n\t\tt.Fatalf(\"second Next error = %T %v, want APIError\", err, err)\n\t}\n\tif apiErr.Retryable {\n\t\tt.Fatal(\"Retryable = true after emitted chunk, want false like reference\")\n\t}\n}\n\nfunc TestOpenAIStreamTerminalErrorUnregistersReferenceStream(t *testing.T) {\n\tcapture := &sequenceHTTPClient{responses: []*http.Response{\n\t\topenAITestResponse(http.StatusOK,\n\t\t\t`data: {\"error\":{\"message\":\"stream failed\",\"type\":\"server_error\",\"code\":\"server_error\"}}`+\"\\n\\n\"),\n\t}}\n\tconfig := openaisdk.DefaultConfig(\"test-key\")\n\tconfig.HTTPClient = capture\n\tmodel := mustNewOpenAILLMWithConfig(t, config, \"gpt-4o\")\n\n\tstream, err := model.Chat(\n\t\tcontext.Background(),\n\t\tllm.NewChatContext(),\n\t\tllm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"Chat() error = %v\", err)\n\t}\n\n\t_, err = stream.Next()\n\tvar apiErr *llm.APIError\n\tif !errors.As(err, &apiErr) {\n\t\tt.Fatalf(\"Next error = %T %v, want APIError\", err, err)\n\t}\n\tmodel.mu.Lock()\n\tstreamCount := len(model.streams)\n\tmodel.mu.Unlock()\n\tif streamCount != 0 {\n\t\tt.Fatalf(\"registered streams = %d, want terminal-error stream unregistered\", streamCount)\n\t}\n\tif chunk, err := stream.Next(); chunk != nil || !errors.Is(err, io.EOF) {\n\t\tt.Fatalf(\"Next after terminal error = (%#v, %v), want EOF\", chunk, err)\n\t}\n}\n\nfunc TestOpenAIStreamEOFUnregistersReferenceStream(t *testing.T) {\n\tcapture := &sequenceHTTPClient{responses: []*http.Response{\n\t\topenAITestResponse(http.StatusOK,\n\t\t\t`data: {\"id\":\"chatcmpl-complete\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"hello\"}}]}`+\"\\n\\n\"+\n\t\t\t\t`data: [DONE]`+\"\\n\\n\"),\n\t}}\n\tconfig := openaisdk.DefaultConfig(\"test-key\")\n\tconfig.HTTPClient = capture\n\tmodel := mustNewOpenAILLMWithConfig(t, config, \"gpt-4o\")\n\n\tstream, err := model.Chat(\n\t\tcontext.Background(),\n\t\tllm.NewChatContext(),\n\t\tllm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"Chat() error = %v\", err)\n\t}\n\n\tchunk, err := stream.Next()\n\tif err != nil {\n\t\tt.Fatalf(\"first Next error = %v, want content chunk\", err)\n\t}\n\tif chunk == nil || chunk.Delta == nil || chunk.Delta.Content != \"hello\" {\n\t\tt.Fatalf(\"first chunk = %#v, want hello content\", chunk)\n\t}\n\tif chunk, err = stream.Next(); chunk != nil || !errors.Is(err, io.EOF) {\n\t\tt.Fatalf(\"second Next = (%#v, %v), want EOF\", chunk, err)\n\t}\n\tmodel.mu.Lock()\n\tstreamCount := len(model.streams)\n\tmodel.mu.Unlock()\n\tif streamCount != 0 {\n\t\tt.Fatalf(\"registered streams = %d, want completed stream unregistered\", streamCount)\n\t}\n}\n\nfunc TestOpenAIStreamSkipsAzureNullDelta(t *testing.T) {\n\tcapture := &sequenceHTTPClient{responses: []*http.Response{\n\t\topenAITestResponse(http.StatusOK,\n\t\t\t`data: {\"id\":\"chatcmpl-1\",\"choices\":[{\"index\":0,\"delta\":null}]}`+\"\\n\\n\"+\n\t\t\t\t`data: {\"id\":\"chatcmpl-1\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"hello\"}}]}`+\"\\n\\n\"+\n\t\t\t\t\"data: [DONE]\\n\\n\"),\n\t}}\n\tconfig := openaisdk.DefaultConfig(\"test-key\")\n\tconfig.HTTPClient = capture\n\tmodel := mustNewOpenAILLMWithConfig(t, config, \"gpt-4o\")\n\n\tstream, err := model.Chat(\n\t\tcontext.Background(),\n\t\tllm.NewChatContext(),\n\t\tllm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"Chat() error = %v\", err)\n\t}\n\tdefer stream.Close()\n\n\tchunk, err := stream.Next()\n\tif err != nil {\n\t\tt.Fatalf(\"Next error = %v, want content chunk after skipped Azure null delta\", err)\n\t}\n\tif chunk == nil || chunk.Delta == nil || chunk.Delta.Content != \"hello\" {\n\t\tt.Fatalf(\"chunk = %#v, want content chunk after skipped Azure null delta\", chunk)\n\t}\n}\n\nfunc TestOpenAIStreamSkipsAzureNullDeltaWithFinishReason(t *testing.T) {\n\tcapture := &sequenceHTTPClient{responses: []*http.Response{\n\t\topenAITestResponse(http.StatusOK,\n\t\t\t`data: {\"id\":\"chatcmpl-1\",\"choices\":[{\"index\":0,\"delta\":null,\"finish_reason\":\"content_filter\"}]}`+\"\\n\\n\"+\n\t\t\t\t`data: {\"id\":\"chatcmpl-1\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"hello\"}}]}`+\"\\n\\n\"+\n\t\t\t\t\"data: [DONE]\\n\\n\"),\n\t}}\n\tconfig := openaisdk.DefaultConfig(\"test-key\")\n\tconfig.HTTPClient = capture\n\tmodel := mustNewOpenAILLMWithConfig(t, config, \"gpt-4o\")\n\n\tstream, err := model.Chat(\n\t\tcontext.Background(),\n\t\tllm.NewChatContext(),\n\t\tllm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"Chat() error = %v\", err)\n\t}\n\tdefer stream.Close()\n\n\tchunk, err := stream.Next()\n\tif err != nil {\n\t\tt.Fatalf(\"Next error = %v, want content chunk after skipped Azure null delta\", err)\n\t}\n\tif chunk == nil || chunk.Delta == nil || chunk.Delta.Content != \"hello\" {\n\t\tt.Fatalf(\"chunk = %#v, want content chunk after skipped Azure null delta with finish reason\", chunk)\n\t}\n}\n\nfunc TestOpenAIStreamSkipsAzureNullDeltaFinishBeforeToolCallComplete(t *testing.T) {\n\tcapture := &sequenceHTTPClient{responses: []*http.Response{\n\t\topenAITestResponse(http.StatusOK,\n\t\t\t`data: {\"id\":\"chatcmpl-null-tool\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"id\":\"call_1\",\"type\":\"function\",\"function\":{\"name\":\"lookup\",\"arguments\":\"{}\"}}]}}]}`+\"\\n\\n\"+\n\t\t\t\t`data: {\"id\":\"chatcmpl-null-tool\",\"choices\":[{\"index\":0,\"delta\":null,\"finish_reason\":\"tool_calls\"}]}`+\"\\n\\n\"+\n\t\t\t\t`data: {\"id\":\"chatcmpl-null-tool\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"after null\"}}]}`+\"\\n\\n\"+\n\t\t\t\t`data: {\"id\":\"chatcmpl-null-tool\",\"choices\":[{\"index\":0,\"delta\":{},\"finish_reason\":\"tool_calls\"}]}`+\"\\n\\n\"+\n\t\t\t\t\"data: [DONE]\\n\\n\"),\n\t}}\n\tconfig := openaisdk.DefaultConfig(\"test-key\")\n\tconfig.HTTPClient = capture\n\tmodel := mustNewOpenAILLMWithConfig(t, config, \"gpt-4o\")\n\n\tstream, err := model.Chat(\n\t\tcontext.Background(),\n\t\tllm.NewChatContext(),\n\t\tllm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"Chat() error = %v\", err)\n\t}\n\tdefer stream.Close()\n\n\tcontentChunk, err := stream.Next()\n\tif err != nil {\n\t\tt.Fatalf(\"first Next error = %v, want content after skipped null delta finish\", err)\n\t}\n\tif contentChunk == nil || contentChunk.Delta == nil || contentChunk.Delta.Content != \"after null\" {\n\t\tt.Fatalf(\"first chunk = %#v, want content after skipped null delta finish\", contentChunk)\n\t}\n\tif len(contentChunk.Delta.ToolCalls) != 0 {\n\t\tt.Fatalf(\"first chunk tool calls = %#v, want pending tool call not completed by null delta\", contentChunk.Delta.ToolCalls)\n\t}\n\n\ttoolChunk, err := stream.Next()\n\tif err != nil {\n\t\tt.Fatalf(\"second Next error = %v, want tool call after real finish chunk\", err)\n\t}\n\tif toolChunk == nil || toolChunk.Delta == nil || len(toolChunk.Delta.ToolCalls) != 1 {\n\t\tt.Fatalf(\"second chunk = %#v, want completed tool call after real finish\", toolChunk)\n\t}\n\tif call := toolChunk.Delta.ToolCalls[0]; call.CallID != \"call_1\" || call.Name != \"lookup\" || call.Arguments != \"{}\" {\n\t\tt.Fatalf(\"tool call = %+v, want accumulated call after real finish\", call)\n\t}\n}\n\nfunc TestOpenAIStreamContentDeltaDefaultsAssistantRole(t *testing.T) {\n\tcapture := &sequenceHTTPClient{responses: []*http.Response{\n\t\topenAITestResponse(http.StatusOK,\n\t\t\t`data: {\"id\":\"chatcmpl-role\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"hello\"}}]}`+\"\\n\\n\"+\n\t\t\t\t\"data: [DONE]\\n\\n\"),\n\t}}\n\tconfig := openaisdk.DefaultConfig(\"test-key\")\n\tconfig.HTTPClient = capture\n\tmodel := mustNewOpenAILLMWithConfig(t, config, \"gpt-4o\")\n\n\tstream, err := model.Chat(\n\t\tcontext.Background(),\n\t\tllm.NewChatContext(),\n\t\tllm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"Chat() error = %v\", err)\n\t}\n\tdefer stream.Close()\n\n\tchunk, err := stream.Next()\n\tif err != nil {\n\t\tt.Fatalf(\"Next error = %v, want content chunk\", err)\n\t}\n\tif chunk == nil || chunk.Delta == nil || chunk.Delta.Content != \"hello\" {\n\t\tt.Fatalf(\"chunk = %#v, want content chunk\", chunk)\n\t}\n\tif chunk.Delta.Role != llm.ChatRoleAssistant {\n\t\tt.Fatalf(\"content chunk role = %q, want assistant\", chunk.Delta.Role)\n\t}\n}\n\nfunc TestOpenAIStreamEmitsAllChoiceDeltasInOrder(t *testing.T) {\n\tcapture := &sequenceHTTPClient{responses: []*http.Response{\n\t\topenAITestResponse(http.StatusOK,\n\t\t\t`data: {\"id\":\"chatcmpl-choices\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"first\"}},{\"index\":1,\"delta\":{\"content\":\"second\"}}]}`+\"\\n\\n\"+\n\t\t\t\t\"data: [DONE]\\n\\n\"),\n\t}}\n\tconfig := openaisdk.DefaultConfig(\"test-key\")\n\tconfig.HTTPClient = capture\n\tmodel := mustNewOpenAILLMWithConfig(t, config, \"gpt-4o\")\n\n\tstream, err := model.Chat(\n\t\tcontext.Background(),\n\t\tllm.NewChatContext(),\n\t\tllm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"Chat() error = %v\", err)\n\t}\n\tdefer stream.Close()\n\n\tfirst, err := stream.Next()\n\tif err != nil {\n\t\tt.Fatalf(\"first Next error = %v\", err)\n\t}\n\tif first == nil || first.Delta == nil || first.Delta.Content != \"first\" {\n\t\tt.Fatalf(\"first chunk = %#v, want first choice content\", first)\n\t}\n\n\tsecond, err := stream.Next()\n\tif err != nil {\n\t\tt.Fatalf(\"second Next error = %v\", err)\n\t}\n\tif second == nil || second.Delta == nil || second.Delta.Content != \"second\" {\n\t\tt.Fatalf(\"second chunk = %#v, want second choice content\", second)\n\t}\n\n\tif _, err := stream.Next(); !errors.Is(err, io.EOF) {\n\t\tt.Fatalf(\"Next after choices = %v, want EOF\", err)\n\t}\n}\n\nfunc TestOpenAIStreamPreservesAzureUsageOnlyChunk(t *testing.T) {\n\tcapture := &sequenceHTTPClient{responses: []*http.Response{\n\t\topenAITestResponse(http.StatusOK,\n\t\t\t`data: {\"id\":\"chatcmpl-usage\",\"choices\":[],\"usage\":{\"completion_tokens\":3,\"prompt_tokens\":2,\"total_tokens\":5,\"prompt_tokens_details\":{\"cached_tokens\":1}}}`+\"\\n\\n\"+\n\t\t\t\t\"data: [DONE]\\n\\n\"),\n\t}}\n\tconfig := openaisdk.DefaultConfig(\"test-key\")\n\tconfig.HTTPClient = capture\n\tmodel := mustNewOpenAILLMWithConfig(t, config, \"gpt-4o\")\n\n\tstream, err := model.Chat(\n\t\tcontext.Background(),\n\t\tllm.NewChatContext(),\n\t\tllm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"Chat() error = %v\", err)\n\t}\n\tdefer stream.Close()\n\n\tchunk, err := stream.Next()\n\tif err != nil {\n\t\tt.Fatalf(\"Next error = %v, want usage chunk\", err)\n\t}\n\tif chunk == nil || chunk.ID != \"chatcmpl-usage\" {\n\t\tt.Fatalf(\"chunk = %#v, want usage chunk id\", chunk)\n\t}\n\tif chunk.Usage == nil {\n\t\tt.Fatalf(\"chunk.Usage = nil, want usage from Azure usage-only stream chunk\")\n\t}\n\tif chunk.Usage.CompletionTokens != 3 || chunk.Usage.PromptTokens != 2 ||\n\t\tchunk.Usage.TotalTokens != 5 || chunk.Usage.PromptCachedTokens != 1 {\n\t\tt.Fatalf(\"chunk.Usage = %+v, want mapped usage-only stream tokens\", chunk.Usage)\n\t}\n}\n\nfunc TestOpenAIStreamPreservesUsageServiceTier(t *testing.T) {\n\tcapture := &sequenceHTTPClient{responses: []*http.Response{\n\t\topenAITestResponse(http.StatusOK,\n\t\t\t`data: {\"id\":\"chatcmpl-tier\",\"choices\":[],\"service_tier\":\"priority\",\"usage\":{\"completion_tokens\":3,\"prompt_tokens\":2,\"total_tokens\":5}}`+\"\\n\\n\"+\n\t\t\t\t\"data: [DONE]\\n\\n\"),\n\t}}\n\tconfig := openaisdk.DefaultConfig(\"test-key\")\n\tconfig.HTTPClient = capture\n\tmodel := mustNewOpenAILLMWithConfig(t, config, \"gpt-4o\")\n\n\tstream, err := model.Chat(\n\t\tcontext.Background(),\n\t\tllm.NewChatContext(),\n\t\tllm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"Chat() error = %v\", err)\n\t}\n\tdefer stream.Close()\n\n\tchunk, err := stream.Next()\n\tif err != nil {\n\t\tt.Fatalf(\"Next error = %v, want usage chunk\", err)\n\t}\n\tif chunk == nil || chunk.Usage == nil {\n\t\tt.Fatalf(\"chunk = %#v, want usage chunk\", chunk)\n\t}\n\tif chunk.Usage.ServiceTier != \"priority\" {\n\t\tt.Fatalf(\"ServiceTier = %q, want priority\", chunk.Usage.ServiceTier)\n\t}\n}\n\nfunc TestOpenAIStreamSkipsEmptyChoicesWithoutUsage(t *testing.T) {\n\tcapture := &sequenceHTTPClient{responses: []*http.Response{\n\t\topenAITestResponse(http.StatusOK,\n\t\t\t`data: {\"id\":\"chatcmpl-empty\",\"choices\":[]}`+\"\\n\\n\"+\n\t\t\t\t`data: {\"id\":\"chatcmpl-empty\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"next\"}}]}`+\"\\n\\n\"+\n\t\t\t\t\"data: [DONE]\\n\\n\"),\n\t}}\n\tconfig := openaisdk.DefaultConfig(\"test-key\")\n\tconfig.HTTPClient = capture\n\tmodel := mustNewOpenAILLMWithConfig(t, config, \"gpt-4o\")\n\n\tstream, err := model.Chat(\n\t\tcontext.Background(),\n\t\tllm.NewChatContext(),\n\t\tllm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"Chat() error = %v\", err)\n\t}\n\tdefer stream.Close()\n\n\tchunk, err := stream.Next()\n\tif err != nil {\n\t\tt.Fatalf(\"Next error = %v, want next content chunk after empty choices\", err)\n\t}\n\tif chunk == nil || chunk.Delta == nil || chunk.Delta.Content != \"next\" {\n\t\tt.Fatalf(\"chunk = %#v, want next content chunk after skipped empty choices\", chunk)\n\t}\n}\n\nfunc TestOpenAIStreamAccumulatesAzureToolCallDeltas(t *testing.T) {\n\tcapture := &sequenceHTTPClient{responses: []*http.Response{\n\t\topenAITestResponse(http.StatusOK,\n\t\t\t`data: {\"id\":\"chatcmpl-tool\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"id\":\"call_1\",\"type\":\"function\",\"function\":{\"name\":\"lookup\",\"arguments\":\"{\\\"query\\\":\"}}]}}]}`+\"\\n\\n\"+\n\t\t\t\t`data: {\"id\":\"chatcmpl-tool\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"\\\"weather\\\"}\"}}]}}]}`+\"\\n\\n\"+\n\t\t\t\t`data: {\"id\":\"chatcmpl-tool\",\"choices\":[{\"index\":0,\"delta\":{},\"finish_reason\":\"tool_calls\"}]}`+\"\\n\\n\"+\n\t\t\t\t\"data: [DONE]\\n\\n\"),\n\t}}\n\tconfig := openaisdk.DefaultConfig(\"test-key\")\n\tconfig.HTTPClient = capture\n\tmodel := mustNewOpenAILLMWithConfig(t, config, \"gpt-4o\")\n\n\tstream, err := model.Chat(\n\t\tcontext.Background(),\n\t\tllm.NewChatContext(),\n\t\tllm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"Chat() error = %v\", err)\n\t}\n\tdefer stream.Close()\n\n\tchunk, err := stream.Next()\n\tif err != nil {\n\t\tt.Fatalf(\"Next error = %v, want accumulated tool call chunk\", err)\n\t}\n\tif chunk == nil || chunk.Delta == nil || len(chunk.Delta.ToolCalls) != 1 {\n\t\tt.Fatalf(\"chunk = %#v, want one accumulated tool call\", chunk)\n\t}\n\tif chunk.Delta.Role != llm.ChatRoleAssistant {\n\t\tt.Fatalf(\"tool call chunk role = %q, want assistant\", chunk.Delta.Role)\n\t}\n\tcall := chunk.Delta.ToolCalls[0]\n\tif call.CallID != \"call_1\" || call.Name != \"lookup\" || call.Arguments != `{\"query\":\"weather\"}` {\n\t\tt.Fatalf(\"tool call = %+v, want accumulated Azure tool-call arguments\", call)\n\t}\n}\n\nfunc TestOpenAIStreamDefaultsToolCallTypeLikeReference(t *testing.T) {\n\tcapture := &sequenceHTTPClient{responses: []*http.Response{\n\t\topenAITestResponse(http.StatusOK,\n\t\t\t`data: {\"id\":\"chatcmpl-tool-default-type\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"id\":\"call_1\",\"function\":{\"name\":\"lookup\",\"arguments\":\"{}\"}}]}}]}`+\"\\n\\n\"+\n\t\t\t\t`data: {\"id\":\"chatcmpl-tool-default-type\",\"choices\":[{\"index\":0,\"delta\":{},\"finish_reason\":\"tool_calls\"}]}`+\"\\n\\n\"+\n\t\t\t\t\"data: [DONE]\\n\\n\"),\n\t}}\n\tconfig := openaisdk.DefaultConfig(\"test-key\")\n\tconfig.HTTPClient = capture\n\tmodel := mustNewOpenAILLMWithConfig(t, config, \"gpt-4o\")\n\n\tstream, err := model.Chat(\n\t\tcontext.Background(),\n\t\tllm.NewChatContext(),\n\t\tllm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"Chat() error = %v\", err)\n\t}\n\tdefer stream.Close()\n\n\tchunk, err := stream.Next()\n\tif err != nil {\n\t\tt.Fatalf(\"Next error = %v, want accumulated tool call chunk\", err)\n\t}\n\tif chunk == nil || chunk.Delta == nil || len(chunk.Delta.ToolCalls) != 1 {\n\t\tt.Fatalf(\"chunk = %#v, want one tool call\", chunk)\n\t}\n\tif got := chunk.Delta.ToolCalls[0].Type; got != \"function\" {\n\t\tt.Fatalf(\"tool call type = %q, want reference default function\", got)\n\t}\n}\n\nfunc TestOpenAIStreamAppliesToolCallTailBeforeFinish(t *testing.T) {\n\tcapture := &sequenceHTTPClient{responses: []*http.Response{\n\t\topenAITestResponse(http.StatusOK,\n\t\t\t`data: {\"id\":\"chatcmpl-tool-tail\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"id\":\"call_1\",\"type\":\"function\",\"function\":{\"name\":\"lookup\",\"arguments\":\"{\\\"query\\\":\"}}]}}]}`+\"\\n\\n\"+\n\t\t\t\t`data: {\"id\":\"chatcmpl-tool-tail\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"\\\"weather\\\"}\"}}]},\"finish_reason\":\"tool_calls\"}]}`+\"\\n\\n\"+\n\t\t\t\t\"data: [DONE]\\n\\n\"),\n\t}}\n\tconfig := openaisdk.DefaultConfig(\"test-key\")\n\tconfig.HTTPClient = capture\n\tmodel := mustNewOpenAILLMWithConfig(t, config, \"gpt-4o\")\n\n\tstream, err := model.Chat(\n\t\tcontext.Background(),\n\t\tllm.NewChatContext(),\n\t\tllm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"Chat() error = %v\", err)\n\t}\n\tdefer stream.Close()\n\n\tchunk, err := stream.Next()\n\tif err != nil {\n\t\tt.Fatalf(\"Next error = %v, want accumulated tool call chunk\", err)\n\t}\n\tif chunk == nil || chunk.Delta == nil || len(chunk.Delta.ToolCalls) != 1 {\n\t\tt.Fatalf(\"chunk = %#v, want one accumulated tool call\", chunk)\n\t}\n\tcall := chunk.Delta.ToolCalls[0]\n\tif call.CallID != \"call_1\" || call.Name != \"lookup\" || call.Arguments != `{\"query\":\"weather\"}` {\n\t\tt.Fatalf(\"tool call = %+v, want tail arguments applied before finish\", call)\n\t}\n}\n\nfunc TestOpenAIStreamAccumulatesInterleavedToolCallDeltas(t *testing.T) {\n\tcapture := &sequenceHTTPClient{responses: []*http.Response{\n\t\topenAITestResponse(http.StatusOK,\n\t\t\t`data: {\"id\":\"chatcmpl-interleaved-tools\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"id\":\"call_1\",\"type\":\"function\",\"function\":{\"name\":\"lookup\",\"arguments\":\"{\\\"city\\\":\"}},{\"index\":1,\"id\":\"call_2\",\"type\":\"function\",\"function\":{\"name\":\"book\",\"arguments\":\"{\\\"date\\\":\"}}]}}]}`+\"\\n\\n\"+\n\t\t\t\t`data: {\"id\":\"chatcmpl-interleaved-tools\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"\\\"Paris\\\"}\"}},{\"index\":1,\"function\":{\"arguments\":\"\\\"today\\\"}\"}}]},\"finish_reason\":\"tool_calls\"}]}`+\"\\n\\n\"+\n\t\t\t\t\"data: [DONE]\\n\\n\"),\n\t}}\n\tconfig := openaisdk.DefaultConfig(\"test-key\")\n\tconfig.HTTPClient = capture\n\tmodel := mustNewOpenAILLMWithConfig(t, config, \"gpt-4o\")\n\n\tstream, err := model.Chat(\n\t\tcontext.Background(),\n\t\tllm.NewChatContext(),\n\t\tllm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"Chat() error = %v\", err)\n\t}\n\tdefer stream.Close()\n\n\tfirst, err := stream.Next()\n\tif err != nil {\n\t\tt.Fatalf(\"first Next error = %v\", err)\n\t}\n\tsecond, err := stream.Next()\n\tif err != nil {\n\t\tt.Fatalf(\"second Next error = %v\", err)\n\t}\n\tgot := []*llm.ChatChunk{first, second}\n\twant := []struct {\n\t\tid string\n\t\tname string\n\t\targs string\n\t}{\n\t\t{id: \"call_1\", name: \"lookup\", args: `{\"city\":\"Paris\"}`},\n\t\t{id: \"call_2\", name: \"book\", args: `{\"date\":\"today\"}`},\n\t}\n\tfor i, chunk := range got {\n\t\tif chunk == nil || chunk.Delta == nil || len(chunk.Delta.ToolCalls) != 1 {\n\t\t\tt.Fatalf(\"chunk %d = %#v, want one tool call\", i, chunk)\n\t\t}\n\t\tcall := chunk.Delta.ToolCalls[0]\n\t\tif call.CallID != want[i].id || call.Name != want[i].name || call.Arguments != want[i].args {\n\t\t\tt.Fatalf(\"tool call %d = %+v, want %+v\", i, call, want[i])\n\t\t}\n\t}\n}\n\nfunc TestOpenAIStreamEmitsAllToolCallsFromFinishedDelta(t *testing.T) {\n\tcapture := &sequenceHTTPClient{responses: []*http.Response{\n\t\topenAITestResponse(http.StatusOK,\n\t\t\t`data: {\"id\":\"chatcmpl-two-tools\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"id\":\"call_1\",\"type\":\"function\",\"function\":{\"name\":\"lookup\",\"arguments\":\"{}\"}},{\"index\":1,\"id\":\"call_2\",\"type\":\"function\",\"function\":{\"name\":\"book\",\"arguments\":\"{\\\"date\\\":\\\"today\\\"}\"}}]},\"finish_reason\":\"tool_calls\"}]}`+\"\\n\\n\"+\n\t\t\t\t\"data: [DONE]\\n\\n\"),\n\t}}\n\tconfig := openaisdk.DefaultConfig(\"test-key\")\n\tconfig.HTTPClient = capture\n\tmodel := mustNewOpenAILLMWithConfig(t, config, \"gpt-4o\")\n\n\tstream, err := model.Chat(\n\t\tcontext.Background(),\n\t\tllm.NewChatContext(),\n\t\tllm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"Chat() error = %v\", err)\n\t}\n\tdefer stream.Close()\n\n\tfirst, err := stream.Next()\n\tif err != nil {\n\t\tt.Fatalf(\"first Next error = %v\", err)\n\t}\n\tsecond, err := stream.Next()\n\tif err != nil {\n\t\tt.Fatalf(\"second Next error = %v\", err)\n\t}\n\tgot := []*llm.ChatChunk{first, second}\n\twant := []struct {\n\t\tid string\n\t\tname string\n\t\targs string\n\t}{\n\t\t{id: \"call_1\", name: \"lookup\", args: \"{}\"},\n\t\t{id: \"call_2\", name: \"book\", args: `{\"date\":\"today\"}`},\n\t}\n\tfor i, chunk := range got {\n\t\tif chunk == nil || chunk.Delta == nil || len(chunk.Delta.ToolCalls) != 1 {\n\t\t\tt.Fatalf(\"chunk %d = %#v, want one tool call\", i, chunk)\n\t\t}\n\t\tcall := chunk.Delta.ToolCalls[0]\n\t\tif call.CallID != want[i].id || call.Name != want[i].name || call.Arguments != want[i].args {\n\t\t\tt.Fatalf(\"tool call %d = %+v, want %+v\", i, call, want[i])\n\t\t}\n\t}\n\tif _, err := stream.Next(); !errors.Is(err, io.EOF) {\n\t\tt.Fatalf(\"Next after tool calls = %v, want EOF\", err)\n\t}\n}\n\nfunc TestOpenAIStreamSplitsUsageAfterToolCallFinish(t *testing.T) {\n\tcapture := &sequenceHTTPClient{responses: []*http.Response{\n\t\topenAITestResponse(http.StatusOK,\n\t\t\t`data: {\"id\":\"chatcmpl-tool-usage\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"id\":\"call_1\",\"type\":\"function\",\"function\":{\"name\":\"lookup\",\"arguments\":\"{\\\"query\\\":\\\"weather\\\"}\"}}]}}]}`+\"\\n\\n\"+\n\t\t\t\t`data: {\"id\":\"chatcmpl-tool-usage\",\"choices\":[{\"index\":0,\"delta\":{},\"finish_reason\":\"tool_calls\"}],\"usage\":{\"completion_tokens\":4,\"prompt_tokens\":6,\"total_tokens\":10,\"prompt_tokens_details\":{\"cached_tokens\":2}}}`+\"\\n\\n\"+\n\t\t\t\t\"data: [DONE]\\n\\n\"),\n\t}}\n\tconfig := openaisdk.DefaultConfig(\"test-key\")\n\tconfig.HTTPClient = capture\n\tmodel := mustNewOpenAILLMWithConfig(t, config, \"gpt-4o\")\n\n\tstream, err := model.Chat(\n\t\tcontext.Background(),\n\t\tllm.NewChatContext(),\n\t\tllm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"Chat() error = %v\", err)\n\t}\n\tdefer stream.Close()\n\n\ttoolChunk, err := stream.Next()\n\tif err != nil {\n\t\tt.Fatalf(\"tool Next error = %v\", err)\n\t}\n\tif toolChunk == nil || toolChunk.Delta == nil || len(toolChunk.Delta.ToolCalls) != 1 {\n\t\tt.Fatalf(\"tool chunk = %#v, want accumulated tool call\", toolChunk)\n\t}\n\tif toolChunk.Usage != nil {\n\t\tt.Fatalf(\"tool chunk usage = %#v, want reference usage-only chunk after tool call\", toolChunk.Usage)\n\t}\n\n\tusageChunk, err := stream.Next()\n\tif err != nil {\n\t\tt.Fatalf(\"usage Next error = %v\", err)\n\t}\n\tif usageChunk == nil || usageChunk.Delta != nil || usageChunk.Usage == nil {\n\t\tt.Fatalf(\"usage chunk = %#v, want usage-only chunk after tool call\", usageChunk)\n\t}\n\tif usageChunk.Usage.CompletionTokens != 4 || usageChunk.Usage.PromptTokens != 6 ||\n\t\tusageChunk.Usage.TotalTokens != 10 || usageChunk.Usage.PromptCachedTokens != 2 {\n\t\tt.Fatalf(\"usage chunk = %+v, want mapped usage tokens\", usageChunk.Usage)\n\t}\n\n\tif _, err := stream.Next(); !errors.Is(err, io.EOF) {\n\t\tt.Fatalf(\"Next after usage = %v, want EOF\", err)\n\t}\n}\n\nfunc TestOpenAIStreamPreservesReferenceExtraContent(t *testing.T) {\n\tcapture := &sequenceHTTPClient{responses: []*http.Response{\n\t\topenAITestResponse(http.StatusOK,\n\t\t\t`data: {\"id\":\"chatcmpl-extra\",\"choices\":[{\"index\":0,\"delta\":{\"extra_content\":{\"google\":{\"thought_signature\":\"sig-text\"}}}}]}`+\"\\n\\n\"+\n\t\t\t\t`data: {\"id\":\"chatcmpl-extra\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"id\":\"call_1\",\"type\":\"function\",\"function\":{\"name\":\"lookup\",\"arguments\":\"{}\"},\"extra_content\":{\"google\":{\"thought_signature\":\"sig-tool\"}}}]}}]}`+\"\\n\\n\"+\n\t\t\t\t`data: {\"id\":\"chatcmpl-extra\",\"choices\":[{\"index\":0,\"delta\":{},\"finish_reason\":\"tool_calls\"}]}`+\"\\n\\n\"+\n\t\t\t\t\"data: [DONE]\\n\\n\"),\n\t}}\n\tconfig := openaisdk.DefaultConfig(\"test-key\")\n\tconfig.HTTPClient = capture\n\tmodel := mustNewOpenAILLMWithConfig(t, config, \"gpt-4o\")\n\n\tstream, err := model.Chat(\n\t\tcontext.Background(),\n\t\tllm.NewChatContext(),\n\t\tllm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"Chat() error = %v\", err)\n\t}\n\tdefer stream.Close()\n\n\textraChunk, err := stream.Next()\n\tif err != nil {\n\t\tt.Fatalf(\"extra Next error = %v, want metadata chunk\", err)\n\t}\n\tif extraChunk == nil || extraChunk.Delta == nil {\n\t\tt.Fatalf(\"extra chunk = %#v, want delta\", extraChunk)\n\t}\n\tgoogleExtra, ok := extraChunk.Delta.Extra[\"google\"].(map[string]any)\n\tif !ok || googleExtra[\"thought_signature\"] != \"sig-text\" {\n\t\tt.Fatalf(\"extra chunk extra = %#v, want google thought signature\", extraChunk.Delta.Extra)\n\t}\n\n\ttoolChunk, err := stream.Next()\n\tif err != nil {\n\t\tt.Fatalf(\"tool Next error = %v, want tool call chunk\", err)\n\t}\n\tif toolChunk == nil || toolChunk.Delta == nil || len(toolChunk.Delta.ToolCalls) != 1 {\n\t\tt.Fatalf(\"tool chunk = %#v, want one tool call\", toolChunk)\n\t}\n\ttoolExtra, ok := toolChunk.Delta.ToolCalls[0].Extra[\"google\"].(map[string]any)\n\tif !ok || toolExtra[\"thought_signature\"] != \"sig-tool\" {\n\t\tt.Fatalf(\"tool extra = %#v, want google thought signature\", toolChunk.Delta.ToolCalls[0].Extra)\n\t}\n}\n\nfunc TestOpenAIStreamSuppressesReferenceThinkingText(t *testing.T) {\n\tcapture := &sequenceHTTPClient{responses: []*http.Response{\n\t\topenAITestResponse(http.StatusOK,\n\t\t\t`data: {\"id\":\"chatcmpl-think\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"\"}}]}`+\"\\n\\n\"+\n\t\t\t\t`data: {\"id\":\"chatcmpl-think\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"hidden reasoning\"}}]}`+\"\\n\\n\"+\n\t\t\t\t`data: {\"id\":\"chatcmpl-think\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"visible\"}}]}`+\"\\n\\n\"+\n\t\t\t\t\"data: [DONE]\\n\\n\"),\n\t}}\n\tconfig := openaisdk.DefaultConfig(\"test-key\")\n\tconfig.HTTPClient = capture\n\tmodel := mustNewOpenAILLMWithConfig(t, config, \"gpt-4o\")\n\n\tstream, err := model.Chat(\n\t\tcontext.Background(),\n\t\tllm.NewChatContext(),\n\t\tllm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"Chat() error = %v\", err)\n\t}\n\tdefer stream.Close()\n\n\tchunk, err := stream.Next()\n\tif err != nil {\n\t\tt.Fatalf(\"Next error = %v\", err)\n\t}\n\tif chunk == nil || chunk.Delta == nil || chunk.Delta.Content != \"visible\" {\n\t\tt.Fatalf(\"chunk = %#v, want visible content after thinking block\", chunk)\n\t}\n\n\tif _, err := stream.Next(); !errors.Is(err, io.EOF) {\n\t\tt.Fatalf(\"Next after visible content = %v, want EOF\", err)\n\t}\n}\n\nfunc TestOpenAIStreamTreatsClientClosedStatusAsGracefulEOF(t *testing.T) {\n\terr := openAIStreamRecvError(llm.NewAPIStatusError(\"client closed\", 499, \"req_499\", nil), true)\n\tif !errors.Is(err, io.EOF) {\n\t\tt.Fatalf(\"openAIStreamRecvError(499) = %v, want EOF\", err)\n\t}\n\n\tstatusErr := llm.NewAPIStatusError(\"rate limited\", http.StatusTooManyRequests, \"req_429\", nil)\n\terr = openAIStreamRecvError(statusErr, true)\n\tvar gotStatusErr *llm.APIStatusError\n\tif !errors.As(err, &gotStatusErr) || gotStatusErr.StatusCode != http.StatusTooManyRequests {\n\t\tt.Fatalf(\"openAIStreamRecvError(429) = %T %v, want APIStatusError 429\", err, err)\n\t}\n}\n\nfunc TestOpenAIStreamNextAfterCloseReturnsEOF(t *testing.T) {\n\tbody := newBlockingReadErrorAfterClose()\n\tconfig := openaisdk.DefaultConfig(\"test-key\")\n\tconfig.HTTPClient = &sequenceHTTPClient{responses: []*http.Response{{\n\t\tStatusCode: http.StatusOK,\n\t\tStatus: http.StatusText(http.StatusOK),\n\t\tBody: body,\n\t\tHeader: make(http.Header),\n\t}}}\n\tmodel := mustNewOpenAILLMWithConfig(t, config, \"gpt-4o\")\n\n\tstream, err := model.Chat(\n\t\tcontext.Background(),\n\t\tllm.NewChatContext(),\n\t\tllm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"Chat() error = %v\", err)\n\t}\n\tnextDone := make(chan error, 1)\n\tgo func() {\n\t\t_, err := stream.Next()\n\t\tnextDone <- err\n\t}()\n\tselect {\n\tcase <-body.reading:\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"timed out waiting for OpenAI stream read\")\n\t}\n\tif err := stream.Close(); err != nil {\n\t\tt.Fatalf(\"Close() error = %v\", err)\n\t}\n\tif _, err := stream.Next(); !errors.Is(err, io.EOF) {\n\t\tt.Fatalf(\"Next() after Close error = %T %v, want EOF\", err, err)\n\t}\n\tselect {\n\tcase err := <-nextDone:\n\t\tif !errors.Is(err, io.EOF) {\n\t\t\tt.Fatalf(\"blocked Next after Close error = %T %v, want EOF\", err, err)\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"blocked Next did not return after Close\")\n\t}\n}\n\nfunc TestOpenAILLMProviderCloseClosesActiveStreams(t *testing.T) {\n\trequestStarted := make(chan struct{})\n\tbody := newBlockingEOFReadCloser(nil)\n\tconfig := openaisdk.DefaultConfig(\"test-key\")\n\tconfig.HTTPClient = openAITestHTTPDoer(func(req *http.Request) (*http.Response, error) {\n\t\tclose(requestStarted)\n\t\treturn &http.Response{\n\t\t\tStatusCode: http.StatusOK,\n\t\t\tStatus: http.StatusText(http.StatusOK),\n\t\t\tBody: body,\n\t\t\tHeader: make(http.Header),\n\t\t\tRequest: req,\n\t\t}, nil\n\t})\n\tmodel := mustNewOpenAILLMWithConfig(t, config, \"gpt-4o\")\n\n\tstream, err := model.Chat(\n\t\tcontext.Background(),\n\t\tllm.NewChatContext(),\n\t\tllm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"Chat() error = %v\", err)\n\t}\n\tdefer stream.Close()\n\tnextDone := make(chan error, 1)\n\tgo func() {\n\t\t_, err := stream.Next()\n\t\tnextDone <- err\n\t}()\n\tselect {\n\tcase <-requestStarted:\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"timed out waiting for OpenAI chat request\")\n\t}\n\n\tif err := llm.Close(model); err != nil {\n\t\tt.Fatalf(\"llm.Close error = %v\", err)\n\t}\n\tselect {\n\tcase <-body.closed:\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"llm.Close did not close active OpenAI chat stream\")\n\t}\n\tif _, err := stream.Next(); !errors.Is(err, io.EOF) {\n\t\tt.Fatalf(\"Next() after provider Close error = %T %v, want EOF\", err, err)\n\t}\n\tselect {\n\tcase err := <-nextDone:\n\t\tif err != nil && !errors.Is(err, io.EOF) {\n\t\t\tt.Fatalf(\"blocked Next after provider Close error = %T %v, want EOF\", err, err)\n\t\t}\n\tdefault:\n\t}\n\tif err := llm.Close(model); err != nil {\n\t\tt.Fatalf(\"second llm.Close error = %v\", err)\n\t}\n}\n\nfunc TestOpenAILLMProviderCloseCancelsPendingChat(t *testing.T) {\n\trequestStarted := make(chan struct{})\n\trequestCanceled := make(chan struct{})\n\tconfig := openaisdk.DefaultConfig(\"test-key\")\n\tconfig.HTTPClient = openAITestHTTPDoer(func(req *http.Request) (*http.Response, error) {\n\t\tclose(requestStarted)\n\t\t<-req.Context().Done()\n\t\tclose(requestCanceled)\n\t\treturn nil, req.Context().Err()\n\t})\n\tmodel := mustNewOpenAILLMWithConfig(t, config, \"gpt-4o\")\n\n\tdone := make(chan error, 1)\n\tgo func() {\n\t\t_, err := startOpenAIChat(t, model,\n\t\t\tcontext.Background(),\n\t\t\tllm.NewChatContext(),\n\t\t\tllm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}),\n\t\t)\n\t\tdone <- err\n\t}()\n\n\tselect {\n\tcase <-requestStarted:\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"timed out waiting for chat request\")\n\t}\n\tif err := llm.Close(model); err != nil {\n\t\tt.Fatalf(\"llm.Close error = %v\", err)\n\t}\n\tselect {\n\tcase <-requestCanceled:\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"llm.Close did not cancel pending OpenAI chat request\")\n\t}\n\tselect {\n\tcase err := <-done:\n\t\tif err != nil && !errors.Is(err, io.ErrClosedPipe) && !errors.Is(err, io.EOF) {\n\t\t\tt.Fatalf(\"Chat error after provider Close = %T %v, want nil, io.ErrClosedPipe, or EOF\", err, err)\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"Chat did not return after provider Close\")\n\t}\n}\n\nfunc TestOpenAILLMChatAfterCloseIsRejected(t *testing.T) {\n\tcapture := &sequenceHTTPClient{responses: []*http.Response{\n\t\topenAITestResponse(http.StatusOK, \"data: [DONE]\\n\\n\"),\n\t\topenAITestResponse(http.StatusOK, \"data: [DONE]\\n\\n\"),\n\t}}\n\tconfig := openaisdk.DefaultConfig(\"test-key\")\n\tconfig.HTTPClient = capture\n\tmodel := mustNewOpenAILLMWithConfig(t, config, \"gpt-4o\")\n\n\tstream, err := startOpenAIChat(t, model,\n\t\tcontext.Background(),\n\t\tllm.NewChatContext(),\n\t\tllm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"initial Chat() error = %v\", err)\n\t}\n\tif err := stream.Close(); err != nil {\n\t\tt.Fatalf(\"stream Close() error = %v\", err)\n\t}\n\tif err := llm.Close(model); err != nil {\n\t\tt.Fatalf(\"llm.Close error = %v\", err)\n\t}\n\n\t_, err = model.Chat(\n\t\tcontext.Background(),\n\t\tllm.NewChatContext(),\n\t\tllm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}),\n\t)\n\tif !errors.Is(err, io.ErrClosedPipe) {\n\t\tt.Fatalf(\"Chat() after Close error = %T %v, want io.ErrClosedPipe\", err, err)\n\t}\n\tif capture.calls != 1 {\n\t\tt.Fatalf(\"HTTP calls after Chat() post-close = %d, want only initial request\", capture.calls)\n\t}\n}\n\nfunc TestOpenAILLMRegisterStreamAfterCloseClosesStream(t *testing.T) {\n\tbody := newBlockingEOFReadCloser(nil)\n\tconfig := openaisdk.DefaultConfig(\"test-key\")\n\tconfig.HTTPClient = &sequenceHTTPClient{responses: []*http.Response{{\n\t\tStatusCode: http.StatusOK,\n\t\tStatus: http.StatusText(http.StatusOK),\n\t\tBody: body,\n\t\tHeader: make(http.Header),\n\t}}}\n\tmodel := mustNewOpenAILLMWithConfig(t, config, \"gpt-4o\")\n\n\tstream, err := model.chatEager(\n\t\tcontext.Background(),\n\t\tllm.NewChatContext(),\n\t\tllm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"Chat() error = %v\", err)\n\t}\n\twrapped, ok := stream.(*openaiStream)\n\tif !ok {\n\t\tt.Fatalf(\"stream = %T, want *openaiStream\", stream)\n\t}\n\tmodel.unregisterStream(wrapped)\n\tdefer wrapped.Close()\n\n\tif err := model.Close(); err != nil {\n\t\tt.Fatalf(\"llm.Close error = %v\", err)\n\t}\n\tmodel.registerStream(wrapped)\n\n\tselect {\n\tcase <-body.closed:\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"late OpenAI chat stream was not closed after provider Close\")\n\t}\n\tif !wrapped.isClosed() {\n\t\tt.Fatal(\"late OpenAI chat stream closed = false, want true after rejected registration\")\n\t}\n}\n\ntype sequenceHTTPClient struct {\n\tresponses []*http.Response\n\tcalls int\n}\n\nfunc (c *sequenceHTTPClient) Do(req *http.Request) (*http.Response, error) {\n\tif c.calls >= len(c.responses) {\n\t\treturn nil, errors.New(\"unexpected HTTP call\")\n\t}\n\tresp := c.responses[c.calls]\n\tresp.Request = req\n\tc.calls++\n\treturn resp, nil\n}\n\nfunc openAITestResponse(statusCode int, body string) *http.Response {\n\treturn &http.Response{\n\t\tStatusCode: statusCode,\n\t\tStatus: http.StatusText(statusCode),\n\t\tBody: io.NopCloser(strings.NewReader(body)),\n\t\tHeader: make(http.Header),\n\t}\n}\n\nfunc TestBuildOpenAIChatCompletionRequestMarksToolsStrict(t *testing.T) {\n\treq := buildOpenAIChatCompletionRequest(\"gpt-4o\", llm.NewChatContext(), &llm.ChatOptions{\n\t\tTools: []llm.Tool{requestTestTool{}},\n\t})\n\n\tif len(req.Tools) != 1 {\n\t\tt.Fatalf(\"len(Tools) = %d, want 1\", len(req.Tools))\n\t}\n\tif req.Tools[0].Function == nil {\n\t\tt.Fatalf(\"tool function is nil\")\n\t}\n\tif !req.Tools[0].Function.Strict {\n\t\tt.Fatalf(\"tool strict = false, want true\")\n\t}\n}\n\nfunc TestBuildOpenAIChatCompletionRequestNormalizesStrictToolSchema(t *testing.T) {\n\treq := buildOpenAIChatCompletionRequest(\"gpt-4.1-mini\", llm.NewChatContext(), &llm.ChatOptions{\n\t\tTools: []llm.Tool{requestOptionalSchemaTool{}},\n\t})\n\n\tif len(req.Tools) != 1 {\n\t\tt.Fatalf(\"len(Tools) = %d, want 1\", len(req.Tools))\n\t}\n\tif req.Tools[0].Function == nil {\n\t\tt.Fatal(\"tool function is nil\")\n\t}\n\tif !req.Tools[0].Function.Strict {\n\t\tt.Fatalf(\"tool strict = false, want true\")\n\t}\n\n\tparamsJSON, err := json.Marshal(req.Tools[0].Function.Parameters)\n\tif err != nil {\n\t\tt.Fatalf(\"tool parameters marshal error = %v\", err)\n\t}\n\tvar params map[string]any\n\tif err := json.Unmarshal(paramsJSON, ¶ms); err != nil {\n\t\tt.Fatalf(\"tool parameters json error = %v\", err)\n\t}\n\n\tif got := params[\"additionalProperties\"]; got != false {\n\t\tt.Fatalf(\"additionalProperties = %#v, want false\", got)\n\t}\n\n\trequired, ok := params[\"required\"].([]any)\n\tif !ok {\n\t\tt.Fatalf(\"required = %#v, want JSON array\", params[\"required\"])\n\t}\n\tif len(required) != 1 || required[0] != \"variant\" {\n\t\tt.Fatalf(\"required = %#v, want [variant]\", required)\n\t}\n\n\tproperties, ok := params[\"properties\"].(map[string]any)\n\tif !ok {\n\t\tt.Fatalf(\"properties = %#v, want object\", params[\"properties\"])\n\t}\n\tvariant, ok := properties[\"variant\"].(map[string]any)\n\tif !ok {\n\t\tt.Fatalf(\"properties.variant = %#v, want object\", properties[\"variant\"])\n\t}\n\ttypes, ok := variant[\"type\"].([]any)\n\tif !ok {\n\t\tt.Fatalf(\"properties.variant.type = %#v, want array\", variant[\"type\"])\n\t}\n\tif len(types) != 2 || types[0] != \"string\" || types[1] != \"null\" {\n\t\tt.Fatalf(\"properties.variant.type = %#v, want [string null]\", types)\n\t}\n}\n\nfunc TestBuildOpenAIChatCompletionRequestKeepsStrictNoArgToolPropertiesObject(t *testing.T) {\n\treq := buildOpenAIChatCompletionRequest(\"gpt-4.1-mini\", llm.NewChatContext(), &llm.ChatOptions{\n\t\tTools: []llm.Tool{requestNoArgSchemaTool{}},\n\t})\n\n\tif len(req.Tools) != 1 || req.Tools[0].Function == nil {\n\t\tt.Fatalf(\"tool = %#v, want one function tool\", req.Tools)\n\t}\n\tparamsJSON, err := json.Marshal(req.Tools[0].Function.Parameters)\n\tif err != nil {\n\t\tt.Fatalf(\"tool parameters marshal error = %v\", err)\n\t}\n\tif strings.Contains(string(paramsJSON), `\"properties\":null`) {\n\t\tt.Fatalf(\"tool parameters = %s, want properties object not null\", paramsJSON)\n\t}\n\tvar params map[string]any\n\tif err := json.Unmarshal(paramsJSON, ¶ms); err != nil {\n\t\tt.Fatalf(\"tool parameters json error = %v\", err)\n\t}\n\tif got := params[\"additionalProperties\"]; got != false {\n\t\tt.Fatalf(\"additionalProperties = %#v, want false\", got)\n\t}\n\tproperties, ok := params[\"properties\"].(map[string]any)\n\tif !ok || len(properties) != 0 {\n\t\tt.Fatalf(\"properties = %#v, want empty object\", params[\"properties\"])\n\t}\n\tif _, ok := params[\"required\"]; ok {\n\t\tt.Fatalf(\"required = %#v, want omitted for no-arg tool\", params[\"required\"])\n\t}\n}\n\nfunc TestBuildOpenAIChatCompletionRequestNormalizesStrictToolSchemaUnions(t *testing.T) {\n\treq := buildOpenAIChatCompletionRequest(\"gpt-4.1-mini\", llm.NewChatContext(), &llm.ChatOptions{\n\t\tTools: []llm.Tool{requestUnionSchemaTool{}},\n\t})\n\n\tparamsJSON, err := json.Marshal(req.Tools[0].Function.Parameters)\n\tif err != nil {\n\t\tt.Fatalf(\"tool parameters marshal error = %v\", err)\n\t}\n\tvar params map[string]any\n\tif err := json.Unmarshal(paramsJSON, ¶ms); err != nil {\n\t\tt.Fatalf(\"tool parameters json error = %v\", err)\n\t}\n\n\tproperties := params[\"properties\"].(map[string]any)\n\tquery := properties[\"query\"].(map[string]any)\n\tif _, ok := query[\"oneOf\"]; ok {\n\t\tt.Fatalf(\"query.oneOf = %#v, want converted away for strict OpenAI schema\", query[\"oneOf\"])\n\t}\n\tanyOf, ok := query[\"anyOf\"].([]any)\n\tif !ok {\n\t\tt.Fatalf(\"query.anyOf = %#v, want anyOf array\", query[\"anyOf\"])\n\t}\n\tif len(anyOf) != 2 {\n\t\tt.Fatalf(\"query.anyOf = %#v, want two non-empty variants\", anyOf)\n\t}\n}\n\nfunc TestBuildOpenAIChatCompletionRequestNormalizesStrictOptionalUnionToolSchema(t *testing.T) {\n\treq := buildOpenAIChatCompletionRequest(\"gpt-4.1-mini\", llm.NewChatContext(), &llm.ChatOptions{\n\t\tTools: []llm.Tool{requestOptionalUnionSchemaTool{}},\n\t})\n\n\tparamsJSON, err := json.Marshal(req.Tools[0].Function.Parameters)\n\tif err != nil {\n\t\tt.Fatalf(\"tool parameters marshal error = %v\", err)\n\t}\n\tvar params map[string]any\n\tif err := json.Unmarshal(paramsJSON, ¶ms); err != nil {\n\t\tt.Fatalf(\"tool parameters json error = %v\", err)\n\t}\n\n\trequired, ok := params[\"required\"].([]any)\n\tif !ok || len(required) != 1 || required[0] != \"query\" {\n\t\tt.Fatalf(\"required = %#v, want [query]\", params[\"required\"])\n\t}\n\tproperties := params[\"properties\"].(map[string]any)\n\tquery := properties[\"query\"].(map[string]any)\n\tanyOf, ok := query[\"anyOf\"].([]any)\n\tif !ok {\n\t\tt.Fatalf(\"query.anyOf = %#v, want anyOf array\", query[\"anyOf\"])\n\t}\n\thasNull := false\n\tfor _, variant := range anyOf {\n\t\tvariantSchema, ok := variant.(map[string]any)\n\t\tif ok && variantSchema[\"type\"] == \"null\" {\n\t\t\thasNull = true\n\t\t}\n\t}\n\tif !hasNull {\n\t\tt.Fatalf(\"query.anyOf = %#v, want null variant for optional union field\", anyOf)\n\t}\n}\n\nfunc TestBuildOpenAIChatCompletionRequestNormalizesStrictToolSchemaNullableUnion(t *testing.T) {\n\treq := buildOpenAIChatCompletionRequest(\"gpt-4.1-mini\", llm.NewChatContext(), &llm.ChatOptions{\n\t\tTools: []llm.Tool{requestNullableUnionSchemaTool{}},\n\t})\n\n\tparamsJSON, err := json.Marshal(req.Tools[0].Function.Parameters)\n\tif err != nil {\n\t\tt.Fatalf(\"tool parameters marshal error = %v\", err)\n\t}\n\tvar params map[string]any\n\tif err := json.Unmarshal(paramsJSON, ¶ms); err != nil {\n\t\tt.Fatalf(\"tool parameters json error = %v\", err)\n\t}\n\n\tproperties := params[\"properties\"].(map[string]any)\n\tmode := properties[\"mode\"].(map[string]any)\n\tif _, ok := mode[\"anyOf\"]; ok {\n\t\tt.Fatalf(\"mode.anyOf = %#v, want collapsed nullable type\", mode[\"anyOf\"])\n\t}\n\ttypes, ok := mode[\"type\"].([]any)\n\tif !ok || len(types) != 2 || types[0] != \"string\" || types[1] != \"null\" {\n\t\tt.Fatalf(\"mode.type = %#v, want [string null]\", mode[\"type\"])\n\t}\n\tenum, ok := mode[\"enum\"].([]any)\n\tif !ok || len(enum) != 3 || enum[0] != \"fast\" || enum[1] != \"safe\" || enum[2] != nil {\n\t\tt.Fatalf(\"mode.enum = %#v, want [fast safe nil]\", mode[\"enum\"])\n\t}\n}\n\nfunc TestBuildOpenAIChatCompletionRequestNormalizesStrictToolSchemaDefs(t *testing.T) {\n\treq := buildOpenAIChatCompletionRequest(\"gpt-4.1-mini\", llm.NewChatContext(), &llm.ChatOptions{\n\t\tTools: []llm.Tool{requestDefsSchemaTool{}},\n\t})\n\n\tparamsJSON, err := json.Marshal(req.Tools[0].Function.Parameters)\n\tif err != nil {\n\t\tt.Fatalf(\"tool parameters marshal error = %v\", err)\n\t}\n\tvar params map[string]any\n\tif err := json.Unmarshal(paramsJSON, ¶ms); err != nil {\n\t\tt.Fatalf(\"tool parameters json error = %v\", err)\n\t}\n\n\tdefs := params[\"$defs\"].(map[string]any)\n\tpayload := defs[\"payload\"].(map[string]any)\n\tif got := payload[\"additionalProperties\"]; got != false {\n\t\tt.Fatalf(\"$defs.payload.additionalProperties = %#v, want false\", got)\n\t}\n\trequired, ok := payload[\"required\"].([]any)\n\tif !ok || len(required) != 1 || required[0] != \"query\" {\n\t\tt.Fatalf(\"$defs.payload.required = %#v, want [query]\", payload[\"required\"])\n\t}\n}\n\nfunc TestBuildOpenAIChatCompletionRequestNormalizesStrictToolSchemaRefSiblings(t *testing.T) {\n\treq := buildOpenAIChatCompletionRequest(\"gpt-4.1-mini\", llm.NewChatContext(), &llm.ChatOptions{\n\t\tTools: []llm.Tool{requestRefSiblingSchemaTool{}},\n\t})\n\n\tparamsJSON, err := json.Marshal(req.Tools[0].Function.Parameters)\n\tif err != nil {\n\t\tt.Fatalf(\"tool parameters marshal error = %v\", err)\n\t}\n\tvar params map[string]any\n\tif err := json.Unmarshal(paramsJSON, ¶ms); err != nil {\n\t\tt.Fatalf(\"tool parameters json error = %v\", err)\n\t}\n\n\tproperties := params[\"properties\"].(map[string]any)\n\tpayload := properties[\"payload\"].(map[string]any)\n\tif _, ok := payload[\"$ref\"]; ok {\n\t\tt.Fatalf(\"payload.$ref = %#v, want inlined strict schema\", payload[\"$ref\"])\n\t}\n\tif got := payload[\"description\"]; got != \"caller payload\" {\n\t\tt.Fatalf(\"payload.description = %#v, want caller payload\", got)\n\t}\n\tif got := payload[\"additionalProperties\"]; got != false {\n\t\tt.Fatalf(\"payload.additionalProperties = %#v, want false\", got)\n\t}\n\trequired, ok := payload[\"required\"].([]any)\n\tif !ok || len(required) != 1 || required[0] != \"query\" {\n\t\tt.Fatalf(\"payload.required = %#v, want [query]\", payload[\"required\"])\n\t}\n}\n\nfunc TestBuildOpenAIChatCompletionRequestNormalizesStrictToolSchemaAllOfDefaultNullable(t *testing.T) {\n\treq := buildOpenAIChatCompletionRequest(\"gpt-4.1-mini\", llm.NewChatContext(), &llm.ChatOptions{\n\t\tTools: []llm.Tool{requestAllOfDefaultSchemaTool{}},\n\t})\n\n\tparamsJSON, err := json.Marshal(req.Tools[0].Function.Parameters)\n\tif err != nil {\n\t\tt.Fatalf(\"tool parameters marshal error = %v\", err)\n\t}\n\tvar params map[string]any\n\tif err := json.Unmarshal(paramsJSON, ¶ms); err != nil {\n\t\tt.Fatalf(\"tool parameters json error = %v\", err)\n\t}\n\n\tproperties := params[\"properties\"].(map[string]any)\n\tmode := properties[\"mode\"].(map[string]any)\n\tif _, ok := mode[\"allOf\"]; ok {\n\t\tt.Fatalf(\"mode.allOf = %#v, want collapsed single allOf schema\", mode[\"allOf\"])\n\t}\n\tif _, ok := mode[\"default\"]; ok {\n\t\tt.Fatalf(\"mode.default = %#v, want omitted for strict schema\", mode[\"default\"])\n\t}\n\ttypes, ok := mode[\"type\"].([]any)\n\tif !ok || len(types) != 2 || types[0] != \"string\" || types[1] != \"null\" {\n\t\tt.Fatalf(\"mode.type = %#v, want [string null]\", mode[\"type\"])\n\t}\n}\n\nfunc TestBuildOpenAIChatCompletionRequestMapsNamedToolChoice(t *testing.T) {\n\treq := buildOpenAIChatCompletionRequest(\"gpt-4o\", llm.NewChatContext(), &llm.ChatOptions{\n\t\tTools: []llm.Tool{requestTestTool{}},\n\t\tToolChoice: map[string]any{\n\t\t\t\"type\": \"function\",\n\t\t\t\"function\": map[string]any{\n\t\t\t\t\"name\": \"lookup\",\n\t\t\t},\n\t\t},\n\t})\n\n\tchoice, ok := req.ToolChoice.(openaisdk.ToolChoice)\n\tif !ok {\n\t\tt.Fatalf(\"ToolChoice = %#v, want openai.ToolChoice\", req.ToolChoice)\n\t}\n\tif choice.Type != openaisdk.ToolTypeFunction {\n\t\tt.Fatalf(\"ToolChoice.Type = %q, want function\", choice.Type)\n\t}\n\tif choice.Function.Name != \"lookup\" {\n\t\tt.Fatalf(\"ToolChoice.Function.Name = %q, want lookup\", choice.Function.Name)\n\t}\n}\n\nfunc TestBuildOpenAIChatCompletionRequestMapsResponseFormat(t *testing.T) {\n\treq := buildOpenAIChatCompletionRequest(\"gpt-4o\", llm.NewChatContext(), &llm.ChatOptions{\n\t\tResponseFormat: map[string]any{\n\t\t\t\"type\": \"json_schema\",\n\t\t\t\"json_schema\": map[string]any{\n\t\t\t\t\"name\": \"WeatherAnswer\",\n\t\t\t\t\"strict\": true,\n\t\t\t\t\"schema\": map[string]any{\n\t\t\t\t\t\"type\": \"object\",\n\t\t\t\t\t\"properties\": map[string]any{\n\t\t\t\t\t\t\"summary\": map[string]any{\"type\": \"string\"},\n\t\t\t\t\t},\n\t\t\t\t\t\"required\": []string{\"summary\"},\n\t\t\t\t\t\"additionalProperties\": false,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\n\tif req.ResponseFormat == nil {\n\t\tt.Fatal(\"ResponseFormat = nil, want json_schema response format\")\n\t}\n\tif req.ResponseFormat.Type != openaisdk.ChatCompletionResponseFormatTypeJSONSchema {\n\t\tt.Fatalf(\"ResponseFormat.Type = %q, want json_schema\", req.ResponseFormat.Type)\n\t}\n\tif req.ResponseFormat.JSONSchema == nil {\n\t\tt.Fatal(\"ResponseFormat.JSONSchema = nil, want schema\")\n\t}\n\tif req.ResponseFormat.JSONSchema.Name != \"WeatherAnswer\" || !req.ResponseFormat.JSONSchema.Strict {\n\t\tt.Fatalf(\"ResponseFormat.JSONSchema = %#v, want strict WeatherAnswer\", req.ResponseFormat.JSONSchema)\n\t}\n}\n\nfunc TestBuildOpenAIChatCompletionRequestAppliesExtraParamResponseFormat(t *testing.T) {\n\treq := buildOpenAIChatCompletionRequest(\"gpt-4o\", llm.NewChatContext(), &llm.ChatOptions{\n\t\tExtraParams: map[string]any{\n\t\t\t\"response_format\": map[string]any{\n\t\t\t\t\"type\": \"json_object\",\n\t\t\t},\n\t\t},\n\t})\n\n\tif req.ResponseFormat == nil {\n\t\tt.Fatal(\"ResponseFormat = nil, want json_object response format\")\n\t}\n\tif req.ResponseFormat.Type != openaisdk.ChatCompletionResponseFormatTypeJSONObject {\n\t\tt.Fatalf(\"ResponseFormat.Type = %q, want json_object\", req.ResponseFormat.Type)\n\t}\n}\n\nfunc TestBuildOpenAIChatCompletionRequestResponseFormatOverridesExtraParam(t *testing.T) {\n\treq := buildOpenAIChatCompletionRequest(\"gpt-4o\", llm.NewChatContext(), &llm.ChatOptions{\n\t\tResponseFormat: map[string]any{\n\t\t\t\"type\": \"json_schema\",\n\t\t\t\"json_schema\": map[string]any{\n\t\t\t\t\"name\": \"Answer\",\n\t\t\t\t\"strict\": true,\n\t\t\t},\n\t\t},\n\t\tExtraParams: map[string]any{\n\t\t\t\"response_format\": map[string]any{\n\t\t\t\t\"type\": \"json_object\",\n\t\t\t},\n\t\t\t\"metadata\": map[string]any{\n\t\t\t\t\"trace\": \"abc\",\n\t\t\t},\n\t\t},\n\t})\n\n\tif req.ResponseFormat == nil {\n\t\tt.Fatal(\"ResponseFormat = nil, want explicit response format\")\n\t}\n\tif req.ResponseFormat.Type != openaisdk.ChatCompletionResponseFormatTypeJSONSchema {\n\t\tt.Fatalf(\"ResponseFormat.Type = %q, want json_schema\", req.ResponseFormat.Type)\n\t}\n\tif req.ResponseFormat.JSONSchema == nil || req.ResponseFormat.JSONSchema.Name != \"Answer\" || !req.ResponseFormat.JSONSchema.Strict {\n\t\tt.Fatalf(\"ResponseFormat.JSONSchema = %#v, want strict Answer\", req.ResponseFormat.JSONSchema)\n\t}\n\tif req.Metadata[\"trace\"] != \"abc\" {\n\t\tt.Fatalf(\"Metadata = %#v, want generic metadata preserved\", req.Metadata)\n\t}\n}\n\nfunc TestOpenAIChatAppliesExtraParamPromptCacheOptions(t *testing.T) {\n\tcapture := &captureDeadlineHTTPClient{\n\t\tstatusCode: http.StatusBadRequest,\n\t\tresponseBody: `{\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"bad_request\"}}`,\n\t}\n\tmodel := NewOpenAILLMWithBaseURLAndHTTPClient(\n\t\t\"test-key\",\n\t\t\"gpt-4o\",\n\t\t\"https://openai.test/v1\",\n\t\tcapture,\n\t)\n\n\t_, _ = startOpenAIChat(t, model,\n\t\tcontext.Background(),\n\t\tllm.NewChatContext(),\n\t\tllm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}),\n\t\tllm.WithExtraParams(map[string]any{\n\t\t\t\"prompt_cache_key\": \"room-123\",\n\t\t\t\"prompt_cache_retention\": \"24h\",\n\t\t}),\n\t)\n\n\tif !strings.Contains(capture.requestBody, `\"prompt_cache_key\":\"room-123\"`) {\n\t\tt.Fatalf(\"request body = %s, want call prompt_cache_key\", capture.requestBody)\n\t}\n\tif !strings.Contains(capture.requestBody, `\"prompt_cache_retention\":\"24h\"`) {\n\t\tt.Fatalf(\"request body = %s, want call prompt_cache_retention\", capture.requestBody)\n\t}\n}\n\nfunc TestOpenAIChatForwardsReferenceExtraContent(t *testing.T) {\n\tcapture := &captureDeadlineHTTPClient{\n\t\tresponseBody: \"data: [DONE]\\n\\n\",\n\t}\n\tconfig := openaisdk.DefaultConfig(\"test-key\")\n\tconfig.HTTPClient = capture\n\tmodel := mustNewOpenAILLMWithConfig(t, config, \"gpt-4o\")\n\n\tchatCtx := llm.NewChatContext()\n\tchatCtx.Append(&llm.ChatMessage{\n\t\tID: \"assistant-turn\",\n\t\tRole: llm.ChatRoleAssistant,\n\t\tContent: []llm.ChatContent{{\n\t\t\tText: \"checking\",\n\t\t}},\n\t\tExtra: map[string]any{\n\t\t\t\"google\": map[string]any{\"thought_signature\": \"msg-sig\"},\n\t\t\t\"debug\": \"drop-message-extra\",\n\t\t},\n\t})\n\tchatCtx.Append(&llm.FunctionCall{\n\t\tID: \"assistant-turn/tool\",\n\t\tCallID: \"call_lookup\",\n\t\tName: \"lookup\",\n\t\tArguments: `{\"city\":\"Paris\"}`,\n\t\tExtra: map[string]any{\n\t\t\t\"livekit\": map[string]any{\"turn_id\": \"turn-1\"},\n\t\t\t\"xai\": map[string]any{\"trace\": \"tool-trace\"},\n\t\t\t\"debug\": \"drop-tool-extra\",\n\t\t},\n\t})\n\tchatCtx.Append(&llm.FunctionCallOutput{\n\t\tID: \"lookup-output\",\n\t\tCallID: \"call_lookup\",\n\t\tName: \"lookup\",\n\t\tOutput: \"sunny\",\n\t})\n\n\tstream, err := startOpenAIChat(t, model, context.Background(), chatCtx, llm.WithConnectOptions(llm.APIConnectOptions{MaxRetry: 0}))\n\tif err != nil {\n\t\tt.Fatalf(\"Chat returned error: %v\", err)\n\t}\n\tif stream != nil {\n\t\t_ = stream.Close()\n\t}\n\n\tvar body map[string]any\n\tif err := json.Unmarshal([]byte(capture.requestBody), &body); err != nil {\n\t\tt.Fatalf(\"Unmarshal request body returned error: %v\", err)\n\t}\n\tmessages, ok := body[\"messages\"].([]any)\n\tif !ok || len(messages) == 0 {\n\t\tt.Fatalf(\"messages = %#v, want non-empty array; body %s\", body[\"messages\"], capture.requestBody)\n\t}\n\tassistant, ok := messages[0].(map[string]any)\n\tif !ok {\n\t\tt.Fatalf(\"messages[0] = %#v, want object\", messages[0])\n\t}\n\tmessageExtra, ok := assistant[\"extra_content\"].(map[string]any)\n\tif !ok {\n\t\tt.Fatalf(\"assistant extra_content = %#v, want object; body %s\", assistant[\"extra_content\"], capture.requestBody)\n\t}\n\tif _, ok := messageExtra[\"google\"].(map[string]any); !ok {\n\t\tt.Fatalf(\"assistant extra_content = %#v, want google payload\", messageExtra)\n\t}\n\tif _, ok := messageExtra[\"debug\"]; ok {\n\t\tt.Fatalf(\"assistant extra_content = %#v, want filtered unsupported key\", messageExtra)\n\t}\n\n\ttoolCalls, ok := assistant[\"tool_calls\"].([]any)\n\tif !ok || len(toolCalls) != 1 {\n\t\tt.Fatalf(\"tool_calls = %#v, want one tool call; body %s\", assistant[\"tool_calls\"], capture.requestBody)\n\t}\n\ttoolCall, ok := toolCalls[0].(map[string]any)\n\tif !ok {\n\t\tt.Fatalf(\"tool_calls[0] = %#v, want object\", toolCalls[0])\n\t}\n\ttoolExtra, ok := toolCall[\"extra_content\"].(map[string]any)\n\tif !ok {\n\t\tt.Fatalf(\"tool extra_content = %#v, want object; body %s\", toolCall[\"extra_content\"], capture.requestBody)\n\t}\n\tif _, ok := toolExtra[\"livekit\"].(map[string]any); !ok {\n\t\tt.Fatalf(\"tool extra_content = %#v, want livekit payload\", toolExtra)\n\t}\n\tif _, ok := toolExtra[\"xai\"].(map[string]any); !ok {\n\t\tt.Fatalf(\"tool extra_content = %#v, want xai payload\", toolExtra)\n\t}\n\tif _, ok := toolExtra[\"debug\"]; ok {\n\t\tt.Fatalf(\"tool extra_content = %#v, want filtered unsupported key\", toolExtra)\n\t}\n}\n\nfunc TestBuildOpenAIChatCompletionRequestDropsUnsupportedReasoningParams(t *testing.T) {\n\treq := buildOpenAIChatCompletionRequest(\"openai/gpt-5\", llm.NewChatContext(), &llm.ChatOptions{\n\t\tParallelToolCalls: true,\n\t\tExtraParams: map[string]any{\n\t\t\t\"temperature\": 0.7,\n\t\t\t\"top_p\": 0.8,\n\t\t\t\"presence_penalty\": 0.1,\n\t\t\t\"frequency_penalty\": 0.2,\n\t\t\t\"n\": 2,\n\t\t\t\"logit_bias\": map[string]any{\"42\": 7.0},\n\t\t\t\"logprobs\": true,\n\t\t\t\"top_logprobs\": 3,\n\t\t\t\"reasoning_effort\": \"low\",\n\t\t\t\"max_completion_tokens\": 128,\n\t\t\t\"service_tier\": \"priority\",\n\t\t\t\"stop\": []string{\"END\"},\n\t\t},\n\t})\n\n\tif req.Temperature != 0 || req.TopP != 0 || req.PresencePenalty != 0 || req.FrequencyPenalty != 0 {\n\t\tt.Fatalf(\"sampling params = %v/%v/%v/%v, want dropped zero values\", req.Temperature, req.TopP, req.PresencePenalty, req.FrequencyPenalty)\n\t}\n\tif req.N != 0 || req.LogitBias != nil || req.LogProbs || req.TopLogProbs != 0 {\n\t\tt.Fatalf(\"unsupported params not dropped: N=%d LogitBias=%#v LogProbs=%v TopLogProbs=%d\", req.N, req.LogitBias, req.LogProbs, req.TopLogProbs)\n\t}\n\tif req.ReasoningEffort != \"low\" {\n\t\tt.Fatalf(\"ReasoningEffort = %q, want preserved for reasoning model without tools\", req.ReasoningEffort)\n\t}\n\tif req.MaxCompletionTokens != 128 || req.ServiceTier != openaisdk.ServiceTierPriority || len(req.Stop) != 1 || req.Stop[0] != \"END\" {\n\t\tt.Fatalf(\"supported params = max_completion_tokens %d service_tier %q stop %#v, want preserved\", req.MaxCompletionTokens, req.ServiceTier, req.Stop)\n\t}\n}\n\nfunc TestBuildOpenAIChatCompletionRequestDefaultsReasoningEffort(t *testing.T) {\n\tcases := []struct {\n\t\tmodel string\n\t\twant string\n\t}{\n\t\t{model: \"gpt-5\", want: \"minimal\"},\n\t\t{model: \"gpt-5-mini\", want: \"minimal\"},\n\t\t{model: \"gpt-5-nano\", want: \"minimal\"},\n\t\t{model: \"gpt-5.1\", want: \"none\"},\n\t\t{model: \"gpt-5.2\", want: \"none\"},\n\t\t{model: \"gpt-5.4\", want: \"none\"},\n\t\t{model: \"gpt-5.4-mini\", want: \"minimal\"},\n\t\t{model: \"gpt-4.1\", want: \"\"},\n\t}\n\n\tfor _, tc := range cases {\n\t\tt.Run(tc.model, func(t *testing.T) {\n\t\t\treq := buildOpenAIChatCompletionRequest(tc.model, llm.NewChatContext(), &llm.ChatOptions{\n\t\t\t\tParallelToolCalls: true,\n\t\t\t})\n\t\t\tif req.ReasoningEffort != tc.want {\n\t\t\t\tt.Fatalf(\"ReasoningEffort = %q, want %q\", req.ReasoningEffort, tc.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestBuildOpenAIChatCompletionRequestDoesNotDefaultProviderPrefixedReasoning(t *testing.T) {\n\treq := buildOpenAIChatCompletionRequest(\"openai/gpt-5.4-mini\", llm.NewChatContext(), &llm.ChatOptions{})\n\n\tif req.ReasoningEffort != \"\" {\n\t\tt.Fatalf(\"ReasoningEffort = %q, want omitted for reference exact model support check\", req.ReasoningEffort)\n\t}\n}\n\nfunc TestBuildOpenAIChatCompletionRequestDropsReasoningEffortWithIncompatibleTools(t *testing.T) {\n\treq := buildOpenAIChatCompletionRequest(\"gpt-5.2\", llm.NewChatContext(), &llm.ChatOptions{\n\t\tTools: []llm.Tool{requestTestTool{}},\n\t\tExtraParams: map[string]any{\n\t\t\t\"reasoning_effort\": \"low\",\n\t\t\t\"max_completion_tokens\": 128,\n\t\t},\n\t})\n\n\tif req.ReasoningEffort != \"\" {\n\t\tt.Fatalf(\"ReasoningEffort = %q, want dropped for gpt-5.2 with tools\", req.ReasoningEffort)\n\t}\n\tif req.MaxCompletionTokens != 128 {\n\t\tt.Fatalf(\"MaxCompletionTokens = %d, want preserved\", req.MaxCompletionTokens)\n\t}\n}\n\nfunc TestBuildOpenAIChatCompletionRequestDropsGPT54MiniDefaultReasoningWithTools(t *testing.T) {\n\treq := buildOpenAIChatCompletionRequest(\"gpt-5.4-mini\", llm.NewChatContext(), &llm.ChatOptions{\n\t\tTools: []llm.Tool{requestTestTool{}},\n\t})\n\n\tif req.ReasoningEffort != \"\" {\n\t\tt.Fatalf(\"ReasoningEffort = %q, want dropped for reference gpt-5.4* tool incompatibility\", req.ReasoningEffort)\n\t}\n}\n\nfunc TestBuildOpenAIChatCompletionRequestDropsGPT54MiniExplicitReasoningWithTools(t *testing.T) {\n\treq := buildOpenAIChatCompletionRequest(\"gpt-5.4-mini\", llm.NewChatContext(), &llm.ChatOptions{\n\t\tTools: []llm.Tool{requestTestTool{}},\n\t\tExtraParams: map[string]any{\n\t\t\t\"reasoning_effort\": \"low\",\n\t\t},\n\t})\n\n\tif req.ReasoningEffort != \"\" {\n\t\tt.Fatalf(\"ReasoningEffort = %q, want dropped for reference gpt-5.4* tool incompatibility\", req.ReasoningEffort)\n\t}\n}\n\nfunc TestBuildOpenAIChatCompletionRequestDropsProviderPrefixedGPT54MiniReasoningWithTools(t *testing.T) {\n\treq := buildOpenAIChatCompletionRequest(\"openai/gpt-5.4-mini\", llm.NewChatContext(), &llm.ChatOptions{\n\t\tTools: []llm.Tool{requestTestTool{}},\n\t\tExtraParams: map[string]any{\n\t\t\t\"reasoning_effort\": \"low\",\n\t\t},\n\t})\n\n\tif req.ReasoningEffort != \"\" {\n\t\tt.Fatalf(\"ReasoningEffort = %q, want dropped after reference provider-prefix stripping\", req.ReasoningEffort)\n\t}\n}\n\nfunc TestOpenAICompletionUsageHandlesMissingTokenDetails(t *testing.T) {\n\tusage := openAICompletionUsage(&openaisdk.Usage{\n\t\tCompletionTokens: 7,\n\t\tPromptTokens: 11,\n\t\tTotalTokens: 18,\n\t}, \"\")\n\n\tif usage == nil {\n\t\tt.Fatal(\"openAICompletionUsage() = nil, want usage\")\n\t}\n\tif usage.CompletionTokens != 7 || usage.PromptTokens != 11 || usage.TotalTokens != 18 {\n\t\tt.Fatalf(\"usage = %#v, want base token counts\", usage)\n\t}\n\tif usage.PromptCachedTokens != 0 {\n\t\tt.Fatalf(\"PromptCachedTokens = %d, want 0 without prompt token details\", usage.PromptCachedTokens)\n\t}\n}\n\nfunc TestOpenAICompletionUsageMapsCachedPromptTokens(t *testing.T) {\n\tusage := openAICompletionUsage(&openaisdk.Usage{\n\t\tPromptTokensDetails: &openaisdk.PromptTokensDetails{CachedTokens: 5},\n\t}, \"\")\n\n\tif usage == nil {\n\t\tt.Fatal(\"openAICompletionUsage() = nil, want usage\")\n\t}\n\tif usage.PromptCachedTokens != 5 {\n\t\tt.Fatalf(\"PromptCachedTokens = %d, want 5\", usage.PromptCachedTokens)\n\t}\n}\n"}}}}