1mod error;
47mod profile;
48mod ws;
49
50pub use cloacina_api_types as types;
51pub use error::ClientError;
52pub use profile::resolve_api_key_scheme;
53pub use ws::{DeliveryPush, SubscribeOptions, DELIVERY_PROTOCOL_VERSION};
54
55use std::sync::Arc;
56use std::time::Duration;
57
58use reqwest::{Method, Response};
59use serde::de::DeserializeOwned;
60use serde_json::Value;
61
62use cloacina_api_types::{
63 AccumulatorStatus, AgentInfo, CompilerStatus, CreateInstanceRequest, CreateKeyRequest,
64 CreateSecretRequest, CreateTenantRequest, DeclaredSurface, ExecuteRequest, ExecuteResponse,
65 ExecutionDetail, ExecutionEventsResponse, ExecutionSummary, ExecutionTasksResponse,
66 FireReactorRequest, FireReactorResponse, FireTriggerRequest, FireTriggerResponse, GraphStatus,
67 InjectAccumulatorRequest, InjectAccumulatorResponse, KeyCreatedResponse, KeyInfo,
68 KeyRevokedResponse, KeyRole, ListResponse, ReactorFire, ReactorFireTimeseries, ReactorStatus,
69 RotateSecretRequest, SecretDeletedResponse, SecretMetadataResponse, TenantCreatedResponse,
70 TenantListResponse, TenantRemovedResponse, TenantSummary, TriggerDetailResponse,
71 TriggerPauseResponse, TriggerScheduleSummary, WorkflowDeletedResponse, WorkflowDetail,
72 WorkflowInstanceSummary, WorkflowPauseResponse, WorkflowSourceResponse, WorkflowSummary,
73 WorkflowUploadedResponse, WsTicketResponse,
74};
75
76#[derive(Debug, Clone, Default)]
78pub struct ClientBuilder {
79 server: String,
80 api_key: Option<String>,
81 tenant: Option<String>,
82 connect_timeout: Option<Duration>,
83 timeout: Option<Duration>,
84}
85
86impl ClientBuilder {
87 pub fn new(server: impl Into<String>) -> Self {
90 Self {
91 server: server.into(),
92 ..Default::default()
93 }
94 }
95
96 pub fn from_cloacinactl_profile(
100 home: Option<&std::path::Path>,
101 profile: Option<&str>,
102 ) -> Result<Self, ClientError> {
103 profile::builder_from_profile(home, profile)
104 }
105
106 pub fn api_key(mut self, key: impl Into<String>) -> Self {
108 self.api_key = Some(key.into());
109 self
110 }
111
112 pub fn tenant(mut self, tenant: impl Into<String>) -> Self {
115 self.tenant = Some(tenant.into());
116 self
117 }
118
119 pub fn connect_timeout(mut self, d: Duration) -> Self {
121 self.connect_timeout = Some(d);
122 self
123 }
124
125 pub fn timeout(mut self, d: Duration) -> Self {
127 self.timeout = Some(d);
128 self
129 }
130
131 pub fn build(self) -> Result<Client, ClientError> {
132 let api_key = self
133 .api_key
134 .ok_or_else(|| ClientError::Config("no API key configured".into()))?;
135 #[cfg(not(target_arch = "wasm32"))]
138 let http = reqwest::Client::builder()
139 .connect_timeout(self.connect_timeout.unwrap_or(Duration::from_secs(5)))
140 .timeout(self.timeout.unwrap_or(Duration::from_secs(30)))
141 .build()
142 .map_err(ClientError::from_reqwest)?;
143 #[cfg(target_arch = "wasm32")]
144 let http = {
145 let _ = (&self.connect_timeout, &self.timeout);
146 reqwest::Client::builder()
147 .build()
148 .map_err(ClientError::from_reqwest)?
149 };
150 Ok(Client {
151 inner: Arc::new(ClientInner {
152 server: self.server.trim_end_matches('/').to_string(),
153 api_key,
154 tenant: self.tenant,
155 http,
156 }),
157 })
158 }
159}
160
161struct ClientInner {
162 server: String,
163 api_key: String,
164 tenant: Option<String>,
165 http: reqwest::Client,
166}
167
168#[derive(Clone)]
171pub struct Client {
172 inner: Arc<ClientInner>,
173}
174
175impl Client {
176 pub fn server(&self) -> &str {
178 &self.inner.server
179 }
180
181 pub fn tenant_segment(&self) -> &str {
184 self.inner.tenant.as_deref().unwrap_or("public")
185 }
186
187 fn url(&self, path: &str) -> String {
188 format!("{}/{}", self.inner.server, path.trim_start_matches('/'))
189 }
190
191 fn request(&self, method: Method, path: &str) -> reqwest::RequestBuilder {
192 self.inner
195 .http
196 .request(method, self.url(path))
197 .bearer_auth(&self.inner.api_key)
198 }
199
200 async fn parse<T: DeserializeOwned>(response: Response) -> Result<T, ClientError> {
201 let status = response.status().as_u16();
202 if response.status().is_success() {
203 return response
204 .json::<T>()
205 .await
206 .map_err(ClientError::from_reqwest);
207 }
208 let body = response.json::<Value>().await.unwrap_or(Value::Null);
209 Err(ClientError::from_status(status, body))
210 }
211
212 pub async fn get_json<T: DeserializeOwned>(&self, path: &str) -> Result<T, ClientError> {
218 let response = self
219 .request(Method::GET, path)
220 .send()
221 .await
222 .map_err(ClientError::from_reqwest)?;
223 Self::parse(response).await
224 }
225
226 pub async fn post_json<B: serde::Serialize + ?Sized, T: DeserializeOwned>(
228 &self,
229 path: &str,
230 body: &B,
231 ) -> Result<T, ClientError> {
232 let response = self
233 .request(Method::POST, path)
234 .json(body)
235 .send()
236 .await
237 .map_err(ClientError::from_reqwest)?;
238 Self::parse(response).await
239 }
240
241 pub async fn put_json<B: serde::Serialize + ?Sized, T: DeserializeOwned>(
243 &self,
244 path: &str,
245 body: &B,
246 ) -> Result<T, ClientError> {
247 let response = self
248 .request(Method::PUT, path)
249 .json(body)
250 .send()
251 .await
252 .map_err(ClientError::from_reqwest)?;
253 Self::parse(response).await
254 }
255
256 pub async fn delete_path(&self, path: &str) -> Result<(), ClientError> {
258 let response = self
259 .request(Method::DELETE, path)
260 .send()
261 .await
262 .map_err(ClientError::from_reqwest)?;
263 let status = response.status().as_u16();
264 if response.status().is_success() {
265 return Ok(());
266 }
267 let body = response.json::<Value>().await.unwrap_or(Value::Null);
268 Err(ClientError::from_status(status, body))
269 }
270
271 fn tenant_of<'a>(&'a self, tenant: Option<&'a str>) -> &'a str {
272 tenant.unwrap_or_else(|| self.tenant_segment())
273 }
274
275 pub async fn health(&self) -> Result<Value, ClientError> {
278 self.get_json("/health").await
279 }
280
281 pub async fn ready(&self) -> Result<(u16, Value), ClientError> {
283 let response = self
284 .request(Method::GET, "/ready")
285 .send()
286 .await
287 .map_err(ClientError::from_reqwest)?;
288 let status = response.status().as_u16();
289 let body = response.json::<Value>().await.unwrap_or(Value::Null);
290 Ok((status, body))
291 }
292
293 pub async fn create_key(
296 &self,
297 name: &str,
298 role: KeyRole,
299 ) -> Result<KeyCreatedResponse, ClientError> {
300 self.post_json(
301 "/v1/auth/keys",
302 &CreateKeyRequest {
303 name: name.to_string(),
304 role,
305 },
306 )
307 .await
308 }
309
310 pub async fn list_keys(&self) -> Result<ListResponse<KeyInfo>, ClientError> {
311 self.get_json("/v1/auth/keys").await
312 }
313
314 pub async fn revoke_key(&self, key_id: &str) -> Result<KeyRevokedResponse, ClientError> {
315 let response = self
316 .request(Method::DELETE, &format!("/v1/auth/keys/{key_id}"))
317 .send()
318 .await
319 .map_err(ClientError::from_reqwest)?;
320 Self::parse(response).await
321 }
322
323 pub async fn create_tenant_key(
324 &self,
325 name: &str,
326 role: KeyRole,
327 tenant: Option<&str>,
328 ) -> Result<KeyCreatedResponse, ClientError> {
329 let t = self.tenant_of(tenant);
330 self.post_json(
331 &format!("/v1/tenants/{t}/keys"),
332 &CreateKeyRequest {
333 name: name.to_string(),
334 role,
335 },
336 )
337 .await
338 }
339
340 pub async fn list_tenant_keys(
343 &self,
344 tenant: Option<&str>,
345 ) -> Result<ListResponse<KeyInfo>, ClientError> {
346 let t = self.tenant_of(tenant);
347 self.get_json(&format!("/v1/tenants/{t}/keys")).await
348 }
349
350 pub async fn revoke_tenant_key(
354 &self,
355 key_id: &str,
356 tenant: Option<&str>,
357 ) -> Result<KeyRevokedResponse, ClientError> {
358 let t = self.tenant_of(tenant);
359 let response = self
360 .request(Method::DELETE, &format!("/v1/tenants/{t}/keys/{key_id}"))
361 .send()
362 .await
363 .map_err(ClientError::from_reqwest)?;
364 Self::parse(response).await
365 }
366
367 pub async fn create_ws_ticket(&self) -> Result<WsTicketResponse, ClientError> {
369 self.post_json("/v1/auth/ws-ticket", &Value::Null).await
370 }
371
372 pub async fn local_login(
382 &self,
383 username: &str,
384 password: &str,
385 tenant: Option<&str>,
386 ) -> Result<Value, ClientError> {
387 let body = serde_json::json!({
388 "username": username,
389 "password": password,
390 "tenant": tenant,
391 });
392 self.post_json("/v1/auth/local/login", &body).await
393 }
394
395 pub async fn refresh(&self) -> Result<Value, ClientError> {
397 self.post_json("/v1/auth/refresh", &Value::Null).await
398 }
399
400 pub async fn logout(&self) -> Result<Value, ClientError> {
402 self.post_json("/v1/auth/logout", &Value::Null).await
403 }
404
405 pub async fn whoami(&self) -> Result<Value, ClientError> {
407 self.get_json("/v1/auth/whoami").await
408 }
409
410 pub async fn list_accounts(&self, tenant: Option<&str>) -> Result<Value, ClientError> {
414 let t = self.tenant_of(tenant);
415 self.get_json(&format!("/v1/tenants/{t}/accounts")).await
416 }
417
418 pub async fn create_account(
420 &self,
421 username: &str,
422 password: &str,
423 role: &str,
424 tenant: Option<&str>,
425 ) -> Result<Value, ClientError> {
426 let t = self.tenant_of(tenant);
427 let body = serde_json::json!({
428 "username": username,
429 "password": password,
430 "role": role,
431 });
432 self.post_json(&format!("/v1/tenants/{t}/accounts"), &body)
433 .await
434 }
435
436 pub async fn disable_account(
438 &self,
439 account_id: &str,
440 tenant: Option<&str>,
441 ) -> Result<Value, ClientError> {
442 let t = self.tenant_of(tenant);
443 let response = self
444 .request(
445 Method::DELETE,
446 &format!("/v1/tenants/{t}/accounts/{account_id}"),
447 )
448 .send()
449 .await
450 .map_err(ClientError::from_reqwest)?;
451 Self::parse(response).await
452 }
453
454 pub async fn reset_password(
456 &self,
457 account_id: &str,
458 password: &str,
459 tenant: Option<&str>,
460 ) -> Result<Value, ClientError> {
461 let t = self.tenant_of(tenant);
462 let body = serde_json::json!({ "password": password });
463 self.post_json(
464 &format!("/v1/tenants/{t}/accounts/{account_id}/password"),
465 &body,
466 )
467 .await
468 }
469
470 pub async fn create_tenant(
473 &self,
474 request: &CreateTenantRequest,
475 ) -> Result<TenantCreatedResponse, ClientError> {
476 self.post_json("/v1/tenants", request).await
477 }
478
479 pub async fn list_tenants(&self) -> Result<ListResponse<TenantSummary>, ClientError> {
480 self.get_json("/v1/tenants").await
481 }
482
483 pub async fn remove_tenant(
484 &self,
485 schema_name: &str,
486 ) -> Result<TenantRemovedResponse, ClientError> {
487 let response = self
488 .request(Method::DELETE, &format!("/v1/tenants/{schema_name}"))
489 .send()
490 .await
491 .map_err(ClientError::from_reqwest)?;
492 Self::parse(response).await
493 }
494
495 pub async fn list_secrets(
501 &self,
502 tenant: Option<&str>,
503 ) -> Result<ListResponse<SecretMetadataResponse>, ClientError> {
504 let t = self.tenant_of(tenant);
505 self.get_json(&format!("/v1/tenants/{t}/secrets")).await
506 }
507
508 pub async fn create_secret(
510 &self,
511 request: &CreateSecretRequest,
512 tenant: Option<&str>,
513 ) -> Result<SecretMetadataResponse, ClientError> {
514 let t = self.tenant_of(tenant);
515 self.post_json(&format!("/v1/tenants/{t}/secrets"), request)
516 .await
517 }
518
519 pub async fn get_secret(
521 &self,
522 name: &str,
523 tenant: Option<&str>,
524 ) -> Result<SecretMetadataResponse, ClientError> {
525 let t = self.tenant_of(tenant);
526 self.get_json(&format!("/v1/tenants/{t}/secrets/{name}"))
527 .await
528 }
529
530 pub async fn rotate_secret(
532 &self,
533 name: &str,
534 request: &RotateSecretRequest,
535 tenant: Option<&str>,
536 ) -> Result<SecretMetadataResponse, ClientError> {
537 let t = self.tenant_of(tenant);
538 self.put_json(&format!("/v1/tenants/{t}/secrets/{name}"), request)
539 .await
540 }
541
542 pub async fn delete_secret(
544 &self,
545 name: &str,
546 tenant: Option<&str>,
547 ) -> Result<SecretDeletedResponse, ClientError> {
548 let t = self.tenant_of(tenant);
549 let response = self
550 .request(Method::DELETE, &format!("/v1/tenants/{t}/secrets/{name}"))
551 .send()
552 .await
553 .map_err(ClientError::from_reqwest)?;
554 Self::parse(response).await
555 }
556
557 pub async fn upload_workflow(
561 &self,
562 package: Vec<u8>,
563 tenant: Option<&str>,
564 ) -> Result<WorkflowUploadedResponse, ClientError> {
565 let t = self.tenant_of(tenant);
566 let part = reqwest::multipart::Part::bytes(package)
567 .file_name("package.cloacina")
568 .mime_str("application/octet-stream")
569 .map_err(ClientError::from_reqwest)?;
570 let form = reqwest::multipart::Form::new().part("file", part);
571 let response = self
572 .request(Method::POST, &format!("/v1/tenants/{t}/workflows"))
573 .multipart(form)
574 .send()
575 .await
576 .map_err(ClientError::from_reqwest)?;
577 Self::parse(response).await
578 }
579
580 pub async fn list_workflows(
581 &self,
582 tenant: Option<&str>,
583 ) -> Result<TenantListResponse<WorkflowSummary>, ClientError> {
584 let t = self.tenant_of(tenant);
585 self.get_json(&format!("/v1/tenants/{t}/workflows")).await
586 }
587
588 pub async fn get_workflow(
589 &self,
590 name: &str,
591 tenant: Option<&str>,
592 ) -> Result<WorkflowDetail, ClientError> {
593 let t = self.tenant_of(tenant);
594 self.get_json(&format!("/v1/tenants/{t}/workflows/{name}"))
595 .await
596 }
597
598 pub async fn delete_workflow(
599 &self,
600 name: &str,
601 version: &str,
602 tenant: Option<&str>,
603 ) -> Result<WorkflowDeletedResponse, ClientError> {
604 let t = self.tenant_of(tenant);
605 let response = self
606 .request(
607 Method::DELETE,
608 &format!("/v1/tenants/{t}/workflows/{name}/{version}"),
609 )
610 .send()
611 .await
612 .map_err(ClientError::from_reqwest)?;
613 Self::parse(response).await
614 }
615
616 pub async fn list_triggers(
619 &self,
620 limit: Option<i64>,
621 offset: Option<i64>,
622 tenant: Option<&str>,
623 ) -> Result<TenantListResponse<TriggerScheduleSummary>, ClientError> {
624 let t = self.tenant_of(tenant);
625 let mut path = format!("/v1/tenants/{t}/triggers");
626 let mut sep = '?';
627 if let Some(l) = limit {
628 path.push_str(&format!("{sep}limit={l}"));
629 sep = '&';
630 }
631 if let Some(o) = offset {
632 path.push_str(&format!("{sep}offset={o}"));
633 }
634 self.get_json(&path).await
635 }
636
637 pub async fn get_trigger(
638 &self,
639 name: &str,
640 tenant: Option<&str>,
641 ) -> Result<TriggerDetailResponse, ClientError> {
642 let t = self.tenant_of(tenant);
643 self.get_json(&format!("/v1/tenants/{t}/triggers/{name}"))
644 .await
645 }
646
647 pub async fn create_instance(
652 &self,
653 workflow: &str,
654 request: &CreateInstanceRequest,
655 tenant: Option<&str>,
656 ) -> Result<WorkflowInstanceSummary, ClientError> {
657 let t = self.tenant_of(tenant);
658 self.post_json(
659 &format!("/v1/tenants/{t}/workflows/{workflow}/instances"),
660 request,
661 )
662 .await
663 }
664
665 pub async fn list_instances(
666 &self,
667 workflow: &str,
668 limit: Option<i64>,
669 offset: Option<i64>,
670 tenant: Option<&str>,
671 ) -> Result<TenantListResponse<WorkflowInstanceSummary>, ClientError> {
672 let t = self.tenant_of(tenant);
673 let mut path = format!("/v1/tenants/{t}/workflows/{workflow}/instances");
674 let mut sep = '?';
675 if let Some(l) = limit {
676 path.push_str(&format!("{sep}limit={l}"));
677 sep = '&';
678 }
679 if let Some(o) = offset {
680 path.push_str(&format!("{sep}offset={o}"));
681 }
682 self.get_json(&path).await
683 }
684
685 pub async fn get_instance(
686 &self,
687 workflow: &str,
688 instance: &str,
689 tenant: Option<&str>,
690 ) -> Result<WorkflowInstanceSummary, ClientError> {
691 let t = self.tenant_of(tenant);
692 self.get_json(&format!(
693 "/v1/tenants/{t}/workflows/{workflow}/instances/{instance}"
694 ))
695 .await
696 }
697
698 pub async fn delete_instance(
699 &self,
700 workflow: &str,
701 instance: &str,
702 tenant: Option<&str>,
703 ) -> Result<(), ClientError> {
704 let t = self.tenant_of(tenant);
705 self.delete_path(&format!(
706 "/v1/tenants/{t}/workflows/{workflow}/instances/{instance}"
707 ))
708 .await
709 }
710
711 pub async fn execute_workflow(
714 &self,
715 name: &str,
716 context: Value,
717 ) -> Result<ExecuteResponse, ClientError> {
718 let t = self.tenant_segment();
719 self.post_json(
720 &format!("/v1/tenants/{t}/workflows/{name}/execute"),
721 &ExecuteRequest {
722 context: Some(context),
723 },
724 )
725 .await
726 }
727
728 pub async fn list_executions(
729 &self,
730 query: &cloacina_api_types::ListExecutionsQuery,
731 tenant: Option<&str>,
732 ) -> Result<TenantListResponse<ExecutionSummary>, ClientError> {
733 let t = self.tenant_of(tenant);
734 let mut path = format!("/v1/tenants/{t}/executions");
735 let mut sep = '?';
736 let mut push = |k: &str, v: String| {
737 path.push_str(&format!("{sep}{k}={v}"));
738 sep = '&';
739 };
740 if let Some(s) = &query.status {
741 push("status", urlencoding::encode(s).into_owned());
742 }
743 if let Some(w) = &query.workflow {
744 push("workflow", urlencoding::encode(w).into_owned());
745 }
746 if let Some(l) = query.limit {
747 push("limit", l.to_string());
748 }
749 if let Some(o) = query.offset {
750 push("offset", o.to_string());
751 }
752 self.get_json(&path).await
753 }
754
755 pub async fn get_execution(
756 &self,
757 exec_id: &str,
758 tenant: Option<&str>,
759 ) -> Result<ExecutionDetail, ClientError> {
760 let t = self.tenant_of(tenant);
761 self.get_json(&format!("/v1/tenants/{t}/executions/{exec_id}"))
762 .await
763 }
764
765 pub async fn get_execution_events(
766 &self,
767 exec_id: &str,
768 tenant: Option<&str>,
769 ) -> Result<ExecutionEventsResponse, ClientError> {
770 let t = self.tenant_of(tenant);
771 self.get_json(&format!("/v1/tenants/{t}/executions/{exec_id}/events"))
772 .await
773 }
774
775 pub async fn get_execution_tasks(
776 &self,
777 tenant_id: &str,
778 exec_id: &str,
779 ) -> Result<ExecutionTasksResponse, ClientError> {
780 self.get_json(&format!(
781 "/v1/tenants/{tenant_id}/executions/{exec_id}/tasks"
782 ))
783 .await
784 }
785
786 pub async fn list_accumulators(&self) -> Result<ListResponse<AccumulatorStatus>, ClientError> {
789 self.get_json("/v1/health/accumulators").await
790 }
791
792 pub async fn list_graphs(&self) -> Result<ListResponse<GraphStatus>, ClientError> {
793 self.get_json("/v1/health/graphs").await
794 }
795
796 pub async fn get_graph(&self, name: &str) -> Result<GraphStatus, ClientError> {
797 self.get_json(&format!("/v1/health/graphs/{name}")).await
798 }
799
800 pub async fn list_reactors(&self) -> Result<ListResponse<ReactorStatus>, ClientError> {
801 self.get_json("/v1/health/reactors").await
802 }
803
804 pub async fn fire_reactor(
807 &self,
808 name: &str,
809 request: &FireReactorRequest,
810 ) -> Result<FireReactorResponse, ClientError> {
811 self.post_json(&format!("/v1/health/reactors/{name}/fire"), request)
812 .await
813 }
814
815 pub async fn list_reactor_fires(
816 &self,
817 name: &str,
818 ) -> Result<ListResponse<ReactorFire>, ClientError> {
819 self.get_json(&format!("/v1/health/reactors/{name}/fires"))
820 .await
821 }
822
823 pub async fn reactor_fire_timeseries(
824 &self,
825 name: &str,
826 ) -> Result<ReactorFireTimeseries, ClientError> {
827 self.get_json(&format!("/v1/health/reactors/{name}/fires/timeseries"))
828 .await
829 }
830
831 pub async fn reactor_interface(&self, name: &str) -> Result<DeclaredSurface, ClientError> {
832 self.get_json(&format!("/v1/health/reactors/{name}/interface"))
833 .await
834 }
835
836 pub async fn accumulator_interface(&self, name: &str) -> Result<DeclaredSurface, ClientError> {
837 self.get_json(&format!("/v1/health/accumulators/{name}/interface"))
838 .await
839 }
840
841 pub async fn inject_accumulator(
842 &self,
843 name: &str,
844 request: &InjectAccumulatorRequest,
845 ) -> Result<InjectAccumulatorResponse, ClientError> {
846 self.post_json(&format!("/v1/health/accumulators/{name}/inject"), request)
847 .await
848 }
849
850 pub async fn pause_workflow(
853 &self,
854 name: &str,
855 tenant: Option<&str>,
856 ) -> Result<WorkflowPauseResponse, ClientError> {
857 let t = self.tenant_of(tenant);
858 self.post_json(
859 &format!("/v1/tenants/{t}/workflows/{name}/pause"),
860 &Value::Null,
861 )
862 .await
863 }
864
865 pub async fn resume_workflow(
866 &self,
867 name: &str,
868 tenant: Option<&str>,
869 ) -> Result<WorkflowPauseResponse, ClientError> {
870 let t = self.tenant_of(tenant);
871 self.post_json(
872 &format!("/v1/tenants/{t}/workflows/{name}/resume"),
873 &Value::Null,
874 )
875 .await
876 }
877
878 pub async fn get_workflow_source(
879 &self,
880 name: &str,
881 tenant: Option<&str>,
882 ) -> Result<WorkflowSourceResponse, ClientError> {
883 let t = self.tenant_of(tenant);
884 self.get_json(&format!("/v1/tenants/{t}/workflows/{name}/source"))
885 .await
886 }
887
888 pub async fn pause_trigger(
889 &self,
890 name: &str,
891 tenant: Option<&str>,
892 ) -> Result<TriggerPauseResponse, ClientError> {
893 let t = self.tenant_of(tenant);
894 self.post_json(
895 &format!("/v1/tenants/{t}/triggers/{name}/pause"),
896 &Value::Null,
897 )
898 .await
899 }
900
901 pub async fn fire_trigger(
904 &self,
905 name: &str,
906 request: &FireTriggerRequest,
907 tenant: Option<&str>,
908 ) -> Result<FireTriggerResponse, ClientError> {
909 let t = self.tenant_of(tenant);
910 self.post_json(&format!("/v1/tenants/{t}/triggers/{name}/fire"), request)
911 .await
912 }
913
914 pub async fn trigger_interface(
917 &self,
918 name: &str,
919 tenant: Option<&str>,
920 ) -> Result<DeclaredSurface, ClientError> {
921 let t = self.tenant_of(tenant);
922 self.get_json(&format!("/v1/tenants/{t}/triggers/{name}/interface"))
923 .await
924 }
925
926 pub async fn resume_trigger(
927 &self,
928 name: &str,
929 tenant: Option<&str>,
930 ) -> Result<TriggerPauseResponse, ClientError> {
931 let t = self.tenant_of(tenant);
932 self.post_json(
933 &format!("/v1/tenants/{t}/triggers/{name}/resume"),
934 &Value::Null,
935 )
936 .await
937 }
938
939 pub async fn list_agents(&self) -> Result<ListResponse<AgentInfo>, ClientError> {
942 self.get_json("/v1/agents").await
943 }
944
945 pub async fn compiler_status(&self) -> Result<CompilerStatus, ClientError> {
946 self.get_json("/v1/compiler/status").await
947 }
948
949 pub fn subscribe_delivery(
955 &self,
956 recipient: &str,
957 options: SubscribeOptions,
958 ) -> impl futures_util::Stream<Item = Result<DeliveryPush, ClientError>> + '_ {
959 ws::subscribe_delivery(self.clone(), recipient.to_string(), options)
960 }
961
962 pub fn follow_execution_events(
966 &self,
967 execution_id: &str,
968 ) -> impl futures_util::Stream<Item = Result<Value, ClientError>> + '_ {
969 self.follow_execution_events_with(execution_id, SubscribeOptions::default())
970 }
971
972 pub fn follow_execution_events_with(
975 &self,
976 execution_id: &str,
977 options: SubscribeOptions,
978 ) -> impl futures_util::Stream<Item = Result<Value, ClientError>> + '_ {
979 ws::follow_execution_events(self.clone(), execution_id.to_string(), options)
980 }
981}