1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3use zeroclaw_macros::Configurable;
4
5use super::schema::{
6 Ai21ModelProviderConfig, AihubmixModelProviderConfig, AnthropicModelProviderConfig,
7 AnyscaleModelProviderConfig, AstraiModelProviderConfig, AtomicChatModelProviderConfig,
8 AvianModelProviderConfig, AzureModelProviderConfig, BaichuanModelProviderConfig,
9 BasetenModelProviderConfig, BedrockModelProviderConfig, CerebrasModelProviderConfig,
10 CloudflareModelProviderConfig, CohereModelProviderConfig, CopilotModelProviderConfig,
11 CustomModelProviderConfig, DeepinfraModelProviderConfig, DeepmystModelProviderConfig,
12 DeepseekModelProviderConfig, DoubaoModelProviderConfig, FireworksModelProviderConfig,
13 FriendliModelProviderConfig, GeminiCliModelProviderConfig, GeminiModelProviderConfig,
14 GlmModelProviderConfig, GroqModelProviderConfig, HuggingfaceModelProviderConfig,
15 HunyuanModelProviderConfig, HyperbolicModelProviderConfig, KiloCliModelProviderConfig,
16 LeptonModelProviderConfig, LitellmModelProviderConfig, LlamacppModelProviderConfig,
17 LmstudioModelProviderConfig, MinimaxModelProviderConfig, MistralModelProviderConfig,
18 ModelProviderConfig, MoonshotModelProviderConfig, NebiusModelProviderConfig,
19 NovitaModelProviderConfig, NscaleModelProviderConfig, NvidiaModelProviderConfig,
20 OllamaModelProviderConfig, OpenAIModelProviderConfig, OpenRouterModelProviderConfig,
21 OpencodeModelProviderConfig, OsaurusModelProviderConfig, OvhModelProviderConfig,
22 PerplexityModelProviderConfig, QianfanModelProviderConfig, QwenModelProviderConfig,
23 RekaModelProviderConfig, SambanovaModelProviderConfig, SglangModelProviderConfig,
24 SiliconflowModelProviderConfig, StepfunModelProviderConfig, SyntheticModelProviderConfig,
25 TelnyxModelProviderConfig, TogetherModelProviderConfig, VeniceModelProviderConfig,
26 VercelModelProviderConfig, VllmModelProviderConfig, XaiModelProviderConfig,
27 YiModelProviderConfig, ZaiModelProviderConfig,
28};
29use super::schema::{
30 AssemblyAiTranscriptionProviderConfig, DeepgramTranscriptionProviderConfig,
31 GoogleTranscriptionProviderConfig, GroqTranscriptionProviderConfig,
32 LocalWhisperTranscriptionProviderConfig, OpenAiTranscriptionProviderConfig,
33};
34use super::schema::{
35 EdgeTtsProviderConfig, ElevenLabsTtsProviderConfig, GoogleTtsProviderConfig,
36 OpenAITtsProviderConfig, PiperTtsProviderConfig, TtsProviderConfig as TtsBaseConfig,
37};
38
39#[macro_export]
59macro_rules! define_provider_ref {
60 ($name:ident, $category_doc:literal) => {
61 #[doc = concat!("Reference to a configured `[", $category_doc, ".<type>.<alias>]` entry.")]
62 #[derive(
66 Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
67 )]
68 #[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
69 #[serde(transparent)]
70 pub struct $name(pub String);
71
72 impl $name {
73 #[must_use]
74 pub fn new(value: impl Into<String>) -> Self {
75 Self(value.into())
76 }
77
78 #[must_use]
79 pub fn as_str(&self) -> &str {
80 &self.0
81 }
82
83 #[must_use]
84 pub fn is_empty(&self) -> bool {
85 self.0.is_empty()
86 }
87
88 #[must_use]
89 pub fn into_inner(self) -> String {
90 self.0
91 }
92 }
93
94 impl std::fmt::Display for $name {
95 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96 std::fmt::Display::fmt(&self.0, f)
97 }
98 }
99
100 impl std::ops::Deref for $name {
101 type Target = str;
102 fn deref(&self) -> &str {
103 &self.0
104 }
105 }
106
107 impl AsRef<str> for $name {
108 fn as_ref(&self) -> &str {
109 &self.0
110 }
111 }
112
113 impl From<String> for $name {
114 fn from(v: String) -> Self {
115 Self(v)
116 }
117 }
118
119 impl From<&str> for $name {
120 fn from(v: &str) -> Self {
121 Self(v.to_string())
122 }
123 }
124
125 impl From<$name> for String {
126 fn from(v: $name) -> Self {
127 v.0
128 }
129 }
130
131 impl PartialEq<str> for $name {
132 fn eq(&self, other: &str) -> bool {
133 self.0 == other
134 }
135 }
136
137 impl PartialEq<&str> for $name {
138 fn eq(&self, other: &&str) -> bool {
139 self.0 == *other
140 }
141 }
142
143 impl PartialEq<String> for $name {
144 fn eq(&self, other: &String) -> bool {
145 &self.0 == other
146 }
147 }
148 };
149}
150
151define_provider_ref!(ModelProviderRef, "providers.models");
152define_provider_ref!(TtsProviderRef, "providers.tts");
153define_provider_ref!(TranscriptionProviderRef, "providers.transcription");
154define_provider_ref!(ChannelRef, "channels");
155
156#[macro_export]
171macro_rules! for_each_model_provider_slot {
172 ($mac:ident) => {
173 $mac! {
174 (openai, "openai", OpenAIModelProviderConfig), (azure, "azure", AzureModelProviderConfig),
175 (anthropic, "anthropic", AnthropicModelProviderConfig), (moonshot, "moonshot", MoonshotModelProviderConfig),
176 (qwen, "qwen", QwenModelProviderConfig),
177 (glm, "glm", GlmModelProviderConfig),
178 (minimax, "minimax", MinimaxModelProviderConfig),
179 (zai, "zai", ZaiModelProviderConfig),
180 (doubao, "doubao", DoubaoModelProviderConfig),
181 (yi, "yi", YiModelProviderConfig),
182 (hunyuan, "hunyuan", HunyuanModelProviderConfig),
183 (qianfan, "qianfan", QianfanModelProviderConfig),
184 (baichuan, "baichuan", BaichuanModelProviderConfig),
185 (openrouter, "openrouter", OpenRouterModelProviderConfig),
186 (ollama, "ollama", OllamaModelProviderConfig),
187 (gemini, "gemini", GeminiModelProviderConfig),
188 (gemini_cli, "gemini_cli", GeminiCliModelProviderConfig),
189 (bedrock, "bedrock", BedrockModelProviderConfig),
190 (telnyx, "telnyx", TelnyxModelProviderConfig),
191 (together, "together", TogetherModelProviderConfig),
192 (fireworks, "fireworks", FireworksModelProviderConfig),
193 (groq, "groq", GroqModelProviderConfig),
194 (mistral, "mistral", MistralModelProviderConfig),
195 (deepseek, "deepseek", DeepseekModelProviderConfig),
196 (atomic_chat, "atomic_chat", AtomicChatModelProviderConfig),
197 (cohere, "cohere", CohereModelProviderConfig),
198 (perplexity, "perplexity", PerplexityModelProviderConfig),
199 (xai, "xai", XaiModelProviderConfig),
200 (cerebras, "cerebras", CerebrasModelProviderConfig),
201 (sambanova, "sambanova", SambanovaModelProviderConfig),
202 (hyperbolic, "hyperbolic", HyperbolicModelProviderConfig),
203 (deepinfra, "deepinfra", DeepinfraModelProviderConfig),
204 (huggingface, "huggingface", HuggingfaceModelProviderConfig),
205 (ai21, "ai21", Ai21ModelProviderConfig),
206 (reka, "reka", RekaModelProviderConfig),
207 (baseten, "baseten", BasetenModelProviderConfig),
208 (nscale, "nscale", NscaleModelProviderConfig),
209 (anyscale, "anyscale", AnyscaleModelProviderConfig),
210 (nebius, "nebius", NebiusModelProviderConfig),
211 (friendli, "friendli", FriendliModelProviderConfig),
212 (stepfun, "stepfun", StepfunModelProviderConfig),
213 (aihubmix, "aihubmix", AihubmixModelProviderConfig),
214 (siliconflow, "siliconflow", SiliconflowModelProviderConfig),
215 (astrai, "astrai", AstraiModelProviderConfig),
216 (avian, "avian", AvianModelProviderConfig),
217 (deepmyst, "deepmyst", DeepmystModelProviderConfig),
218 (venice, "venice", VeniceModelProviderConfig),
219 (novita, "novita", NovitaModelProviderConfig),
220 (nvidia, "nvidia", NvidiaModelProviderConfig),
221 (vercel, "vercel", VercelModelProviderConfig),
222 (cloudflare, "cloudflare", CloudflareModelProviderConfig),
223 (ovh, "ovh", OvhModelProviderConfig),
224 (copilot, "copilot", CopilotModelProviderConfig),
225 (lmstudio, "lmstudio", LmstudioModelProviderConfig),
226 (llamacpp, "llamacpp", LlamacppModelProviderConfig),
227 (sglang, "sglang", SglangModelProviderConfig),
228 (vllm, "vllm", VllmModelProviderConfig),
229 (osaurus, "osaurus", OsaurusModelProviderConfig),
230 (litellm, "litellm", LitellmModelProviderConfig),
231 (lepton, "lepton", LeptonModelProviderConfig),
232 (synthetic, "synthetic", SyntheticModelProviderConfig),
233 (opencode, "opencode", OpencodeModelProviderConfig), (kilocli, "kilocli", KiloCliModelProviderConfig),
234 (custom, "custom", CustomModelProviderConfig),
235 }
236 };
237}
238
239macro_rules! emit_model_providers_struct {
240 ($(($field:ident, $type_str:literal, $cfg_ty:ty)),+ $(,)?) => {
241 #[derive(Debug, Clone, Default, Serialize, Deserialize, Configurable)]
255 #[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
256 #[prefix = "providers.models"]
257 pub struct ModelProviders {
258 $(
259 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
260 #[nested]
261 pub $field: HashMap<String, $cfg_ty>,
262 )+
263 }
264 };
265}
266for_each_model_provider_slot!(emit_model_providers_struct);
267
268impl ModelProviders {
269 pub fn iter_entries(&self) -> impl Iterator<Item = (&'static str, &str, &ModelProviderConfig)> {
280 let mut out: Vec<(&'static str, &str, &ModelProviderConfig)> = Vec::new();
281 macro_rules! emit_iter {
282 ($(($field:ident, $type_str:literal, $cfg_ty:ty)),+ $(,)?) => {
283 $(
284 for (alias, cfg) in &self.$field {
285 out.push(($type_str, alias.as_str(), &cfg.base));
286 }
287 )+
288 };
289 }
290 for_each_model_provider_slot!(emit_iter);
291 out.into_iter()
292 }
293
294 pub fn iter_entries_mut(
296 &mut self,
297 ) -> impl Iterator<Item = (&'static str, &str, &mut ModelProviderConfig)> {
298 let mut out: Vec<(&'static str, &str, &mut ModelProviderConfig)> = Vec::new();
299 macro_rules! emit_iter_mut {
300 ($(($field:ident, $type_str:literal, $cfg_ty:ty)),+ $(,)?) => {
301 $(
302 for (alias, cfg) in self.$field.iter_mut() {
303 out.push(($type_str, alias.as_str(), &mut cfg.base));
304 }
305 )+
306 };
307 }
308 for_each_model_provider_slot!(emit_iter_mut);
309 out.into_iter()
310 }
311
312 pub fn resolved_endpoint_uri(&self, family: &str, alias: &str) -> Option<&'static str> {
317 use super::schema::FamilyEndpoint;
318 macro_rules! emit_endpoint {
319 ($(($field:ident, $type_str:literal, $cfg_ty:ty)),+ $(,)?) => {
320 match family {
321 $( $type_str => self.$field.get(alias).and_then(|cfg| cfg.endpoint_uri()), )+
322 _ => None,
323 }
324 };
325 }
326 for_each_model_provider_slot!(emit_endpoint)
327 }
328
329 pub fn find(&self, family: &str, alias: &str) -> Option<&ModelProviderConfig> {
333 macro_rules! emit_get {
334 ($(($field:ident, $type_str:literal, $cfg_ty:ty)),+ $(,)?) => {
335 match family {
336 $( $type_str => self.$field.get(alias).map(|cfg| &cfg.base), )+
337 _ => None,
338 }
339 };
340 }
341 for_each_model_provider_slot!(emit_get)
342 }
343
344 pub fn ensure(&mut self, family: &str, alias: &str) -> Option<&mut ModelProviderConfig> {
350 macro_rules! emit_ensure {
351 ($(($field:ident, $type_str:literal, $cfg_ty:ty)),+ $(,)?) => {
352 match family {
353 $(
354 $type_str => Some(
355 &mut self
356 .$field
357 .entry(alias.to_string())
358 .or_default()
359 .base,
360 ),
361 )+
362 _ => None,
363 }
364 };
365 }
366 for_each_model_provider_slot!(emit_ensure)
367 }
368
369 pub fn contains_model_provider_type(&self, family: &str) -> bool {
372 macro_rules! emit_contains {
373 ($(($field:ident, $type_str:literal, $cfg_ty:ty)),+ $(,)?) => {
374 match family {
375 $( $type_str => !self.$field.is_empty(), )+
376 _ => false,
377 }
378 };
379 }
380 for_each_model_provider_slot!(emit_contains)
381 }
382
383 pub fn aliases_of<'a>(&'a self, family: &str) -> Box<dyn Iterator<Item = &'a str> + 'a> {
386 macro_rules! emit_aliases {
387 ($(($field:ident, $type_str:literal, $cfg_ty:ty)),+ $(,)?) => {
388 match family {
389 $( $type_str => Box::new(self.$field.keys().map(String::as_str)), )+
390 _ => Box::new(std::iter::empty()),
391 }
392 };
393 }
394 for_each_model_provider_slot!(emit_aliases)
395 }
396
397 pub fn remove_alias(&mut self, family: &str, alias: &str) -> bool {
400 macro_rules! emit_remove {
401 ($(($field:ident, $type_str:literal, $cfg_ty:ty)),+ $(,)?) => {
402 match family {
403 $( $type_str => self.$field.remove(alias).is_some(), )+
404 _ => false,
405 }
406 };
407 }
408 for_each_model_provider_slot!(emit_remove)
409 }
410
411 pub fn is_empty(&self) -> bool {
413 macro_rules! emit_is_empty {
414 ($(($field:ident, $type_str:literal, $cfg_ty:ty)),+ $(,)?) => {
415 $( self.$field.is_empty() && )+ true
416 };
417 }
418 for_each_model_provider_slot!(emit_is_empty)
419 }
420
421 pub fn len(&self) -> usize {
423 macro_rules! emit_len {
424 ($(($field:ident, $type_str:literal, $cfg_ty:ty)),+ $(,)?) => {
425 0 $( + self.$field.len() )+
426 };
427 }
428 for_each_model_provider_slot!(emit_len)
429 }
430}
431
432#[derive(Debug, Clone, Default, Serialize, Deserialize, Configurable)]
436#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
437#[prefix = "providers.tts"]
438pub struct TtsProviders {
439 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
440 #[nested]
441 pub openai: HashMap<String, OpenAITtsProviderConfig>,
442 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
443 #[nested]
444 pub elevenlabs: HashMap<String, ElevenLabsTtsProviderConfig>,
445 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
446 #[nested]
447 pub google: HashMap<String, GoogleTtsProviderConfig>,
448 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
449 #[nested]
450 pub edge: HashMap<String, EdgeTtsProviderConfig>,
451 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
452 #[nested]
453 pub piper: HashMap<String, PiperTtsProviderConfig>,
454}
455
456impl TtsProviders {
457 pub fn iter_entries(
460 &self,
461 ) -> Box<dyn Iterator<Item = (&'static str, &str, &TtsBaseConfig)> + '_> {
462 Box::new(
463 std::iter::empty()
464 .chain(
465 self.openai
466 .iter()
467 .map(|(a, c)| ("openai", a.as_str(), &c.base)),
468 )
469 .chain(
470 self.elevenlabs
471 .iter()
472 .map(|(a, c)| ("elevenlabs", a.as_str(), &c.base)),
473 )
474 .chain(
475 self.google
476 .iter()
477 .map(|(a, c)| ("google", a.as_str(), &c.base)),
478 )
479 .chain(self.edge.iter().map(|(a, c)| ("edge", a.as_str(), &c.base)))
480 .chain(
481 self.piper
482 .iter()
483 .map(|(a, c)| ("piper", a.as_str(), &c.base)),
484 ),
485 )
486 }
487
488 pub fn iter_entries_mut(
490 &mut self,
491 ) -> Box<dyn Iterator<Item = (&'static str, &str, &mut TtsBaseConfig)> + '_> {
492 Box::new(
493 std::iter::empty()
494 .chain(
495 self.openai
496 .iter_mut()
497 .map(|(a, c)| ("openai", a.as_str(), &mut c.base)),
498 )
499 .chain(
500 self.elevenlabs
501 .iter_mut()
502 .map(|(a, c)| ("elevenlabs", a.as_str(), &mut c.base)),
503 )
504 .chain(
505 self.google
506 .iter_mut()
507 .map(|(a, c)| ("google", a.as_str(), &mut c.base)),
508 )
509 .chain(
510 self.edge
511 .iter_mut()
512 .map(|(a, c)| ("edge", a.as_str(), &mut c.base)),
513 )
514 .chain(
515 self.piper
516 .iter_mut()
517 .map(|(a, c)| ("piper", a.as_str(), &mut c.base)),
518 ),
519 )
520 }
521
522 pub fn is_empty(&self) -> bool {
524 self.openai.is_empty()
525 && self.elevenlabs.is_empty()
526 && self.google.is_empty()
527 && self.edge.is_empty()
528 && self.piper.is_empty()
529 }
530}
531
532#[derive(Debug, Clone, Default, Serialize, Deserialize, Configurable)]
536#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
537#[prefix = "providers.transcription"]
538pub struct TranscriptionProviders {
539 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
540 #[nested]
541 pub groq: HashMap<String, GroqTranscriptionProviderConfig>,
542 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
543 #[nested]
544 pub openai: HashMap<String, OpenAiTranscriptionProviderConfig>,
545 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
546 #[nested]
547 pub deepgram: HashMap<String, DeepgramTranscriptionProviderConfig>,
548 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
549 #[nested]
550 pub assemblyai: HashMap<String, AssemblyAiTranscriptionProviderConfig>,
551 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
552 #[nested]
553 pub google: HashMap<String, GoogleTranscriptionProviderConfig>,
554 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
555 #[nested]
556 pub local_whisper: HashMap<String, LocalWhisperTranscriptionProviderConfig>,
557}
558
559impl TranscriptionProviders {
560 pub fn is_empty(&self) -> bool {
562 self.groq.is_empty()
563 && self.openai.is_empty()
564 && self.deepgram.is_empty()
565 && self.assemblyai.is_empty()
566 && self.google.is_empty()
567 && self.local_whisper.is_empty()
568 }
569
570 pub fn iter_aliases(&self) -> impl Iterator<Item = (&'static str, &str)> {
572 let mut out: Vec<(&'static str, &str)> = Vec::new();
573 for k in self.groq.keys() {
574 out.push(("groq", k.as_str()));
575 }
576 for k in self.openai.keys() {
577 out.push(("openai", k.as_str()));
578 }
579 for k in self.deepgram.keys() {
580 out.push(("deepgram", k.as_str()));
581 }
582 for k in self.assemblyai.keys() {
583 out.push(("assemblyai", k.as_str()));
584 }
585 for k in self.google.keys() {
586 out.push(("google", k.as_str()));
587 }
588 for k in self.local_whisper.keys() {
589 out.push(("local_whisper", k.as_str()));
590 }
591 out.into_iter()
592 }
593}
594
595#[derive(Debug, Clone, Default, Serialize, Deserialize, Configurable)]
613#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
614#[prefix = "providers"]
615pub struct Providers {
616 #[serde(default)]
618 #[nested]
619 pub models: ModelProviders,
620
621 #[serde(default)]
623 #[nested]
624 pub tts: TtsProviders,
625
626 #[serde(default)]
628 #[nested]
629 pub transcription: TranscriptionProviders,
630}
631
632macro_rules! emit_model_cost_rates_struct {
650 ($(($field:ident, $type_str:literal, $cfg_ty:ty)),+ $(,)?) => {
651 #[derive(Debug, Clone, Default, Serialize, Deserialize, Configurable)]
656 #[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
657 #[prefix = "cost.rates.providers.models"]
658 pub struct ModelCostRatesByProvider {
659 $(
660 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
661 #[nested]
662 #[resource_key]
663 pub $field: HashMap<String, super::schema::ModelCostRates>,
664 )+
665 }
666
667 impl ModelCostRatesByProvider {
668 #[must_use]
670 pub fn get(
671 &self,
672 provider_type: &str,
673 model_id: &str,
674 ) -> Option<&super::schema::ModelCostRates> {
675 match provider_type {
676 $(
677 $type_str => self.$field.get(model_id),
678 )+
679 _ => None,
680 }
681 }
682
683 pub fn iter_entries(
688 &self,
689 ) -> impl Iterator<Item = (&'static str, &str, &super::schema::ModelCostRates)> {
690 let mut out: Vec<(&'static str, &str, &super::schema::ModelCostRates)> = Vec::new();
691 $(
692 for (model_id, rates) in &self.$field {
693 out.push(($type_str, model_id.as_str(), rates));
694 }
695 )+
696 out.into_iter()
697 }
698
699 pub fn is_empty(&self) -> bool {
701 $(self.$field.is_empty())&&+
702 }
703 }
704 };
705}
706for_each_model_provider_slot!(emit_model_cost_rates_struct);
707
708#[macro_export]
712macro_rules! for_each_tts_provider_slot {
713 ($mac:ident, $rate_ty:ty) => {
714 $mac! {
715 $rate_ty,
716 (openai, "openai"),
717 (elevenlabs, "elevenlabs"),
718 (google, "google"),
719 (edge, "edge"),
720 (piper, "piper"),
721 }
722 };
723}
724
725#[macro_export]
727macro_rules! for_each_transcription_provider_slot {
728 ($mac:ident, $rate_ty:ty) => {
729 $mac! {
730 $rate_ty,
731 (groq, "groq"),
732 (openai, "openai"),
733 (deepgram, "deepgram"),
734 (assemblyai, "assemblyai"),
735 (google, "google"),
736 (local_whisper, "local_whisper"),
737 }
738 };
739}
740
741macro_rules! emit_simple_cost_rates_struct {
746 (
747 $struct_name:ident,
748 $rate_ty:ty,
749 $prefix:literal,
750 $resource_doc:literal,
751 $(($field:ident, $type_str:literal)),+ $(,)?
752 ) => {
753 #[doc = concat!("`", $prefix, ".<type>.<", $resource_doc, ">`")]
754 #[derive(Debug, Clone, Default, Serialize, Deserialize, Configurable)]
756 #[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
757 #[prefix = $prefix]
758 pub struct $struct_name {
759 $(
760 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
761 #[nested]
762 #[resource_key]
763 pub $field: HashMap<String, $rate_ty>,
764 )+
765 }
766
767 impl $struct_name {
768 #[must_use]
770 pub fn get(&self, provider_type: &str, resource_id: &str) -> Option<&$rate_ty> {
771 match provider_type {
772 $($type_str => self.$field.get(resource_id),)+
773 _ => None,
774 }
775 }
776
777 pub fn iter_entries(
780 &self,
781 ) -> impl Iterator<Item = (&'static str, &str, &$rate_ty)> {
782 let mut out: Vec<(&'static str, &str, &$rate_ty)> = Vec::new();
783 $(
784 for (resource_id, rates) in &self.$field {
785 out.push(($type_str, resource_id.as_str(), rates));
786 }
787 )+
788 out.into_iter()
789 }
790
791 pub fn is_empty(&self) -> bool {
793 $(self.$field.is_empty())&&+
794 }
795 }
796 };
797}
798
799macro_rules! emit_tts_cost_rates_struct {
800 ($rate_ty:ty, $($slot:tt),+ $(,)?) => {
801 emit_simple_cost_rates_struct! {
802 TtsCostRatesByProvider,
803 $rate_ty,
804 "cost.rates.providers.tts",
805 "voice",
806 $($slot),+
807 }
808 };
809}
810for_each_tts_provider_slot!(emit_tts_cost_rates_struct, super::schema::TtsCostRates);
811
812macro_rules! emit_transcription_cost_rates_struct {
813 ($rate_ty:ty, $($slot:tt),+ $(,)?) => {
814 emit_simple_cost_rates_struct! {
815 TranscriptionCostRatesByProvider,
816 $rate_ty,
817 "cost.rates.providers.transcription",
818 "model",
819 $($slot),+
820 }
821 };
822}
823for_each_transcription_provider_slot!(
824 emit_transcription_cost_rates_struct,
825 super::schema::TranscriptionCostRates
826);