Initial scaffold: GyanDoot v2.0 in Rust
Full Cargo workspace with 11 crates: - gyandoot-core: Shared types, config, error types - gyandoot-agent: Agent loop, tool execution, context compression - gyandoot-providers: LLM provider abstraction (OpenAI, Anthropic, Ollama) - gyandoot-memory: MemPalace persistent memory system - gyandoot-platform: Platform adapters (Telegram, Discord, Webhook, CLI) - gyandoot-security: Credential vault, sanitizer, threat detector - gyandoot-kanban: Task decomposition, swarm orchestration, diagnostics - gyandoot-subagents: Sub-agent spawning and orchestration - gyandoot-skills: Skills discovery, loading, preprocessing, bundles - gyandoot-profile: Multi-agent profile manager - gyandoot-gateway: Gateway sessions, routing, access control Plus: CLI entry point with clap, cross-platform installer script, KANBAN.md, .gnpl/AGENTS.md Workspace compiles clean: cargo check passes with 0 errors.
This commit is contained in:
220
crates/gyandoot-gateway/src/lib.rs
Normal file
220
crates/gyandoot-gateway/src/lib.rs
Normal file
@@ -0,0 +1,220 @@
|
||||
//! # Gyandoot Gateway
|
||||
//!
|
||||
//! Gateway for platform sessions, message routing, and streaming delivery.
|
||||
//! Connects platform adapters (Telegram, Discord, Webhook) to the agent core.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
use uuid::Uuid;
|
||||
use chrono::Utc;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::info;
|
||||
|
||||
/// Unique session identifier.
|
||||
pub type SessionId = Uuid;
|
||||
|
||||
/// Source of a session (which platform + chat).
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SessionSource {
|
||||
pub platform: String,
|
||||
pub chat_id: String,
|
||||
pub thread_id: Option<String>,
|
||||
}
|
||||
|
||||
/// A gateway session with lifecycle tracking.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GatewaySession {
|
||||
pub id: SessionId,
|
||||
pub source: SessionSource,
|
||||
pub created_at: chrono::DateTime<Utc>,
|
||||
pub last_active: chrono::DateTime<Utc>,
|
||||
pub message_count: u64,
|
||||
pub active: bool,
|
||||
}
|
||||
|
||||
impl GatewaySession {
|
||||
pub fn new(source: SessionSource) -> Self {
|
||||
Self {
|
||||
id: Uuid::new_v4(),
|
||||
source,
|
||||
created_at: Utc::now(),
|
||||
last_active: Utc::now(),
|
||||
message_count: 0,
|
||||
active: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Delivery target specification.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DeliveryTarget {
|
||||
pub platform: String,
|
||||
pub chat_id: String,
|
||||
pub thread_id: Option<String>,
|
||||
}
|
||||
|
||||
/// Parses a target string like "telegram:-1001234567890:17585".
|
||||
pub fn parse_delivery_target(target: &str) -> Option<DeliveryTarget> {
|
||||
let parts: Vec<&str> = target.splitn(3, ':').collect();
|
||||
match parts.len() {
|
||||
1 => Some(DeliveryTarget {
|
||||
platform: parts[0].to_string(),
|
||||
chat_id: String::new(),
|
||||
thread_id: None,
|
||||
}),
|
||||
2 => Some(DeliveryTarget {
|
||||
platform: parts[0].to_string(),
|
||||
chat_id: parts[1].to_string(),
|
||||
thread_id: None,
|
||||
}),
|
||||
3 => Some(DeliveryTarget {
|
||||
platform: parts[0].to_string(),
|
||||
chat_id: parts[1].to_string(),
|
||||
thread_id: Some(parts[2].to_string()),
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Three-gate access control for incoming messages.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AccessControl {
|
||||
pub block_threats: bool,
|
||||
pub allowed_users: Option<Vec<String>>,
|
||||
pub allowed_channels: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
impl AccessControl {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
block_threats: true,
|
||||
allowed_users: None,
|
||||
allowed_channels: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_user(&self, username: &str) -> bool {
|
||||
match &self.allowed_users {
|
||||
Some(users) => users.iter().any(|u| u.eq_ignore_ascii_case(username)),
|
||||
None => true, // empty = allow all
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_channel(&self, channel: &str) -> bool {
|
||||
match &self.allowed_channels {
|
||||
Some(channels) => channels.iter().any(|c| c == channel),
|
||||
None => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AccessControl {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// The main gateway runner — orchestrates platform connections and message flow.
|
||||
pub struct GatewayRunner {
|
||||
pub sessions: Arc<Mutex<HashMap<SessionId, GatewaySession>>>,
|
||||
pub access_control: AccessControl,
|
||||
running: Arc<Mutex<bool>>,
|
||||
}
|
||||
|
||||
impl GatewayRunner {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
sessions: Arc::new(Mutex::new(HashMap::new())),
|
||||
access_control: AccessControl::new(),
|
||||
running: Arc::new(Mutex::new(false)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_access_control(mut self, ac: AccessControl) -> Self {
|
||||
self.access_control = ac;
|
||||
self
|
||||
}
|
||||
|
||||
pub async fn start(&self) -> anyhow::Result<()> {
|
||||
let mut running = self.running.lock().await;
|
||||
*running = true;
|
||||
info!("Gateway started");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn stop(&self) {
|
||||
let mut running = self.running.lock().await;
|
||||
*running = false;
|
||||
info!("Gateway stopped");
|
||||
}
|
||||
|
||||
pub async fn is_running(&self) -> bool {
|
||||
*self.running.lock().await
|
||||
}
|
||||
|
||||
pub async fn create_session(&self, source: SessionSource) -> SessionId {
|
||||
let session = GatewaySession::new(source);
|
||||
let id = session.id;
|
||||
let mut sessions = self.sessions.lock().await;
|
||||
sessions.insert(id, session);
|
||||
id
|
||||
}
|
||||
|
||||
pub async fn get_session(&self, id: &SessionId) -> Option<GatewaySession> {
|
||||
let sessions = self.sessions.lock().await;
|
||||
sessions.get(id).cloned()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for GatewayRunner {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_session_creation() {
|
||||
let source = SessionSource {
|
||||
platform: "telegram".into(),
|
||||
chat_id: "-10012345".into(),
|
||||
thread_id: None,
|
||||
};
|
||||
let session = GatewaySession::new(source);
|
||||
assert!(session.active);
|
||||
assert_eq!(session.message_count, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_delivery_target_parsing() {
|
||||
let target = parse_delivery_target("telegram:-100123:17585").unwrap();
|
||||
assert_eq!(target.platform, "telegram");
|
||||
assert_eq!(target.chat_id, "-100123");
|
||||
assert_eq!(target.thread_id, Some("17585".into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_access_control() {
|
||||
let ac = AccessControl {
|
||||
block_threats: true,
|
||||
allowed_users: Some(vec!["kapil".into()]),
|
||||
allowed_channels: None,
|
||||
};
|
||||
assert!(ac.check_user("Kapil"));
|
||||
assert!(!ac.check_user("maya"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_gateway_lifecycle() {
|
||||
let gw = GatewayRunner::new();
|
||||
assert!(!gw.is_running().await);
|
||||
gw.start().await.unwrap();
|
||||
assert!(gw.is_running().await);
|
||||
gw.stop().await;
|
||||
assert!(!gw.is_running().await);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user