stasis-rs 0.3.0

Durable AI orchestration framework with runtime jobs, lineage, and memory integration
Documentation
use async_trait::async_trait;
use genai::chat::ChatRequest;

use crate::domain::errors::Result;
use crate::infrastructure::llm::genai_chat_client::GenaiChatClient;
use crate::ports::outbound::ai_chat_client::AiChatClient;
use crate::ports::outbound::llm_gateway::LlmGateway;

#[derive(Clone, Debug)]
pub struct GenaiLlmGateway {
    chat_client: GenaiChatClient,
}

impl GenaiLlmGateway {
    pub fn new(model: impl Into<String>) -> Self {
        Self {
            chat_client: GenaiChatClient::new(model),
        }
    }

    pub fn from_env() -> Self {
        Self {
            chat_client: GenaiChatClient::from_env(),
        }
    }

    pub fn model(&self) -> &str {
        self.chat_client.model()
    }
}

#[async_trait]
impl LlmGateway for GenaiLlmGateway {
    async fn complete(&self, prompt: &str) -> Result<String> {
        let response = self
            .chat_client
            .complete(ChatRequest::from_user(prompt), None)
            .await?;

        let text = response
            .into_first_text()
            .map(|v| v.trim().to_string())
            .filter(|v| !v.is_empty())
            .ok_or_else(|| {
                crate::domain::errors::StasisError::PortFailure(format!(
                    "genai completion returned empty text for model '{}'",
                    self.model()
                ))
            })?;

        Ok(text)
    }
}

#[cfg(test)]
mod tests {
    use super::GenaiLlmGateway;

    #[test]
    fn gateway_construction_keeps_model_name() {
        let gateway = GenaiLlmGateway::new("gpt-4o-mini");
        assert_eq!(gateway.model(), "gpt-4o-mini");
    }
}