1use std::collections::HashMap;
2
3use serde::{Deserialize, Serialize};
4
5use crate::client::Client;
6use crate::error::Result;
7
8#[derive(Debug, Clone, Serialize, Default)]
10pub struct TextToSpeechRequest {
11 pub model: String,
13
14 pub text: String,
16
17 #[serde(skip_serializing_if = "Option::is_none")]
19 pub voice: Option<String>,
20
21 #[serde(rename = "format", skip_serializing_if = "Option::is_none")]
23 pub output_format: Option<String>,
24
25 #[serde(skip_serializing_if = "Option::is_none")]
27 pub speed: Option<f64>,
28}
29
30pub type TtsRequest = TextToSpeechRequest;
32
33#[derive(Debug, Clone, Deserialize)]
35pub struct TextToSpeechResponse {
36 pub audio_base64: String,
38
39 pub format: String,
41
42 pub size_bytes: i64,
44
45 pub model: String,
47
48 #[serde(default)]
50 pub cost_ticks: i64,
51
52 #[serde(default)]
54 pub request_id: String,
55}
56
57pub type TtsResponse = TextToSpeechResponse;
59
60#[derive(Debug, Clone, Serialize, Default)]
62pub struct SpeechToTextRequest {
63 pub model: String,
65
66 pub audio_base64: String,
68
69 #[serde(skip_serializing_if = "Option::is_none")]
71 pub filename: Option<String>,
72
73 #[serde(skip_serializing_if = "Option::is_none")]
75 pub language: Option<String>,
76}
77
78pub type SttRequest = SpeechToTextRequest;
80
81#[derive(Debug, Clone, Deserialize)]
83pub struct SpeechToTextResponse {
84 pub text: String,
86
87 pub model: String,
89
90 #[serde(default)]
92 pub cost_ticks: i64,
93
94 #[serde(default)]
96 pub request_id: String,
97}
98
99pub type SttResponse = SpeechToTextResponse;
101
102#[derive(Debug, Clone, Serialize, Default)]
104pub struct MusicRequest {
105 pub model: String,
107
108 pub prompt: String,
110
111 #[serde(skip_serializing_if = "Option::is_none")]
113 pub duration_seconds: Option<i32>,
114}
115
116#[derive(Debug, Clone, Deserialize)]
118pub struct MusicResponse {
119 #[serde(default)]
121 pub audio_clips: Vec<MusicClip>,
122
123 #[serde(default)]
125 pub model: String,
126
127 #[serde(default)]
129 pub cost_ticks: i64,
130
131 #[serde(default)]
133 pub request_id: String,
134}
135
136#[derive(Debug, Clone, Deserialize)]
138pub struct MusicClip {
139 pub base64: String,
141
142 #[serde(default)]
144 pub format: String,
145
146 #[serde(default)]
148 pub size_bytes: i64,
149
150 #[serde(default)]
152 pub index: i32,
153}
154
155#[derive(Debug, Clone, Serialize, Default)]
157pub struct SoundEffectRequest {
158 pub prompt: String,
160
161 #[serde(skip_serializing_if = "Option::is_none")]
163 pub duration_seconds: Option<f64>,
164}
165
166#[derive(Debug, Clone, Deserialize)]
168pub struct SoundEffectResponse {
169 pub audio_base64: String,
171
172 pub format: String,
174
175 #[serde(default)]
177 pub size_bytes: i64,
178
179 #[serde(default)]
181 pub model: String,
182
183 #[serde(default)]
185 pub cost_ticks: i64,
186
187 #[serde(default)]
189 pub request_id: String,
190}
191
192#[derive(Debug, Clone, Deserialize)]
198pub struct AudioResponse {
199 #[serde(default)]
201 pub audio_base64: Option<String>,
202
203 #[serde(default)]
205 pub format: Option<String>,
206
207 #[serde(default)]
209 pub size_bytes: Option<i64>,
210
211 #[serde(default)]
213 pub model: Option<String>,
214
215 #[serde(default)]
217 pub cost_ticks: i64,
218
219 #[serde(default)]
221 pub request_id: String,
222
223 #[serde(flatten)]
225 pub extra: HashMap<String, serde_json::Value>,
226}
227
228#[derive(Debug, Clone, Serialize, Deserialize, Default)]
230pub struct DialogueTurn {
231 pub speaker: String,
233
234 pub text: String,
236
237 #[serde(skip_serializing_if = "Option::is_none")]
239 pub voice: Option<String>,
240}
241
242#[derive(Debug, Clone, Serialize)]
244pub struct DialogueVoice {
245 pub voice_id: String,
246 pub name: String,
247}
248
249#[derive(Debug, Clone, Serialize, Default)]
252pub struct DialogueRequest {
253 pub text: String,
255
256 pub voices: Vec<DialogueVoice>,
258
259 #[serde(skip_serializing_if = "Option::is_none")]
261 pub model: Option<String>,
262
263 #[serde(rename = "output_format", skip_serializing_if = "Option::is_none")]
265 pub output_format: Option<String>,
266
267 #[serde(skip_serializing_if = "Option::is_none")]
269 pub seed: Option<i32>,
270}
271
272impl DialogueRequest {
273 pub fn from_turns(turns: Vec<DialogueTurn>, model: Option<String>) -> Self {
276 let text = turns.iter()
278 .map(|t| format!("{}: {}", t.speaker, t.text))
279 .collect::<Vec<_>>()
280 .join("\n");
281
282 let mut seen = std::collections::HashSet::new();
284 let voices: Vec<DialogueVoice> = turns.iter()
285 .filter(|t| t.voice.is_some() && seen.insert(t.speaker.clone()))
286 .map(|t| DialogueVoice {
287 voice_id: t.voice.clone().unwrap_or_default(),
288 name: t.speaker.clone(),
289 })
290 .collect();
291
292 Self {
293 text,
294 voices,
295 model,
296 ..Default::default()
297 }
298 }
299}
300
301#[derive(Debug, Clone, Serialize, Default)]
303pub struct SpeechToSpeechRequest {
304 #[serde(skip_serializing_if = "Option::is_none")]
306 pub model: Option<String>,
307
308 pub audio_base64: String,
310
311 #[serde(skip_serializing_if = "Option::is_none")]
313 pub voice: Option<String>,
314
315 #[serde(rename = "format", skip_serializing_if = "Option::is_none")]
317 pub output_format: Option<String>,
318}
319
320#[derive(Debug, Clone, Serialize, Default)]
322pub struct IsolateVoiceRequest {
323 pub audio_base64: String,
325
326 #[serde(rename = "format", skip_serializing_if = "Option::is_none")]
328 pub output_format: Option<String>,
329}
330
331pub type IsolateRequest = IsolateVoiceRequest;
333
334#[derive(Debug, Clone, Serialize, Default)]
336pub struct RemixVoiceRequest {
337 pub audio_base64: String,
339
340 #[serde(skip_serializing_if = "Option::is_none")]
342 pub voice: Option<String>,
343
344 #[serde(skip_serializing_if = "Option::is_none")]
346 pub model: Option<String>,
347
348 #[serde(rename = "format", skip_serializing_if = "Option::is_none")]
350 pub output_format: Option<String>,
351}
352
353pub type RemixRequest = RemixVoiceRequest;
355
356#[derive(Debug, Clone, Serialize, Default)]
358pub struct DubRequest {
359 pub audio_base64: String,
361
362 #[serde(skip_serializing_if = "Option::is_none")]
364 pub filename: Option<String>,
365
366 pub target_language: String,
368
369 #[serde(skip_serializing_if = "Option::is_none")]
371 pub source_language: Option<String>,
372}
373
374#[derive(Debug, Clone, Serialize, Default)]
376pub struct AlignRequest {
377 pub audio_base64: String,
379
380 pub text: String,
382
383 #[serde(skip_serializing_if = "Option::is_none")]
385 pub language: Option<String>,
386}
387
388#[derive(Debug, Clone, Deserialize)]
390pub struct AlignmentSegment {
391 pub text: String,
393
394 pub start: f64,
396
397 pub end: f64,
399}
400
401#[derive(Debug, Clone, Deserialize)]
403pub struct AlignedWord {
404 pub text: String,
406
407 pub start_time: f64,
409
410 pub end_time: f64,
412
413 #[serde(default)]
415 pub confidence: f64,
416}
417
418#[derive(Debug, Clone, Deserialize)]
420pub struct AlignResponse {
421 #[serde(default)]
423 pub segments: Vec<AlignmentSegment>,
424
425 #[serde(default)]
427 pub alignment: Vec<AlignedWord>,
428
429 #[serde(default)]
431 pub model: String,
432
433 #[serde(default)]
435 pub cost_ticks: i64,
436
437 #[serde(default)]
439 pub request_id: String,
440}
441
442#[derive(Debug, Clone, Deserialize)]
448pub struct DialogueResponse {
449 pub audio_base64: String,
450 pub format: String,
451 #[serde(default)]
452 pub size_bytes: i64,
453 #[serde(default)]
454 pub model: String,
455 #[serde(default)]
456 pub cost_ticks: i64,
457 #[serde(default)]
458 pub request_id: String,
459}
460
461#[derive(Debug, Clone, Deserialize)]
463pub struct SpeechToSpeechResponse {
464 pub audio_base64: String,
465 pub format: String,
466 #[serde(default)]
467 pub size_bytes: i64,
468 #[serde(default)]
469 pub model: String,
470 #[serde(default)]
471 pub cost_ticks: i64,
472 #[serde(default)]
473 pub request_id: String,
474}
475
476#[derive(Debug, Clone, Deserialize)]
478pub struct IsolateVoiceResponse {
479 pub audio_base64: String,
480 pub format: String,
481 #[serde(default)]
482 pub size_bytes: i64,
483 #[serde(default)]
484 pub cost_ticks: i64,
485 #[serde(default)]
486 pub request_id: String,
487}
488
489#[derive(Debug, Clone, Deserialize)]
491pub struct RemixVoiceResponse {
492 #[serde(default)]
493 pub audio_base64: Option<String>,
494 pub format: String,
495 #[serde(default)]
496 pub size_bytes: i64,
497 #[serde(default)]
498 pub voice_id: Option<String>,
499 #[serde(default)]
500 pub cost_ticks: i64,
501 #[serde(default)]
502 pub request_id: String,
503}
504
505#[derive(Debug, Clone, Deserialize)]
507pub struct DubResponse {
508 pub dubbing_id: String,
509 pub audio_base64: String,
510 pub format: String,
511 #[serde(default)]
512 pub target_lang: String,
513 #[serde(default)]
514 pub status: String,
515 #[serde(default)]
516 pub processing_time_seconds: f64,
517 #[serde(default)]
518 pub cost_ticks: i64,
519 #[serde(default)]
520 pub request_id: String,
521}
522
523#[derive(Debug, Clone, Deserialize)]
525pub struct VoiceDesignResponse {
526 pub previews: Vec<VoicePreview>,
527 #[serde(default)]
528 pub cost_ticks: i64,
529 #[serde(default)]
530 pub request_id: String,
531}
532
533#[derive(Debug, Clone, Deserialize)]
535pub struct VoicePreview {
536 pub generated_voice_id: String,
537 pub audio_base64: String,
538 pub format: String,
539}
540
541#[derive(Debug, Clone, Deserialize)]
543pub struct StarfishTTSResponse {
544 #[serde(default)]
545 pub audio_base64: Option<String>,
546 #[serde(default)]
547 pub url: Option<String>,
548 pub format: String,
549 #[serde(default)]
550 pub size_bytes: i64,
551 #[serde(default)]
552 pub duration: f64,
553 #[serde(default)]
554 pub model: String,
555 #[serde(default)]
556 pub cost_ticks: i64,
557 #[serde(default)]
558 pub request_id: String,
559}
560
561#[derive(Debug, Clone, Serialize, Default)]
563pub struct MusicAdvancedRequest {
564 pub prompt: String,
565 #[serde(skip_serializing_if = "Option::is_none")]
566 pub duration_seconds: Option<i32>,
567 #[serde(skip_serializing_if = "Option::is_none")]
568 pub model: Option<String>,
569 #[serde(skip_serializing_if = "Option::is_none")]
570 pub finetune_id: Option<String>,
571}
572
573#[derive(Debug, Clone, Deserialize)]
575pub struct MusicAdvancedClip {
576 #[serde(default)]
577 pub base64: String,
578 #[serde(default)]
579 pub format: String,
580 #[serde(default)]
581 pub size: i64,
582}
583
584#[derive(Debug, Clone, Deserialize)]
586pub struct MusicAdvancedResponse {
587 #[serde(default)]
588 pub clips: Vec<MusicAdvancedClip>,
589 #[serde(default)]
590 pub model: String,
591 #[serde(default)]
592 pub cost_ticks: i64,
593 #[serde(default)]
594 pub request_id: String,
595}
596
597#[derive(Debug, Clone, Serialize, Deserialize)]
599pub struct MusicFinetuneInfo {
600 pub finetune_id: String,
601 pub name: String,
602 #[serde(default)]
603 pub description: Option<String>,
604 #[serde(default)]
605 pub status: String,
606 #[serde(default)]
607 pub model_id: Option<String>,
608 #[serde(default)]
609 pub created_at: Option<String>,
610}
611
612#[derive(Debug, Clone, Deserialize)]
614pub struct MusicFinetuneListResponse {
615 pub finetunes: Vec<MusicFinetuneInfo>,
616}
617
618#[derive(Debug, Clone, Serialize)]
620pub struct MusicFinetuneCreateRequest {
621 pub name: String,
622 #[serde(skip_serializing_if = "Option::is_none")]
623 pub description: Option<String>,
624 pub samples: Vec<String>,
625}
626
627#[derive(Debug, Clone, Serialize, Default)]
629pub struct VoiceDesignRequest {
630 #[serde(rename = "voice_description")]
632 pub description: String,
633
634 #[serde(rename = "sample_text")]
636 pub text: String,
637
638 #[serde(rename = "format", skip_serializing_if = "Option::is_none")]
640 pub output_format: Option<String>,
641}
642
643#[derive(Debug, Clone, Serialize, Default)]
645pub struct StarfishTTSRequest {
646 pub text: String,
648
649 #[serde(skip_serializing_if = "Option::is_none")]
651 pub voice: Option<String>,
652
653 #[serde(rename = "format", skip_serializing_if = "Option::is_none")]
655 pub output_format: Option<String>,
656
657 #[serde(skip_serializing_if = "Option::is_none")]
659 pub speed: Option<f64>,
660}
661
662#[derive(Debug, Clone, Serialize, Deserialize, Default)]
668pub struct MusicSection {
669 pub section_type: String,
670 #[serde(skip_serializing_if = "Option::is_none")]
671 pub lyrics: Option<String>,
672 #[serde(skip_serializing_if = "Option::is_none")]
673 pub style: Option<String>,
674 #[serde(skip_serializing_if = "Option::is_none")]
675 pub style_exclude: Option<String>,
676}
677
678#[derive(Debug, Clone, Serialize, Default)]
680pub struct ElevenMusicRequest {
681 pub model: String,
682 pub prompt: String,
683 #[serde(skip_serializing_if = "Option::is_none")]
684 pub sections: Option<Vec<MusicSection>>,
685 #[serde(skip_serializing_if = "Option::is_none")]
686 pub duration_seconds: Option<i32>,
687 #[serde(skip_serializing_if = "Option::is_none")]
688 pub language: Option<String>,
689 #[serde(skip_serializing_if = "Option::is_none")]
690 pub vocals: Option<bool>,
691 #[serde(skip_serializing_if = "Option::is_none")]
692 pub style: Option<String>,
693 #[serde(skip_serializing_if = "Option::is_none")]
694 pub style_exclude: Option<String>,
695 #[serde(skip_serializing_if = "Option::is_none")]
696 pub finetune_id: Option<String>,
697 #[serde(skip_serializing_if = "Option::is_none")]
698 pub edit_reference_id: Option<String>,
699 #[serde(skip_serializing_if = "Option::is_none")]
700 pub edit_instruction: Option<String>,
701}
702
703#[derive(Debug, Clone, Deserialize)]
705pub struct ElevenMusicClip {
706 #[serde(default)]
708 pub base64: String,
709 #[serde(default)]
711 pub format: String,
712 #[serde(default)]
714 pub size: i64,
715}
716
717#[derive(Debug, Clone, Deserialize)]
720pub struct ElevenMusicResponse {
721 #[serde(default)]
723 pub clips: Vec<ElevenMusicClip>,
724 #[serde(default)]
726 pub model: String,
727 #[serde(default)]
729 pub cost_ticks: i64,
730 #[serde(default)]
732 pub request_id: String,
733}
734
735#[derive(Debug, Clone, Serialize, Deserialize)]
737pub struct FinetuneInfo {
738 pub finetune_id: String,
739 pub name: String,
740 #[serde(default)]
741 pub status: String,
742 #[serde(default)]
743 pub created_at: Option<String>,
744}
745
746#[derive(Debug, Clone, Deserialize)]
748pub struct ListFinetunesResponse {
749 pub finetunes: Vec<FinetuneInfo>,
750}
751
752impl Client {
757 pub async fn speak(&self, req: &TextToSpeechRequest) -> Result<TextToSpeechResponse> {
759 let (mut resp, meta) = self
760 .post_json::<TextToSpeechRequest, TextToSpeechResponse>("/qai/v1/audio/tts", req)
761 .await?;
762 if resp.cost_ticks == 0 {
763 resp.cost_ticks = meta.cost_ticks;
764 }
765 if resp.request_id.is_empty() {
766 resp.request_id = meta.request_id;
767 }
768 Ok(resp)
769 }
770
771 pub async fn transcribe(&self, req: &SpeechToTextRequest) -> Result<SpeechToTextResponse> {
773 let (mut resp, meta) = self
774 .post_json::<SpeechToTextRequest, SpeechToTextResponse>("/qai/v1/audio/stt", req)
775 .await?;
776 if resp.cost_ticks == 0 {
777 resp.cost_ticks = meta.cost_ticks;
778 }
779 if resp.request_id.is_empty() {
780 resp.request_id = meta.request_id;
781 }
782 Ok(resp)
783 }
784
785 pub async fn sound_effects(&self, req: &SoundEffectRequest) -> Result<SoundEffectResponse> {
787 let (mut resp, meta) = self
788 .post_json::<SoundEffectRequest, SoundEffectResponse>(
789 "/qai/v1/audio/sound-effects",
790 req,
791 )
792 .await?;
793 if resp.cost_ticks == 0 {
794 resp.cost_ticks = meta.cost_ticks;
795 }
796 if resp.request_id.is_empty() {
797 resp.request_id = meta.request_id;
798 }
799 Ok(resp)
800 }
801
802 pub async fn generate_music(&self, req: &MusicRequest) -> Result<MusicResponse> {
804 let (mut resp, meta) = self
805 .post_json::<MusicRequest, MusicResponse>("/qai/v1/audio/music", req)
806 .await?;
807 if resp.cost_ticks == 0 {
808 resp.cost_ticks = meta.cost_ticks;
809 }
810 if resp.request_id.is_empty() {
811 resp.request_id = meta.request_id;
812 }
813 Ok(resp)
814 }
815
816 pub async fn dialogue(&self, req: &DialogueRequest) -> Result<AudioResponse> {
818 let (mut resp, meta) = self
819 .post_json::<DialogueRequest, AudioResponse>("/qai/v1/audio/dialogue", req)
820 .await?;
821 if resp.cost_ticks == 0 {
822 resp.cost_ticks = meta.cost_ticks;
823 }
824 if resp.request_id.is_empty() {
825 resp.request_id = meta.request_id;
826 }
827 Ok(resp)
828 }
829
830 pub async fn speech_to_speech(
832 &self,
833 req: &SpeechToSpeechRequest,
834 ) -> Result<AudioResponse> {
835 let (mut resp, meta) = self
836 .post_json::<SpeechToSpeechRequest, AudioResponse>(
837 "/qai/v1/audio/speech-to-speech",
838 req,
839 )
840 .await?;
841 if resp.cost_ticks == 0 {
842 resp.cost_ticks = meta.cost_ticks;
843 }
844 if resp.request_id.is_empty() {
845 resp.request_id = meta.request_id;
846 }
847 Ok(resp)
848 }
849
850 pub async fn isolate_voice(&self, req: &IsolateVoiceRequest) -> Result<AudioResponse> {
852 let (mut resp, meta) = self
853 .post_json::<IsolateVoiceRequest, AudioResponse>("/qai/v1/audio/isolate", req)
854 .await?;
855 if resp.cost_ticks == 0 {
856 resp.cost_ticks = meta.cost_ticks;
857 }
858 if resp.request_id.is_empty() {
859 resp.request_id = meta.request_id;
860 }
861 Ok(resp)
862 }
863
864 pub async fn remix_voice(&self, req: &RemixVoiceRequest) -> Result<AudioResponse> {
866 let (mut resp, meta) = self
867 .post_json::<RemixVoiceRequest, AudioResponse>("/qai/v1/audio/remix", req)
868 .await?;
869 if resp.cost_ticks == 0 {
870 resp.cost_ticks = meta.cost_ticks;
871 }
872 if resp.request_id.is_empty() {
873 resp.request_id = meta.request_id;
874 }
875 Ok(resp)
876 }
877
878 pub async fn dub(&self, req: &DubRequest) -> Result<AudioResponse> {
880 let (mut resp, meta) = self
881 .post_json::<DubRequest, AudioResponse>("/qai/v1/audio/dub", req)
882 .await?;
883 if resp.cost_ticks == 0 {
884 resp.cost_ticks = meta.cost_ticks;
885 }
886 if resp.request_id.is_empty() {
887 resp.request_id = meta.request_id;
888 }
889 Ok(resp)
890 }
891
892 pub async fn align(&self, req: &AlignRequest) -> Result<AlignResponse> {
894 let (mut resp, meta) = self
895 .post_json::<AlignRequest, AlignResponse>("/qai/v1/audio/align", req)
896 .await?;
897 if resp.cost_ticks == 0 {
898 resp.cost_ticks = meta.cost_ticks;
899 }
900 if resp.request_id.is_empty() {
901 resp.request_id = meta.request_id;
902 }
903 Ok(resp)
904 }
905
906 pub async fn voice_design(&self, req: &VoiceDesignRequest) -> Result<AudioResponse> {
908 let (mut resp, meta) = self
909 .post_json::<VoiceDesignRequest, AudioResponse>("/qai/v1/audio/voice-design", req)
910 .await?;
911 if resp.cost_ticks == 0 {
912 resp.cost_ticks = meta.cost_ticks;
913 }
914 if resp.request_id.is_empty() {
915 resp.request_id = meta.request_id;
916 }
917 Ok(resp)
918 }
919
920 pub async fn starfish_tts(&self, req: &StarfishTTSRequest) -> Result<AudioResponse> {
922 let (mut resp, meta) = self
923 .post_json::<StarfishTTSRequest, AudioResponse>("/qai/v1/audio/starfish-tts", req)
924 .await?;
925 if resp.cost_ticks == 0 {
926 resp.cost_ticks = meta.cost_ticks;
927 }
928 if resp.request_id.is_empty() {
929 resp.request_id = meta.request_id;
930 }
931 Ok(resp)
932 }
933
934 pub async fn generate_music_advanced(
936 &self,
937 req: &ElevenMusicRequest,
938 ) -> Result<ElevenMusicResponse> {
939 let (mut resp, meta) = self
940 .post_json::<ElevenMusicRequest, ElevenMusicResponse>(
941 "/qai/v1/audio/music/advanced",
942 req,
943 )
944 .await?;
945 if resp.cost_ticks == 0 {
946 resp.cost_ticks = meta.cost_ticks;
947 }
948 if resp.request_id.is_empty() {
949 resp.request_id = meta.request_id;
950 }
951 Ok(resp)
952 }
953
954 pub async fn list_finetunes(&self) -> Result<ListFinetunesResponse> {
956 let (resp, _) = self
957 .get_json::<ListFinetunesResponse>("/qai/v1/audio/finetunes")
958 .await?;
959 Ok(resp)
960 }
961
962 pub async fn create_finetune(
964 &self,
965 name: &str,
966 files: Vec<crate::voices::CloneVoiceFile>,
967 ) -> Result<FinetuneInfo> {
968 let mut form = reqwest::multipart::Form::new().text("name", name.to_string());
969
970 for file in files {
971 let part = reqwest::multipart::Part::bytes(file.data)
972 .file_name(file.filename)
973 .mime_str(&file.mime_type)
974 .map_err(|e| crate::error::Error::Http(e.into()))?;
975 form = form.part("files", part);
976 }
977
978 let (resp, _) = self
979 .post_multipart::<FinetuneInfo>("/qai/v1/audio/finetunes", form)
980 .await?;
981 Ok(resp)
982 }
983
984 pub async fn delete_finetune(&self, id: &str) -> Result<serde_json::Value> {
986 let path = format!("/qai/v1/audio/finetunes/{id}");
987 let (resp, _) = self.delete_json::<serde_json::Value>(&path).await?;
988 Ok(resp)
989 }
990}