apollo-agent 0.4.0

Local-first Rust AI agent runtime — Telegram-first, trait-driven, SurrealDB + RocksDB state layer.
Documentation
//! Core Channel trait — messaging interface.
//! Inspired by ZeroClaw's channel abstraction + NanoClaw's group isolation.

use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use tokio::sync::mpsc;

/// Incoming message from a channel.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IncomingMessage {
    pub id: String,
    pub sender_id: String,
    pub sender_name: Option<String>,
    pub chat_id: String,
    pub text: String,
    pub is_group: bool,
    pub reply_to: Option<String>,
    pub timestamp: chrono::DateTime<chrono::Utc>,
}

/// Outgoing message to a channel.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OutgoingMessage {
    pub chat_id: String,
    pub text: String,
    pub reply_to: Option<String>,
}

/// The core Channel trait.
/// Implement for each messaging platform (Telegram, Discord, CLI, WebSocket, etc.)
#[async_trait]
pub trait Channel: Send + Sync {
    /// Channel name (e.g., "telegram", "discord", "cli")
    fn name(&self) -> &str;

    /// Start receiving messages. Returns a receiver for incoming messages.
    async fn start(&mut self) -> anyhow::Result<mpsc::Receiver<IncomingMessage>>;

    /// Send a message through this channel. Returns an optional message ID for tracking/editing.
    async fn send(&self, message: OutgoingMessage) -> anyhow::Result<Option<String>>;

    /// Send a typing indicator (or equivalent) to the user.
    async fn send_typing(&self, _chat_id: &str) -> anyhow::Result<()> {
        Ok(())
    }

    /// Edit a previously sent message if the channel supports it.
    async fn edit(&self, _chat_id: &str, _message_id: &str, _new_text: &str) -> anyhow::Result<()> {
        Ok(())
    }

    /// Whether this channel supports live draft updates (send → update → finalize).
    fn supports_draft_updates(&self) -> bool {
        false
    }

    /// Send an initial draft placeholder (e.g. "⏳"). Returns message ID for edits.
    async fn send_draft(&self, _chat_id: &str, _text: &str) -> anyhow::Result<Option<String>> {
        Ok(None)
    }

    /// Edit the draft with a live progress update (tool start/end). Rate-limited by impl.
    async fn update_draft_progress(
        &self,
        _chat_id: &str,
        _message_id: &str,
        _text: &str,
    ) -> anyhow::Result<()> {
        Ok(())
    }

    /// Finalize the draft with the full formatted response, replacing any progress text.
    async fn finalize_draft(
        &self,
        _chat_id: &str,
        _message_id: &str,
        _text: &str,
    ) -> anyhow::Result<()> {
        Ok(())
    }

    /// Stop the channel gracefully.
    async fn stop(&mut self) -> anyhow::Result<()>;
}