1use reqwest::Client;
6use url::Url;
7
8use crate::agent::{Agent, AgentMode};
9use crate::recipe::Recipe;
10
11#[derive(Debug, Clone)]
13pub struct AgentOvenClient {
14 base_url: Url,
16
17 http: Client,
19
20 api_key: Option<String>,
22
23 kitchen_id: Option<String>,
25}
26
27impl AgentOvenClient {
28 pub fn new(base_url: &str) -> anyhow::Result<Self> {
30 Ok(Self {
31 base_url: Url::parse(base_url)?,
32 http: Client::new(),
33 api_key: None,
34 kitchen_id: None,
35 })
36 }
37
38 pub fn from_env() -> anyhow::Result<Self> {
42 let cfg = crate::config::AgentOvenConfig::load();
43 Self::from_config(&cfg)
44 }
45
46 pub fn from_config(cfg: &crate::config::AgentOvenConfig) -> anyhow::Result<Self> {
48 let mut client = Self::new(&cfg.url)?;
49 client.api_key = cfg.auth_credential().map(|s| s.to_string());
50 client.kitchen_id = cfg.kitchen.clone();
51 Ok(client)
52 }
53
54 pub fn with_api_key(mut self, key: impl Into<String>) -> Self {
56 self.api_key = Some(key.into());
57 self
58 }
59
60 pub fn with_kitchen(mut self, kitchen_id: impl Into<String>) -> Self {
62 self.kitchen_id = Some(kitchen_id.into());
63 self
64 }
65
66 pub async fn register(&self, agent: &Agent) -> anyhow::Result<Agent> {
73 let url = self.url("/api/v1/agents");
74
75 let mut body = serde_json::json!({
77 "name": agent.name,
78 "description": agent.description,
79 "framework": agent.framework,
80 });
81 let obj = body.as_object_mut().unwrap();
82
83 if !agent.version.is_empty() {
84 obj.insert("version".into(), agent.version.clone().into());
85 }
86 if !agent.model_provider.is_empty() {
87 obj.insert("model_provider".into(), agent.model_provider.clone().into());
88 }
89 if !agent.model_name.is_empty() {
90 obj.insert("model_name".into(), agent.model_name.clone().into());
91 }
92 if let Some(ref bp) = agent.backup_provider {
93 obj.insert("backup_provider".into(), bp.clone().into());
94 }
95 if let Some(ref bm) = agent.backup_model {
96 obj.insert("backup_model".into(), bm.clone().into());
97 }
98 if let Some(ref sp) = agent.system_prompt {
99 obj.insert("system_prompt".into(), sp.clone().into());
100 }
101 if let Some(mt) = agent.max_turns {
102 obj.insert("max_turns".into(), mt.into());
103 }
104 if !agent.skills.is_empty() {
105 obj.insert("skills".into(), serde_json::to_value(&agent.skills)?);
106 }
107 if !agent.ingredients.is_empty() {
108 obj.insert(
109 "ingredients".into(),
110 serde_json::to_value(&agent.ingredients)?,
111 );
112 }
113 if !agent.tags.is_empty() {
114 obj.insert("tags".into(), serde_json::to_value(&agent.tags)?);
115 }
116 if !agent.guardrails.is_empty() {
117 obj.insert(
118 "guardrails".into(),
119 serde_json::to_value(&agent.guardrails)?,
120 );
121 }
122 if agent.mode != AgentMode::default() {
123 obj.insert("mode".into(), serde_json::to_value(&agent.mode)?);
124 }
125
126 let resp = self
127 .authed_request(self.http.post(url))
128 .json(&body)
129 .send()
130 .await?
131 .error_for_status()?;
132 Ok(resp.json().await?)
133 }
134
135 pub async fn get_agent(&self, name: &str, version: Option<&str>) -> anyhow::Result<Agent> {
137 let path = match version {
138 Some(v) => format!("/api/v1/agents/{name}/versions/{v}"),
139 None => format!("/api/v1/agents/{name}"),
140 };
141 let url = self.url(&path);
142 let resp = self
143 .authed_request(self.http.get(url))
144 .send()
145 .await?
146 .error_for_status()?;
147 Ok(resp.json().await?)
148 }
149
150 pub async fn list_agents(&self) -> anyhow::Result<Vec<Agent>> {
152 let url = self.url("/api/v1/agents");
153 let resp = self
154 .authed_request(self.http.get(url))
155 .send()
156 .await?
157 .error_for_status()?;
158 Ok(resp.json().await?)
159 }
160
161 pub async fn bake(&self, agent: &Agent, environment: &str) -> anyhow::Result<Agent> {
163 let url = self.url(&format!("/api/v1/agents/{}/bake", agent.name));
164 let resp = self
165 .authed_request(self.http.post(url))
166 .json(&serde_json::json!({
167 "version": agent.version,
168 "environment": environment,
169 }))
170 .send()
171 .await?
172 .error_for_status()?;
173 Ok(resp.json().await?)
174 }
175
176 pub async fn rewarm(&self, name: &str) -> anyhow::Result<serde_json::Value> {
178 let url = self.url(&format!("/api/v1/agents/{name}/rewarm"));
179 let resp = self
180 .authed_request(self.http.post(url))
181 .send()
182 .await?
183 .error_for_status()?;
184 Ok(resp.json().await?)
185 }
186
187 pub async fn create_recipe(&self, recipe: &Recipe) -> anyhow::Result<Recipe> {
191 let url = self.url("/api/v1/recipes");
192 let resp = self
193 .authed_request(self.http.post(url))
194 .json(recipe)
195 .send()
196 .await?
197 .error_for_status()?;
198 Ok(resp.json().await?)
199 }
200
201 pub async fn bake_recipe(
203 &self,
204 recipe_name: &str,
205 input: serde_json::Value,
206 ) -> anyhow::Result<serde_json::Value> {
207 let url = self.url(&format!("/api/v1/recipes/{recipe_name}/bake"));
208 let resp = self
209 .authed_request(self.http.post(url))
210 .json(&input)
211 .send()
212 .await?
213 .error_for_status()?;
214 Ok(resp.json().await?)
215 }
216
217 pub async fn update_agent(
221 &self,
222 name: &str,
223 updates: serde_json::Value,
224 ) -> anyhow::Result<Agent> {
225 let url = self.url(&format!("/api/v1/agents/{name}"));
226 let resp = self
227 .authed_request(self.http.put(url))
228 .json(&updates)
229 .send()
230 .await?
231 .error_for_status()?;
232 Ok(resp.json().await?)
233 }
234
235 pub async fn delete_agent(&self, name: &str) -> anyhow::Result<()> {
237 let url = self.url(&format!("/api/v1/agents/{name}"));
238 self.authed_request(self.http.delete(url))
239 .send()
240 .await?
241 .error_for_status()?;
242 Ok(())
243 }
244
245 pub async fn recook_agent(
247 &self,
248 name: &str,
249 edits: serde_json::Value,
250 ) -> anyhow::Result<serde_json::Value> {
251 let url = self.url(&format!("/api/v1/agents/{name}/recook"));
252 let resp = self
253 .authed_request(self.http.post(url))
254 .json(&edits)
255 .send()
256 .await?
257 .error_for_status()?;
258 Ok(resp.json().await?)
259 }
260
261 pub async fn cool_agent(&self, name: &str) -> anyhow::Result<serde_json::Value> {
263 let url = self.url(&format!("/api/v1/agents/{name}/cool"));
264 let resp = self
265 .authed_request(self.http.post(url))
266 .send()
267 .await?
268 .error_for_status()?;
269 Ok(resp.json().await?)
270 }
271
272 pub async fn retire_agent(&self, name: &str) -> anyhow::Result<serde_json::Value> {
274 let url = self.url(&format!("/api/v1/agents/{name}/retire"));
275 let resp = self
276 .authed_request(self.http.post(url))
277 .send()
278 .await?
279 .error_for_status()?;
280 Ok(resp.json().await?)
281 }
282
283 pub async fn test_agent(
285 &self,
286 name: &str,
287 message: &str,
288 thinking: bool,
289 ) -> anyhow::Result<serde_json::Value> {
290 let url = self.url(&format!("/api/v1/agents/{name}/test"));
291 let resp = self
292 .authed_request(self.http.post(url))
293 .json(&serde_json::json!({
294 "message": message,
295 "thinking_enabled": thinking,
296 }))
297 .send()
298 .await?
299 .error_for_status()?;
300 Ok(resp.json().await?)
301 }
302
303 pub async fn invoke_agent(
305 &self,
306 name: &str,
307 message: &str,
308 variables: Option<serde_json::Value>,
309 thinking: bool,
310 ) -> anyhow::Result<serde_json::Value> {
311 let url = self.url(&format!("/api/v1/agents/{name}/invoke"));
312 let mut body = serde_json::json!({
313 "message": message,
314 "thinking_enabled": thinking,
315 });
316 if let Some(vars) = variables {
317 body["variables"] = vars;
318 }
319 let resp = self
320 .authed_request(self.http.post(url))
321 .json(&body)
322 .send()
323 .await?
324 .error_for_status()?;
325 Ok(resp.json().await?)
326 }
327
328 pub async fn agent_config(&self, name: &str) -> anyhow::Result<serde_json::Value> {
330 let url = self.url(&format!("/api/v1/agents/{name}/config"));
331 let resp = self
332 .authed_request(self.http.get(url))
333 .send()
334 .await?
335 .error_for_status()?;
336 Ok(resp.json().await?)
337 }
338
339 pub async fn agent_card(&self, name: &str) -> anyhow::Result<serde_json::Value> {
341 let url = self.url(&format!("/api/v1/agents/{name}/card"));
342 let resp = self
343 .authed_request(self.http.get(url))
344 .send()
345 .await?
346 .error_for_status()?;
347 Ok(resp.json().await?)
348 }
349
350 pub async fn agent_versions(&self, name: &str) -> anyhow::Result<Vec<serde_json::Value>> {
352 let url = self.url(&format!("/api/v1/agents/{name}/versions"));
353 let resp = self
354 .authed_request(self.http.get(url))
355 .send()
356 .await?
357 .error_for_status()?;
358 Ok(resp.json().await?)
359 }
360
361 pub async fn list_providers(&self) -> anyhow::Result<Vec<serde_json::Value>> {
365 let url = self.url("/api/v1/models/providers");
366 let resp = self
367 .authed_request(self.http.get(url))
368 .send()
369 .await?
370 .error_for_status()?;
371 Ok(resp.json().await?)
372 }
373
374 pub async fn add_provider(
376 &self,
377 provider: serde_json::Value,
378 ) -> anyhow::Result<serde_json::Value> {
379 let url = self.url("/api/v1/models/providers");
380 let resp = self
381 .authed_request(self.http.post(url))
382 .json(&provider)
383 .send()
384 .await?
385 .error_for_status()?;
386 Ok(resp.json().await?)
387 }
388
389 pub async fn get_provider(&self, name: &str) -> anyhow::Result<serde_json::Value> {
391 let url = self.url(&format!("/api/v1/models/providers/{name}"));
392 let resp = self
393 .authed_request(self.http.get(url))
394 .send()
395 .await?
396 .error_for_status()?;
397 Ok(resp.json().await?)
398 }
399
400 pub async fn update_provider(
402 &self,
403 name: &str,
404 provider: serde_json::Value,
405 ) -> anyhow::Result<serde_json::Value> {
406 let url = self.url(&format!("/api/v1/models/providers/{name}"));
407 let resp = self
408 .authed_request(self.http.put(url))
409 .json(&provider)
410 .send()
411 .await?
412 .error_for_status()?;
413 Ok(resp.json().await?)
414 }
415
416 pub async fn delete_provider(&self, name: &str) -> anyhow::Result<()> {
418 let url = self.url(&format!("/api/v1/models/providers/{name}"));
419 self.authed_request(self.http.delete(url))
420 .send()
421 .await?
422 .error_for_status()?;
423 Ok(())
424 }
425
426 pub async fn test_provider(&self, name: &str) -> anyhow::Result<serde_json::Value> {
428 let url = self.url(&format!("/api/v1/models/providers/{name}/test"));
429 let resp = self
430 .authed_request(self.http.post(url))
431 .send()
432 .await?
433 .error_for_status()?;
434 Ok(resp.json().await?)
435 }
436
437 pub async fn discover_provider(&self, name: &str) -> anyhow::Result<serde_json::Value> {
439 let url = self.url(&format!("/api/v1/models/providers/{name}/discover"));
440 let resp = self
441 .authed_request(self.http.post(url))
442 .send()
443 .await?
444 .error_for_status()?;
445 Ok(resp.json().await?)
446 }
447
448 pub async fn list_tools(&self) -> anyhow::Result<Vec<serde_json::Value>> {
452 let url = self.url("/api/v1/tools");
453 let resp = self
454 .authed_request(self.http.get(url))
455 .send()
456 .await?
457 .error_for_status()?;
458 Ok(resp.json().await?)
459 }
460
461 pub async fn add_tool(&self, tool: serde_json::Value) -> anyhow::Result<serde_json::Value> {
463 let url = self.url("/api/v1/tools");
464 let resp = self
465 .authed_request(self.http.post(url))
466 .json(&tool)
467 .send()
468 .await?
469 .error_for_status()?;
470 Ok(resp.json().await?)
471 }
472
473 pub async fn bulk_add_tools(
475 &self,
476 payload: serde_json::Value,
477 ) -> anyhow::Result<serde_json::Value> {
478 let url = self.url("/api/v1/tools/bulk");
479 let resp = self
480 .authed_request(self.http.post(url))
481 .json(&payload)
482 .send()
483 .await?
484 .error_for_status()?;
485 Ok(resp.json().await?)
486 }
487
488 pub async fn get_tool(&self, name: &str) -> anyhow::Result<serde_json::Value> {
490 let url = self.url(&format!("/api/v1/tools/{name}"));
491 let resp = self
492 .authed_request(self.http.get(url))
493 .send()
494 .await?
495 .error_for_status()?;
496 Ok(resp.json().await?)
497 }
498
499 pub async fn update_tool(
501 &self,
502 name: &str,
503 tool: serde_json::Value,
504 ) -> anyhow::Result<serde_json::Value> {
505 let url = self.url(&format!("/api/v1/tools/{name}"));
506 let resp = self
507 .authed_request(self.http.put(url))
508 .json(&tool)
509 .send()
510 .await?
511 .error_for_status()?;
512 Ok(resp.json().await?)
513 }
514
515 pub async fn delete_tool(&self, name: &str) -> anyhow::Result<()> {
517 let url = self.url(&format!("/api/v1/tools/{name}"));
518 self.authed_request(self.http.delete(url))
519 .send()
520 .await?
521 .error_for_status()?;
522 Ok(())
523 }
524
525 pub async fn list_prompts(&self) -> anyhow::Result<Vec<serde_json::Value>> {
529 let url = self.url("/api/v1/prompts");
530 let resp = self
531 .authed_request(self.http.get(url))
532 .send()
533 .await?
534 .error_for_status()?;
535 Ok(resp.json().await?)
536 }
537
538 pub async fn add_prompt(&self, prompt: serde_json::Value) -> anyhow::Result<serde_json::Value> {
540 let url = self.url("/api/v1/prompts");
541 let resp = self
542 .authed_request(self.http.post(url))
543 .json(&prompt)
544 .send()
545 .await?
546 .error_for_status()?;
547 Ok(resp.json().await?)
548 }
549
550 pub async fn get_prompt(&self, name: &str) -> anyhow::Result<serde_json::Value> {
552 let url = self.url(&format!("/api/v1/prompts/{name}"));
553 let resp = self
554 .authed_request(self.http.get(url))
555 .send()
556 .await?
557 .error_for_status()?;
558 Ok(resp.json().await?)
559 }
560
561 pub async fn update_prompt(
563 &self,
564 name: &str,
565 prompt: serde_json::Value,
566 ) -> anyhow::Result<serde_json::Value> {
567 let url = self.url(&format!("/api/v1/prompts/{name}"));
568 let resp = self
569 .authed_request(self.http.put(url))
570 .json(&prompt)
571 .send()
572 .await?
573 .error_for_status()?;
574 Ok(resp.json().await?)
575 }
576
577 pub async fn delete_prompt(&self, name: &str) -> anyhow::Result<()> {
579 let url = self.url(&format!("/api/v1/prompts/{name}"));
580 self.authed_request(self.http.delete(url))
581 .send()
582 .await?
583 .error_for_status()?;
584 Ok(())
585 }
586
587 pub async fn validate_prompt(&self, name: &str) -> anyhow::Result<serde_json::Value> {
589 let url = self.url(&format!("/api/v1/prompts/{name}/validate"));
590 let resp = self
591 .authed_request(self.http.post(url))
592 .send()
593 .await?
594 .error_for_status()?;
595 Ok(resp.json().await?)
596 }
597
598 pub async fn prompt_versions(&self, name: &str) -> anyhow::Result<Vec<serde_json::Value>> {
600 let url = self.url(&format!("/api/v1/prompts/{name}/versions"));
601 let resp = self
602 .authed_request(self.http.get(url))
603 .send()
604 .await?
605 .error_for_status()?;
606 Ok(resp.json().await?)
607 }
608
609 pub async fn list_kitchens(&self) -> anyhow::Result<Vec<serde_json::Value>> {
613 let url = self.url("/api/v1/kitchens");
614 let resp = self
615 .authed_request(self.http.get(url))
616 .send()
617 .await?
618 .error_for_status()?;
619 Ok(resp.json().await?)
620 }
621
622 pub async fn get_kitchen(&self, id: &str) -> anyhow::Result<serde_json::Value> {
624 let url = self.url(&format!("/api/v1/kitchens/{id}"));
625 let resp = self
626 .authed_request(self.http.get(url))
627 .send()
628 .await?
629 .error_for_status()?;
630 Ok(resp.json().await?)
631 }
632
633 pub async fn create_kitchen(
635 &self,
636 kitchen: serde_json::Value,
637 ) -> anyhow::Result<serde_json::Value> {
638 let url = self.url("/api/v1/kitchens");
639 let resp = self
640 .authed_request(self.http.post(url))
641 .json(&kitchen)
642 .send()
643 .await?
644 .error_for_status()?;
645 Ok(resp.json().await?)
646 }
647
648 pub async fn delete_kitchen(&self, id: &str) -> anyhow::Result<()> {
650 let url = self.url(&format!("/api/v1/kitchens/{id}"));
651 self.authed_request(self.http.delete(url))
652 .send()
653 .await?
654 .error_for_status()?;
655 Ok(())
656 }
657
658 pub async fn server_info(&self) -> anyhow::Result<serde_json::Value> {
661 let url = self.url("/api/v1/info");
662 let resp = self
663 .authed_request(self.http.get(url))
664 .send()
665 .await?
666 .error_for_status()?;
667 Ok(resp.json().await?)
668 }
669
670 pub async fn get_settings(&self) -> anyhow::Result<serde_json::Value> {
672 let url = self.url("/api/v1/settings");
673 let resp = self
674 .authed_request(self.http.get(url))
675 .send()
676 .await?
677 .error_for_status()?;
678 Ok(resp.json().await?)
679 }
680
681 pub async fn update_settings(
683 &self,
684 settings: serde_json::Value,
685 ) -> anyhow::Result<serde_json::Value> {
686 let url = self.url("/api/v1/settings");
687 let resp = self
688 .authed_request(self.http.put(url))
689 .json(&settings)
690 .send()
691 .await?
692 .error_for_status()?;
693 Ok(resp.json().await?)
694 }
695
696 pub async fn list_sessions(&self, agent_name: &str) -> anyhow::Result<Vec<serde_json::Value>> {
700 let url = self.url(&format!("/api/v1/agents/{agent_name}/sessions"));
701 let resp = self
702 .authed_request(self.http.get(url))
703 .send()
704 .await?
705 .error_for_status()?;
706 Ok(resp.json().await?)
707 }
708
709 pub async fn create_session(&self, agent_name: &str) -> anyhow::Result<serde_json::Value> {
711 let url = self.url(&format!("/api/v1/agents/{agent_name}/sessions"));
712 let resp = self
713 .authed_request(self.http.post(url))
714 .json(&serde_json::json!({}))
715 .send()
716 .await?
717 .error_for_status()?;
718 Ok(resp.json().await?)
719 }
720
721 pub async fn get_session(
723 &self,
724 agent_name: &str,
725 session_id: &str,
726 ) -> anyhow::Result<serde_json::Value> {
727 let url = self.url(&format!(
728 "/api/v1/agents/{agent_name}/sessions/{session_id}"
729 ));
730 let resp = self
731 .authed_request(self.http.get(url))
732 .send()
733 .await?
734 .error_for_status()?;
735 Ok(resp.json().await?)
736 }
737
738 pub async fn delete_session(&self, agent_name: &str, session_id: &str) -> anyhow::Result<()> {
740 let url = self.url(&format!(
741 "/api/v1/agents/{agent_name}/sessions/{session_id}"
742 ));
743 self.authed_request(self.http.delete(url))
744 .send()
745 .await?
746 .error_for_status()?;
747 Ok(())
748 }
749
750 pub async fn send_session_message(
752 &self,
753 agent_name: &str,
754 session_id: &str,
755 message: &str,
756 thinking: bool,
757 ) -> anyhow::Result<serde_json::Value> {
758 let url = self.url(&format!(
759 "/api/v1/agents/{agent_name}/sessions/{session_id}/messages"
760 ));
761 let resp = self
762 .authed_request(self.http.post(url))
763 .json(&serde_json::json!({
764 "message": message,
765 "thinking_enabled": thinking,
766 }))
767 .send()
768 .await?
769 .error_for_status()?;
770 Ok(resp.json().await?)
771 }
772
773 pub async fn rag_query(&self, query: serde_json::Value) -> anyhow::Result<serde_json::Value> {
777 let url = self.url("/api/v1/rag/query");
778 let resp = self
779 .authed_request(self.http.post(url))
780 .json(&query)
781 .send()
782 .await?
783 .error_for_status()?;
784 Ok(resp.json().await?)
785 }
786
787 pub async fn rag_ingest(
789 &self,
790 request: serde_json::Value,
791 ) -> anyhow::Result<serde_json::Value> {
792 let url = self.url("/api/v1/rag/ingest");
793 let resp = self
794 .authed_request(self.http.post(url))
795 .json(&request)
796 .send()
797 .await?
798 .error_for_status()?;
799 Ok(resp.json().await?)
800 }
801
802 pub async fn list_traces(
806 &self,
807 agent: Option<&str>,
808 limit: u32,
809 ) -> anyhow::Result<Vec<serde_json::Value>> {
810 let mut url = self.url("/api/v1/traces");
811 {
812 let mut query = url.query_pairs_mut();
813 query.append_pair("limit", &limit.to_string());
814 if let Some(a) = agent {
815 query.append_pair("agent", a);
816 }
817 }
818 let resp = self
819 .authed_request(self.http.get(url))
820 .send()
821 .await?
822 .error_for_status()?;
823 Ok(resp.json().await?)
824 }
825
826 pub async fn get_trace(&self, trace_id: &str) -> anyhow::Result<serde_json::Value> {
828 let url = self.url(&format!("/api/v1/traces/{trace_id}"));
829 let resp = self
830 .authed_request(self.http.get(url))
831 .send()
832 .await?
833 .error_for_status()?;
834 Ok(resp.json().await?)
835 }
836
837 pub async fn model_catalog(&self) -> anyhow::Result<Vec<serde_json::Value>> {
841 let url = self.url("/api/v1/models/catalog");
842 let resp = self
843 .authed_request(self.http.get(url))
844 .send()
845 .await?
846 .error_for_status()?;
847 Ok(resp.json().await?)
848 }
849
850 pub async fn catalog_refresh(&self) -> anyhow::Result<serde_json::Value> {
852 let url = self.url("/api/v1/models/catalog/refresh");
853 let resp = self
854 .authed_request(self.http.post(url))
855 .send()
856 .await?
857 .error_for_status()?;
858 Ok(resp.json().await?)
859 }
860
861 pub async fn model_cost(&self) -> anyhow::Result<serde_json::Value> {
863 let url = self.url("/api/v1/models/cost");
864 let resp = self
865 .authed_request(self.http.get(url))
866 .send()
867 .await?
868 .error_for_status()?;
869 Ok(resp.json().await?)
870 }
871
872 pub async fn get_recipe(&self, name: &str) -> anyhow::Result<serde_json::Value> {
876 let url = self.url(&format!("/api/v1/recipes/{name}"));
877 let resp = self
878 .authed_request(self.http.get(url))
879 .send()
880 .await?
881 .error_for_status()?;
882 Ok(resp.json().await?)
883 }
884
885 pub async fn list_recipes(&self) -> anyhow::Result<Vec<serde_json::Value>> {
887 let url = self.url("/api/v1/recipes");
888 let resp = self
889 .authed_request(self.http.get(url))
890 .send()
891 .await?
892 .error_for_status()?;
893 Ok(resp.json().await?)
894 }
895
896 pub async fn delete_recipe(&self, name: &str) -> anyhow::Result<()> {
898 let url = self.url(&format!("/api/v1/recipes/{name}"));
899 self.authed_request(self.http.delete(url))
900 .send()
901 .await?
902 .error_for_status()?;
903 Ok(())
904 }
905
906 pub async fn recipe_runs(&self, name: &str) -> anyhow::Result<Vec<serde_json::Value>> {
908 let url = self.url(&format!("/api/v1/recipes/{name}/runs"));
909 let resp = self
910 .authed_request(self.http.get(url))
911 .send()
912 .await?
913 .error_for_status()?;
914 Ok(resp.json().await?)
915 }
916
917 pub async fn approve_gate(
919 &self,
920 recipe: &str,
921 run_id: &str,
922 step_name: &str,
923 approved: bool,
924 comment: Option<&str>,
925 ) -> anyhow::Result<serde_json::Value> {
926 let url = self.url(&format!(
927 "/api/v1/recipes/{recipe}/runs/{run_id}/gates/{step_name}/approve"
928 ));
929 let mut body = serde_json::json!({ "approved": approved });
930 if let Some(c) = comment {
931 body["comment"] = serde_json::json!(c);
932 }
933 let resp = self
934 .authed_request(self.http.post(url))
935 .json(&body)
936 .send()
937 .await?
938 .error_for_status()?;
939 Ok(resp.json().await?)
940 }
941
942 pub async fn list_audit(&self, limit: u32) -> anyhow::Result<Vec<serde_json::Value>> {
946 let mut url = self.url("/api/v1/audit");
947 url.query_pairs_mut()
948 .append_pair("limit", &limit.to_string());
949 let resp = self
950 .authed_request(self.http.get(url))
951 .send()
952 .await?
953 .error_for_status()?;
954 Ok(resp.json().await?)
955 }
956
957 pub async fn guardrail_kinds(&self) -> anyhow::Result<Vec<serde_json::Value>> {
961 let url = self.url("/api/v1/guardrails/kinds");
962 let resp = self
963 .authed_request(self.http.get(url))
964 .send()
965 .await?
966 .error_for_status()?;
967 Ok(resp.json().await?)
968 }
969
970 pub async fn raw_get<T: serde::de::DeserializeOwned>(&self, path: &str) -> anyhow::Result<T> {
974 let url = self.url(path);
975 let resp = self
976 .authed_request(self.http.get(url))
977 .send()
978 .await?
979 .error_for_status()?;
980 Ok(resp.json().await?)
981 }
982
983 pub async fn raw_post<T: serde::de::DeserializeOwned>(
985 &self,
986 path: &str,
987 body: &serde_json::Value,
988 ) -> anyhow::Result<T> {
989 let url = self.url(path);
990 let resp = self
991 .authed_request(self.http.post(url))
992 .json(body)
993 .send()
994 .await?
995 .error_for_status()?;
996 Ok(resp.json().await?)
997 }
998
999 pub async fn raw_delete(&self, path: &str) -> anyhow::Result<()> {
1001 let url = self.url(path);
1002 self.authed_request(self.http.delete(url))
1003 .send()
1004 .await?
1005 .error_for_status()?;
1006 Ok(())
1007 }
1008
1009 fn url(&self, path: &str) -> Url {
1012 self.base_url.join(path).expect("Invalid URL path")
1013 }
1014
1015 fn authed_request(&self, builder: reqwest::RequestBuilder) -> reqwest::RequestBuilder {
1016 let mut b = builder;
1017 if let Some(ref key) = self.api_key {
1018 b = b.bearer_auth(key);
1019 }
1020 if let Some(ref kitchen) = self.kitchen_id {
1021 b = b.header("X-Kitchen-Id", kitchen.as_str());
1022 }
1023 b
1024 }
1025}