tirea_agentos_server/service/
api.rs1use axum::http::StatusCode;
2use axum::response::{IntoResponse, Response};
3use axum::Json;
4use std::sync::Arc;
5use tirea_agentos::contracts::storage::{MailboxStore, ThreadReader};
6use tirea_agentos::runtime::{AgentOs, AgentOsRunError};
7
8use super::mailbox_service::MailboxService;
9
10#[derive(Clone)]
11pub struct AppState {
12 pub os: Arc<AgentOs>,
13 pub read_store: Arc<dyn ThreadReader>,
14 pub mailbox_service: Arc<MailboxService>,
15}
16
17impl AppState {
18 pub fn new(
19 os: Arc<AgentOs>,
20 read_store: Arc<dyn ThreadReader>,
21 mailbox_service: Arc<MailboxService>,
22 ) -> Self {
23 Self {
24 os,
25 read_store,
26 mailbox_service,
27 }
28 }
29
30 pub fn mailbox_store(&self) -> &Arc<dyn MailboxStore> {
31 self.mailbox_service.mailbox_store()
32 }
33}
34
35#[derive(Debug, thiserror::Error)]
36pub enum ApiError {
37 #[error("agent not found: {0}")]
38 AgentNotFound(String),
39
40 #[error("thread not found: {0}")]
41 ThreadNotFound(String),
42
43 #[error("run not found: {0}")]
44 RunNotFound(String),
45
46 #[error("bad request: {0}")]
47 BadRequest(String),
48
49 #[error("internal error: {0}")]
50 Internal(String),
51}
52
53impl IntoResponse for ApiError {
54 fn into_response(self) -> Response {
55 let (code, msg) = match &self {
56 ApiError::AgentNotFound(_) => (StatusCode::NOT_FOUND, self.to_string()),
57 ApiError::ThreadNotFound(_) => (StatusCode::NOT_FOUND, self.to_string()),
58 ApiError::RunNotFound(_) => (StatusCode::NOT_FOUND, self.to_string()),
59 ApiError::BadRequest(_) => (StatusCode::BAD_REQUEST, self.to_string()),
60 ApiError::Internal(_) => (StatusCode::INTERNAL_SERVER_ERROR, self.to_string()),
61 };
62 let body = Json(serde_json::json!({ "error": msg }));
63 (code, body).into_response()
64 }
65}
66
67impl From<AgentOsRunError> for ApiError {
68 fn from(e: AgentOsRunError) -> Self {
69 match e {
70 AgentOsRunError::Resolve(
71 tirea_agentos::runtime::AgentOsResolveError::AgentNotFound(id),
72 ) => ApiError::AgentNotFound(id),
73 AgentOsRunError::Resolve(other) => ApiError::BadRequest(other.to_string()),
74 other => ApiError::Internal(other.to_string()),
75 }
76 }
77}
78
79pub fn normalize_optional_id(value: Option<String>) -> Option<String> {
80 value.and_then(|raw| {
81 let trimmed = raw.trim();
82 if trimmed.is_empty() {
83 None
84 } else {
85 Some(trimmed.to_string())
86 }
87 })
88}