anda_core 0.16.1

Core types and traits for Anda -- an AI agent framework built with Rust, powered by ICP and TEEs.
Documentation
use serde::{Deserialize, Serialize};
use std::fmt;

use crate::{
    AgentOutput, BoxError, ContentPart, Document, Documents, FunctionDefinition, Json, Message,
    Resource,
};

/// LLM completion capability exposed by an agent context.
pub trait CompletionFeatures: Sized {
    /// Generates a completion for the request and optional resources.
    fn completion(
        &self,
        req: CompletionRequest,
        resources: Vec<Resource>,
    ) -> impl Future<Output = Result<AgentOutput, BoxError>> + Send;

    /// Returns the name of the model.
    fn model_name(&self) -> String;
}

/// Provider-agnostic reasoning/thinking effort requested for a completion.
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum ModelEffort {
    /// Smallest reasoning budget supported by the provider.
    Minimal,
    /// Low reasoning budget.
    Low,
    /// Medium reasoning budget.
    Medium,
    /// High reasoning budget.
    High,
    /// Maximum reasoning budget supported by the provider.
    Max,
}

impl ModelEffort {
    /// Returns the lowercase wire value for this effort level.
    pub fn as_str(self) -> &'static str {
        match self {
            Self::Minimal => "minimal",
            Self::Low => "low",
            Self::Medium => "medium",
            Self::High => "high",
            Self::Max => "max",
        }
    }
}

impl fmt::Display for ModelEffort {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(self.as_str())
    }
}

/// Provider-neutral completion request.
#[derive(Debug, Clone, Default)]
pub struct CompletionRequest {
    /// System instructions sent to the completion provider.
    pub instructions: String,

    /// Role used for `prompt` and `content`; defaults to `user` when omitted.
    pub role: Option<String>,

    /// The chat history to be sent to the completion model provider.
    ///
    /// This is the provider-neutral, persistable view (see [`ContentPart`]). Adapters
    /// convert it into provider messages, which is lossy by design: a provider's per-turn
    /// intermediate state does not survive the round trip. Use [`Self::raw_history`] to keep
    /// a round lossless.
    pub chat_history: Vec<Message>,

    /// Provider-specific history used by model adapters. It is empty for most callers.
    ///
    /// # Why this exists
    ///
    /// Some providers require their own opaque per-turn state to be echoed back on the next
    /// request — Anthropic's `thinking.signature`, Gemini's `thoughtSignature`. Round-tripping
    /// that through [`ContentPart`] would mean polluting the persisted wire type with
    /// provider-specific fields, so instead the adapter stores the provider's own message
    /// JSON here verbatim.
    ///
    /// # The contract
    ///
    /// - Each turn, the runner appends the response's
    ///   [`AgentOutput::raw_history`](crate::model::AgentOutput::raw_history) onto this field
    ///   and clears [`Self::chat_history`], so `raw_history` accumulates across one complete
    ///   reasoning round.
    /// - Every adapter must send `raw_history` **before** the messages converted from
    ///   `chat_history`, so provider state stays intact for the whole round.
    /// - It belongs to the model that produced it. Adapters send it verbatim, so handing one
    ///   provider another's messages makes the request unparseable and the provider rejects the
    ///   whole call. A caller that reroutes a live conversation to a different model must clear
    ///   this field and replay `chat_history` instead.
    /// - It is scoped to one in-process round only: the engine clears it at the RPC boundary
    ///   and it is `#[serde(skip)]` on `AgentOutput`, so it never reaches a persisted
    ///   conversation or a remote caller.
    ///
    /// A resumed conversation therefore replays from `chat_history` alone and legitimately
    /// carries no provider intermediate state. Adapters must tolerate that rather than emit a
    /// block the provider will reject.
    pub raw_history: Vec<Json>,

    /// The documents to embed into the prompt.
    pub documents: Documents,

    /// Prompt sent to the completion provider using `role`.
    /// It can be empty.
    pub prompt: String,

    /// The content parts to be sent to the completion model provider.
    /// It can be empty.
    pub content: Vec<ContentPart>,

    /// The tools to be sent to the completion model provider.
    pub tools: Vec<FunctionDefinition>,

    /// Whether the tool choice is required.
    pub tool_choice_required: bool,

    /// Sampling temperature requested from the provider, usually in the `[0.0, 2.0]` range.
    pub temperature: Option<f64>,

    /// Upper bound for the number of tokens that can be generated for a response.
    pub max_output_tokens: Option<usize>,

    /// An object specifying the JSON format that the model must output.
    pub output_schema: Option<Json>,

    /// The stop sequence to be sent to the completion model provider.
    pub stop: Option<Vec<String>>,

    /// The name or label of the model to be used for the completion request.
    pub model: Option<String>,

    /// Optional reasoning/thinking effort for providers and models that support it.
    pub effort: Option<ModelEffort>,
}

impl CompletionRequest {
    /// Adds a document to the request.
    pub fn context(mut self, id: String, text: String) -> Self {
        self.documents.append(Document::from_text(id, text));
        self
    }

    /// Adds multiple documents to the request.
    pub fn append_documents(mut self, docs: Documents) -> Self {
        self.documents.extend(docs);
        self
    }

    /// Adds multiple tools to the request.
    pub fn append_tools(mut self, tools: Vec<FunctionDefinition>) -> Self {
        self.tools.extend(tools);
        self
    }
}