1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3use zeroclaw_macros::Configurable;
4
5use super::schema::{
6 Ai21ModelProviderConfig, AihubmixModelProviderConfig, AnthropicModelProviderConfig,
7 AnyscaleModelProviderConfig, ArceeModelProviderConfig, AstraiModelProviderConfig,
8 AtomicChatModelProviderConfig, AvianModelProviderConfig, AzureModelProviderConfig,
9 BaichuanModelProviderConfig, BasetenModelProviderConfig, BedrockModelProviderConfig,
10 CerebrasModelProviderConfig, CloudflareModelProviderConfig, CohereModelProviderConfig,
11 CopilotModelProviderConfig, CustomModelProviderConfig, DeepinfraModelProviderConfig,
12 DeepmystModelProviderConfig, DeepseekModelProviderConfig, DoubaoModelProviderConfig,
13 FeatherlessModelProviderConfig, FireworksModelProviderConfig, FriendliModelProviderConfig,
14 GeminiCliModelProviderConfig, GeminiModelProviderConfig, GithubModelsModelProviderConfig,
15 GlmModelProviderConfig, GroqModelProviderConfig, HuggingfaceModelProviderConfig,
16 HunyuanModelProviderConfig, HyperbolicModelProviderConfig, InceptionModelProviderConfig,
17 KiloCliModelProviderConfig, KiloModelProviderConfig, LambdaAiModelProviderConfig,
18 LeptonModelProviderConfig, LitellmModelProviderConfig, LlamacppModelProviderConfig,
19 LmstudioModelProviderConfig, MinimaxModelProviderConfig, MistralModelProviderConfig,
20 ModelProviderConfig, MoonshotModelProviderConfig, MorphModelProviderConfig,
21 NebiusModelProviderConfig, NovitaModelProviderConfig, NscaleModelProviderConfig,
22 NvidiaModelProviderConfig, OllamaModelProviderConfig, OpenAIModelProviderConfig,
23 OpenRouterModelProviderConfig, OpencodeModelProviderConfig, OsaurusModelProviderConfig,
24 OvhModelProviderConfig, PerplexityModelProviderConfig, QianfanModelProviderConfig,
25 QwenModelProviderConfig, RekaModelProviderConfig, SambanovaModelProviderConfig,
26 SglangModelProviderConfig, SiliconflowModelProviderConfig, StepfunModelProviderConfig,
27 SyntheticModelProviderConfig, TelnyxModelProviderConfig, TogetherModelProviderConfig,
28 UpstageModelProviderConfig, VeniceModelProviderConfig, VercelModelProviderConfig,
29 VllmModelProviderConfig, XaiModelProviderConfig, YiModelProviderConfig, ZaiModelProviderConfig,
30};
31use super::schema::{
32 AssemblyAiTranscriptionProviderConfig, DeepgramTranscriptionProviderConfig,
33 GoogleTranscriptionProviderConfig, GroqTranscriptionProviderConfig,
34 LocalWhisperTranscriptionProviderConfig, OpenAiTranscriptionProviderConfig,
35};
36use super::schema::{
37 EdgeTtsProviderConfig, ElevenLabsTtsProviderConfig, GoogleTtsProviderConfig,
38 OpenAITtsProviderConfig, PiperTtsProviderConfig, TtsProviderConfig as TtsBaseConfig,
39};
40
41#[macro_export]
61macro_rules! define_provider_ref {
62 ($name:ident, $category_doc:literal) => {
63 #[doc = concat!("Reference to a configured `[", $category_doc, ".<type>.<alias>]` entry.")]
64 #[derive(
68 Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
69 )]
70 #[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
71 #[serde(transparent)]
72 pub struct $name(pub String);
73
74 impl $name {
75 #[must_use]
76 pub fn new(value: impl Into<String>) -> Self {
77 Self(value.into())
78 }
79
80 #[must_use]
81 pub fn as_str(&self) -> &str {
82 &self.0
83 }
84
85 #[must_use]
86 pub fn is_empty(&self) -> bool {
87 self.0.is_empty()
88 }
89
90 #[must_use]
91 pub fn into_inner(self) -> String {
92 self.0
93 }
94 }
95
96 impl std::fmt::Display for $name {
97 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98 std::fmt::Display::fmt(&self.0, f)
99 }
100 }
101
102 impl std::ops::Deref for $name {
103 type Target = str;
104 fn deref(&self) -> &str {
105 &self.0
106 }
107 }
108
109 impl AsRef<str> for $name {
110 fn as_ref(&self) -> &str {
111 &self.0
112 }
113 }
114
115 impl From<String> for $name {
116 fn from(v: String) -> Self {
117 Self(v)
118 }
119 }
120
121 impl From<&str> for $name {
122 fn from(v: &str) -> Self {
123 Self(v.to_string())
124 }
125 }
126
127 impl From<$name> for String {
128 fn from(v: $name) -> Self {
129 v.0
130 }
131 }
132
133 impl PartialEq<str> for $name {
134 fn eq(&self, other: &str) -> bool {
135 self.0 == other
136 }
137 }
138
139 impl PartialEq<&str> for $name {
140 fn eq(&self, other: &&str) -> bool {
141 self.0 == *other
142 }
143 }
144
145 impl PartialEq<String> for $name {
146 fn eq(&self, other: &String) -> bool {
147 &self.0 == other
148 }
149 }
150 };
151}
152
153define_provider_ref!(ModelProviderRef, "providers.models");
154define_provider_ref!(TtsProviderRef, "providers.tts");
155define_provider_ref!(TranscriptionProviderRef, "providers.transcription");
156define_provider_ref!(ChannelRef, "channels");
157
158pub const MAX_FALLBACK_DEPTH: usize = 3;
165
166#[macro_export]
181macro_rules! for_each_model_provider_slot {
182 ($mac:ident) => {
183 $mac! {
184 (openai, "openai", OpenAIModelProviderConfig), (azure, "azure", AzureModelProviderConfig),
185 (anthropic, "anthropic", AnthropicModelProviderConfig), (moonshot, "moonshot", MoonshotModelProviderConfig),
186 (qwen, "qwen", QwenModelProviderConfig),
187 (glm, "glm", GlmModelProviderConfig),
188 (minimax, "minimax", MinimaxModelProviderConfig),
189 (zai, "zai", ZaiModelProviderConfig),
190 (doubao, "doubao", DoubaoModelProviderConfig),
191 (yi, "yi", YiModelProviderConfig),
192 (hunyuan, "hunyuan", HunyuanModelProviderConfig),
193 (qianfan, "qianfan", QianfanModelProviderConfig),
194 (baichuan, "baichuan", BaichuanModelProviderConfig),
195 (openrouter, "openrouter", OpenRouterModelProviderConfig),
196 (ollama, "ollama", OllamaModelProviderConfig),
197 (gemini, "gemini", GeminiModelProviderConfig),
198 (gemini_cli, "gemini_cli", GeminiCliModelProviderConfig),
199 (bedrock, "bedrock", BedrockModelProviderConfig),
200 (telnyx, "telnyx", TelnyxModelProviderConfig),
201 (together, "together", TogetherModelProviderConfig),
202 (fireworks, "fireworks", FireworksModelProviderConfig),
203 (groq, "groq", GroqModelProviderConfig),
204 (mistral, "mistral", MistralModelProviderConfig),
205 (deepseek, "deepseek", DeepseekModelProviderConfig),
206 (atomic_chat, "atomic_chat", AtomicChatModelProviderConfig),
207 (cohere, "cohere", CohereModelProviderConfig),
208 (perplexity, "perplexity", PerplexityModelProviderConfig),
209 (xai, "xai", XaiModelProviderConfig),
210 (cerebras, "cerebras", CerebrasModelProviderConfig),
211 (sambanova, "sambanova", SambanovaModelProviderConfig),
212 (hyperbolic, "hyperbolic", HyperbolicModelProviderConfig),
213 (deepinfra, "deepinfra", DeepinfraModelProviderConfig),
214 (huggingface, "huggingface", HuggingfaceModelProviderConfig),
215 (ai21, "ai21", Ai21ModelProviderConfig),
216 (reka, "reka", RekaModelProviderConfig),
217 (baseten, "baseten", BasetenModelProviderConfig),
218 (nscale, "nscale", NscaleModelProviderConfig),
219 (anyscale, "anyscale", AnyscaleModelProviderConfig),
220 (nebius, "nebius", NebiusModelProviderConfig),
221 (friendli, "friendli", FriendliModelProviderConfig),
222 (stepfun, "stepfun", StepfunModelProviderConfig),
223 (aihubmix, "aihubmix", AihubmixModelProviderConfig),
224 (siliconflow, "siliconflow", SiliconflowModelProviderConfig),
225 (astrai, "astrai", AstraiModelProviderConfig),
226 (avian, "avian", AvianModelProviderConfig),
227 (deepmyst, "deepmyst", DeepmystModelProviderConfig),
228 (venice, "venice", VeniceModelProviderConfig),
229 (novita, "novita", NovitaModelProviderConfig),
230 (nvidia, "nvidia", NvidiaModelProviderConfig),
231 (vercel, "vercel", VercelModelProviderConfig),
232 (cloudflare, "cloudflare", CloudflareModelProviderConfig),
233 (ovh, "ovh", OvhModelProviderConfig),
234 (copilot, "copilot", CopilotModelProviderConfig),
235 (lmstudio, "lmstudio", LmstudioModelProviderConfig),
236 (llamacpp, "llamacpp", LlamacppModelProviderConfig),
237 (sglang, "sglang", SglangModelProviderConfig),
238 (vllm, "vllm", VllmModelProviderConfig),
239 (osaurus, "osaurus", OsaurusModelProviderConfig),
240 (litellm, "litellm", LitellmModelProviderConfig),
241 (lepton, "lepton", LeptonModelProviderConfig),
242 (morph, "morph", MorphModelProviderConfig),
243 (github_models, "github_models", GithubModelsModelProviderConfig),
244 (upstage, "upstage", UpstageModelProviderConfig),
245 (featherless, "featherless", FeatherlessModelProviderConfig),
246 (arcee, "arcee", ArceeModelProviderConfig),
247 (lambda_ai, "lambda_ai", LambdaAiModelProviderConfig),
248 (inception, "inception", InceptionModelProviderConfig),
249 (synthetic, "synthetic", SyntheticModelProviderConfig),
250 (opencode, "opencode", OpencodeModelProviderConfig),
251 (kilocli, "kilocli", KiloCliModelProviderConfig),
252 (kilo, "kilo", KiloModelProviderConfig),
253 (custom, "custom", CustomModelProviderConfig),
254 }
255 };
256}
257
258macro_rules! emit_model_providers_struct {
259 ($(($field:ident, $type_str:literal, $cfg_ty:ty)),+ $(,)?) => {
260 #[derive(Debug, Clone, Default, Serialize, Deserialize, Configurable)]
274 #[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
275 #[prefix = "providers.models"]
276 pub struct ModelProviders {
277 $(
278 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
279 #[nested]
280 pub $field: HashMap<String, $cfg_ty>,
281 )+
282 }
283 };
284}
285for_each_model_provider_slot!(emit_model_providers_struct);
286
287impl ModelProviders {
288 pub fn iter_entries(&self) -> impl Iterator<Item = (&'static str, &str, &ModelProviderConfig)> {
299 let mut out: Vec<(&'static str, &str, &ModelProviderConfig)> = Vec::new();
300 macro_rules! emit_iter {
301 ($(($field:ident, $type_str:literal, $cfg_ty:ty)),+ $(,)?) => {
302 $(
303 for (alias, cfg) in &self.$field {
304 out.push(($type_str, alias.as_str(), &cfg.base));
305 }
306 )+
307 };
308 }
309 for_each_model_provider_slot!(emit_iter);
310 out.into_iter()
311 }
312
313 pub fn iter_entries_mut(
315 &mut self,
316 ) -> impl Iterator<Item = (&'static str, &str, &mut ModelProviderConfig)> {
317 let mut out: Vec<(&'static str, &str, &mut ModelProviderConfig)> = Vec::new();
318 macro_rules! emit_iter_mut {
319 ($(($field:ident, $type_str:literal, $cfg_ty:ty)),+ $(,)?) => {
320 $(
321 for (alias, cfg) in self.$field.iter_mut() {
322 out.push(($type_str, alias.as_str(), &mut cfg.base));
323 }
324 )+
325 };
326 }
327 for_each_model_provider_slot!(emit_iter_mut);
328 out.into_iter()
329 }
330
331 pub fn resolved_endpoint_uri(&self, family: &str, alias: &str) -> Option<&'static str> {
336 use super::schema::FamilyEndpoint;
337 macro_rules! emit_endpoint {
338 ($(($field:ident, $type_str:literal, $cfg_ty:ty)),+ $(,)?) => {
339 match family {
340 $( $type_str => self.$field.get(alias).and_then(|cfg| cfg.endpoint_uri()), )+
341 _ => None,
342 }
343 };
344 }
345 for_each_model_provider_slot!(emit_endpoint)
346 }
347
348 pub fn find(&self, family: &str, alias: &str) -> Option<&ModelProviderConfig> {
352 macro_rules! emit_get {
353 ($(($field:ident, $type_str:literal, $cfg_ty:ty)),+ $(,)?) => {
354 match family {
355 $( $type_str => self.$field.get(alias).map(|cfg| &cfg.base), )+
356 _ => None,
357 }
358 };
359 }
360 for_each_model_provider_slot!(emit_get)
361 }
362
363 pub fn find_by_name(&self, name: &str) -> Option<(&'static str, String, &ModelProviderConfig)> {
369 if let Some((kind, alias)) = name.split_once('.') {
370 macro_rules! emit_dotted {
371 ($(($field:ident, $type_str:literal, $cfg_ty:ty)),+ $(,)?) => {
372 match kind {
373 $( $type_str => self.$field.get(alias).map(|c| ($type_str, alias.to_string(), &c.base)), )+
374 _ => None,
375 }
376 };
377 }
378 return for_each_model_provider_slot!(emit_dotted);
379 }
380 let mut hit: Option<(&'static str, String, &ModelProviderConfig)> = None;
381 macro_rules! emit_bare {
382 ($(($field:ident, $type_str:literal, $cfg_ty:ty)),+ $(,)?) => {
383 $(
384 if let Some(c) = self.$field.get(name) {
385 if hit.is_some() {
386 return None; }
388 hit = Some(($type_str, name.to_string(), &c.base));
389 }
390 )+
391 };
392 }
393 for_each_model_provider_slot!(emit_bare);
394 hit
395 }
396
397 pub fn ensure(&mut self, family: &str, alias: &str) -> Option<&mut ModelProviderConfig> {
403 macro_rules! emit_ensure {
404 ($(($field:ident, $type_str:literal, $cfg_ty:ty)),+ $(,)?) => {
405 match family {
406 $(
407 $type_str => Some(
408 &mut self
409 .$field
410 .entry(alias.to_string())
411 .or_default()
412 .base,
413 ),
414 )+
415 _ => None,
416 }
417 };
418 }
419 for_each_model_provider_slot!(emit_ensure)
420 }
421
422 pub fn contains_model_provider_type(&self, family: &str) -> bool {
425 macro_rules! emit_contains {
426 ($(($field:ident, $type_str:literal, $cfg_ty:ty)),+ $(,)?) => {
427 match family {
428 $( $type_str => !self.$field.is_empty(), )+
429 _ => false,
430 }
431 };
432 }
433 for_each_model_provider_slot!(emit_contains)
434 }
435
436 pub fn aliases_of<'a>(&'a self, family: &str) -> Box<dyn Iterator<Item = &'a str> + 'a> {
439 macro_rules! emit_aliases {
440 ($(($field:ident, $type_str:literal, $cfg_ty:ty)),+ $(,)?) => {
441 match family {
442 $( $type_str => Box::new(self.$field.keys().map(String::as_str)), )+
443 _ => Box::new(std::iter::empty()),
444 }
445 };
446 }
447 for_each_model_provider_slot!(emit_aliases)
448 }
449
450 #[must_use]
455 pub fn slot_names() -> &'static [&'static str] {
456 macro_rules! emit_slot_names {
457 ($(($field:ident, $type_str:literal, $cfg_ty:ty)),+ $(,)?) => {
458 &[$($type_str),+]
459 };
460 }
461 const NAMES: &[&str] = for_each_model_provider_slot!(emit_slot_names);
462 NAMES
463 }
464
465 pub fn remove_alias(&mut self, family: &str, alias: &str) -> bool {
468 macro_rules! emit_remove {
469 ($(($field:ident, $type_str:literal, $cfg_ty:ty)),+ $(,)?) => {
470 match family {
471 $( $type_str => self.$field.remove(alias).is_some(), )+
472 _ => false,
473 }
474 };
475 }
476 for_each_model_provider_slot!(emit_remove)
477 }
478
479 pub fn is_empty(&self) -> bool {
481 macro_rules! emit_is_empty {
482 ($(($field:ident, $type_str:literal, $cfg_ty:ty)),+ $(,)?) => {
483 $( self.$field.is_empty() && )+ true
484 };
485 }
486 for_each_model_provider_slot!(emit_is_empty)
487 }
488
489 pub fn len(&self) -> usize {
491 macro_rules! emit_len {
492 ($(($field:ident, $type_str:literal, $cfg_ty:ty)),+ $(,)?) => {
493 0 $( + self.$field.len() )+
494 };
495 }
496 for_each_model_provider_slot!(emit_len)
497 }
498}
499
500#[derive(Debug, Clone, Default, Serialize, Deserialize, Configurable)]
504#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
505#[prefix = "providers.tts"]
506pub struct TtsProviders {
507 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
508 #[nested]
509 pub openai: HashMap<String, OpenAITtsProviderConfig>,
510 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
511 #[nested]
512 pub elevenlabs: HashMap<String, ElevenLabsTtsProviderConfig>,
513 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
514 #[nested]
515 pub google: HashMap<String, GoogleTtsProviderConfig>,
516 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
517 #[nested]
518 pub edge: HashMap<String, EdgeTtsProviderConfig>,
519 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
520 #[nested]
521 pub piper: HashMap<String, PiperTtsProviderConfig>,
522}
523
524impl TtsProviders {
525 pub fn iter_entries(
528 &self,
529 ) -> Box<dyn Iterator<Item = (&'static str, &str, &TtsBaseConfig)> + '_> {
530 Box::new(
531 std::iter::empty()
532 .chain(
533 self.openai
534 .iter()
535 .map(|(a, c)| ("openai", a.as_str(), &c.base)),
536 )
537 .chain(
538 self.elevenlabs
539 .iter()
540 .map(|(a, c)| ("elevenlabs", a.as_str(), &c.base)),
541 )
542 .chain(
543 self.google
544 .iter()
545 .map(|(a, c)| ("google", a.as_str(), &c.base)),
546 )
547 .chain(self.edge.iter().map(|(a, c)| ("edge", a.as_str(), &c.base)))
548 .chain(
549 self.piper
550 .iter()
551 .map(|(a, c)| ("piper", a.as_str(), &c.base)),
552 ),
553 )
554 }
555
556 pub fn iter_entries_mut(
558 &mut self,
559 ) -> Box<dyn Iterator<Item = (&'static str, &str, &mut TtsBaseConfig)> + '_> {
560 Box::new(
561 std::iter::empty()
562 .chain(
563 self.openai
564 .iter_mut()
565 .map(|(a, c)| ("openai", a.as_str(), &mut c.base)),
566 )
567 .chain(
568 self.elevenlabs
569 .iter_mut()
570 .map(|(a, c)| ("elevenlabs", a.as_str(), &mut c.base)),
571 )
572 .chain(
573 self.google
574 .iter_mut()
575 .map(|(a, c)| ("google", a.as_str(), &mut c.base)),
576 )
577 .chain(
578 self.edge
579 .iter_mut()
580 .map(|(a, c)| ("edge", a.as_str(), &mut c.base)),
581 )
582 .chain(
583 self.piper
584 .iter_mut()
585 .map(|(a, c)| ("piper", a.as_str(), &mut c.base)),
586 ),
587 )
588 }
589
590 pub fn is_empty(&self) -> bool {
592 self.openai.is_empty()
593 && self.elevenlabs.is_empty()
594 && self.google.is_empty()
595 && self.edge.is_empty()
596 && self.piper.is_empty()
597 }
598}
599
600#[derive(Debug, Clone, Default, Serialize, Deserialize, Configurable)]
604#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
605#[prefix = "providers.transcription"]
606pub struct TranscriptionProviders {
607 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
608 #[nested]
609 pub groq: HashMap<String, GroqTranscriptionProviderConfig>,
610 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
611 #[nested]
612 pub openai: HashMap<String, OpenAiTranscriptionProviderConfig>,
613 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
614 #[nested]
615 pub deepgram: HashMap<String, DeepgramTranscriptionProviderConfig>,
616 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
617 #[nested]
618 pub assemblyai: HashMap<String, AssemblyAiTranscriptionProviderConfig>,
619 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
620 #[nested]
621 pub google: HashMap<String, GoogleTranscriptionProviderConfig>,
622 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
623 #[nested]
624 pub local_whisper: HashMap<String, LocalWhisperTranscriptionProviderConfig>,
625}
626
627impl TranscriptionProviders {
628 pub fn is_empty(&self) -> bool {
630 self.groq.is_empty()
631 && self.openai.is_empty()
632 && self.deepgram.is_empty()
633 && self.assemblyai.is_empty()
634 && self.google.is_empty()
635 && self.local_whisper.is_empty()
636 }
637
638 pub fn iter_aliases(&self) -> impl Iterator<Item = (&'static str, &str)> {
640 let mut out: Vec<(&'static str, &str)> = Vec::new();
641 for k in self.groq.keys() {
642 out.push(("groq", k.as_str()));
643 }
644 for k in self.openai.keys() {
645 out.push(("openai", k.as_str()));
646 }
647 for k in self.deepgram.keys() {
648 out.push(("deepgram", k.as_str()));
649 }
650 for k in self.assemblyai.keys() {
651 out.push(("assemblyai", k.as_str()));
652 }
653 for k in self.google.keys() {
654 out.push(("google", k.as_str()));
655 }
656 for k in self.local_whisper.keys() {
657 out.push(("local_whisper", k.as_str()));
658 }
659 out.into_iter()
660 }
661}
662
663#[derive(Debug, Clone, Default, Serialize, Deserialize, Configurable)]
681#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
682#[prefix = "providers"]
683pub struct Providers {
684 #[serde(default)]
686 #[nested]
687 pub models: ModelProviders,
688
689 #[serde(default)]
691 #[nested]
692 pub tts: TtsProviders,
693
694 #[serde(default)]
696 #[nested]
697 pub transcription: TranscriptionProviders,
698}
699
700macro_rules! emit_model_cost_rates_struct {
718 ($(($field:ident, $type_str:literal, $cfg_ty:ty)),+ $(,)?) => {
719 #[derive(Debug, Clone, Default, Serialize, Deserialize, Configurable)]
724 #[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
725 #[prefix = "cost.rates.providers.models"]
726 pub struct ModelCostRatesByProvider {
727 $(
728 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
729 #[nested]
730 #[resource_key]
731 pub $field: HashMap<String, super::schema::ModelCostRates>,
732 )+
733 }
734
735 impl ModelCostRatesByProvider {
736 #[must_use]
738 pub fn get(
739 &self,
740 provider_type: &str,
741 model_id: &str,
742 ) -> Option<&super::schema::ModelCostRates> {
743 match provider_type {
744 $(
745 $type_str => self.$field.get(model_id),
746 )+
747 _ => None,
748 }
749 }
750
751 pub fn iter_entries(
756 &self,
757 ) -> impl Iterator<Item = (&'static str, &str, &super::schema::ModelCostRates)> {
758 let mut out: Vec<(&'static str, &str, &super::schema::ModelCostRates)> = Vec::new();
759 $(
760 for (model_id, rates) in &self.$field {
761 out.push(($type_str, model_id.as_str(), rates));
762 }
763 )+
764 out.into_iter()
765 }
766
767 pub fn is_empty(&self) -> bool {
769 $(self.$field.is_empty())&&+
770 }
771 }
772 };
773}
774for_each_model_provider_slot!(emit_model_cost_rates_struct);
775
776#[macro_export]
780macro_rules! for_each_tts_provider_slot {
781 ($mac:ident, $rate_ty:ty) => {
782 $mac! {
783 $rate_ty,
784 (openai, "openai"),
785 (elevenlabs, "elevenlabs"),
786 (google, "google"),
787 (edge, "edge"),
788 (piper, "piper"),
789 }
790 };
791}
792
793#[macro_export]
795macro_rules! for_each_transcription_provider_slot {
796 ($mac:ident, $rate_ty:ty) => {
797 $mac! {
798 $rate_ty,
799 (groq, "groq"),
800 (openai, "openai"),
801 (deepgram, "deepgram"),
802 (assemblyai, "assemblyai"),
803 (google, "google"),
804 (local_whisper, "local_whisper"),
805 }
806 };
807}
808
809macro_rules! collect_rate_slot_names {
814 ($rate_ty:ty, $(($field:ident, $type_str:literal)),+ $(,)?) => {
815 &[$($type_str),+]
816 };
817}
818
819impl TtsProviders {
820 #[must_use]
823 pub fn slot_names() -> &'static [&'static str] {
824 const NAMES: &[&str] = for_each_tts_provider_slot!(collect_rate_slot_names, ());
825 NAMES
826 }
827}
828
829impl TranscriptionProviders {
830 #[must_use]
833 pub fn slot_names() -> &'static [&'static str] {
834 const NAMES: &[&str] = for_each_transcription_provider_slot!(collect_rate_slot_names, ());
835 NAMES
836 }
837}
838
839macro_rules! emit_simple_cost_rates_struct {
844 (
845 $struct_name:ident,
846 $rate_ty:ty,
847 $prefix:literal,
848 $resource_doc:literal,
849 $(($field:ident, $type_str:literal)),+ $(,)?
850 ) => {
851 #[doc = concat!("`", $prefix, ".<type>.<", $resource_doc, ">`")]
852 #[derive(Debug, Clone, Default, Serialize, Deserialize, Configurable)]
854 #[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
855 #[prefix = $prefix]
856 pub struct $struct_name {
857 $(
858 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
859 #[nested]
860 #[resource_key]
861 pub $field: HashMap<String, $rate_ty>,
862 )+
863 }
864
865 impl $struct_name {
866 #[must_use]
868 pub fn get(&self, provider_type: &str, resource_id: &str) -> Option<&$rate_ty> {
869 match provider_type {
870 $($type_str => self.$field.get(resource_id),)+
871 _ => None,
872 }
873 }
874
875 pub fn iter_entries(
878 &self,
879 ) -> impl Iterator<Item = (&'static str, &str, &$rate_ty)> {
880 let mut out: Vec<(&'static str, &str, &$rate_ty)> = Vec::new();
881 $(
882 for (resource_id, rates) in &self.$field {
883 out.push(($type_str, resource_id.as_str(), rates));
884 }
885 )+
886 out.into_iter()
887 }
888
889 pub fn is_empty(&self) -> bool {
891 $(self.$field.is_empty())&&+
892 }
893 }
894 };
895}
896
897macro_rules! emit_tts_cost_rates_struct {
898 ($rate_ty:ty, $($slot:tt),+ $(,)?) => {
899 emit_simple_cost_rates_struct! {
900 TtsCostRatesByProvider,
901 $rate_ty,
902 "cost.rates.providers.tts",
903 "voice",
904 $($slot),+
905 }
906 };
907}
908for_each_tts_provider_slot!(emit_tts_cost_rates_struct, super::schema::TtsCostRates);
909
910macro_rules! emit_transcription_cost_rates_struct {
911 ($rate_ty:ty, $($slot:tt),+ $(,)?) => {
912 emit_simple_cost_rates_struct! {
913 TranscriptionCostRatesByProvider,
914 $rate_ty,
915 "cost.rates.providers.transcription",
916 "model",
917 $($slot),+
918 }
919 };
920}
921for_each_transcription_provider_slot!(
922 emit_transcription_cost_rates_struct,
923 super::schema::TranscriptionCostRates
924);