# Plugin System Type Architecture

**Created**: 2025-12-07
**Nanotime**: 1765166485033261056
**File ID**: 16681577588676290559

## Overview

This document defines the complete type system for the plugin-based context management architecture.

**Flow**: `Transport -> Hub -> Plugin -> Stream<T> -> Plugin -> Hub -> Transport`

The hub converts everything into a unified type that can be transformed for external transport.

---

## Type Hierarchy

### Layer 1: Transport Layer Types

**Location**: `src/transport/types.rs`

```rust
/// External JSON-RPC request from client
pub struct TransportRequest {
    pub jsonrpc: String,        // "2.0"
    pub method: String,          // e.g., "agent.chat"
    pub params: serde_json::Value,
    pub id: RequestId,
}

/// External JSON-RPC response to client
pub struct TransportResponse {
    pub jsonrpc: String,
    pub result: Option<serde_json::Value>,
    pub error: Option<TransportError>,
    pub id: RequestId,
}

/// Request identifier (can be string, number, or null)
pub enum RequestId {
    String(String),
    Number(i64),
    Null,
}

/// Error sent to client
pub struct TransportError {
    pub code: i32,
    pub message: String,
    pub data: Option<serde_json::Value>,
}

/// Streaming response (SSE)
pub struct StreamingTransportResponse {
    pub stream_id: String,
    pub sse_url: String,
    pub status: StreamStatus,
}

pub enum StreamStatus {
    Running,
    Completed,
    Failed,
}

/// SSE event sent to client
pub struct SseEvent {
    pub event: String,           // "chunk", "done", "error"
    pub data: serde_json::Value,
}
```

---

### Layer 2: Hub Types

**Location**: `src/hub/types.rs`

```rust
/// Hub's internal request representation
pub struct HubRequest {
    pub plugin_name: String,     // Extracted from method: "agent.chat" -> "agent"
    pub method_name: String,     // Extracted from method: "agent.chat" -> "chat"
    pub params: serde_json::Value,
    pub request_id: RequestId,
    pub path: PluginPath,        // Initialized as root
}

/// Hub's unified stream item type
/// All plugins must convert their Stream<T> to Stream<HubStreamItem>
pub enum HubStreamItem {
    /// Progress update
    Progress {
        path: PluginPath,
        message: String,
        percentage: Option<f32>,
    },

    /// Data chunk with type information
    Data {
        path: PluginPath,
        content_type: String,    // e.g., "AgentChatItem", "ToolResult"
        data: serde_json::Value, // Type-erased plugin data
    },

    /// Error occurred
    Error {
        path: PluginPath,
        error: String,
        recoverable: bool,
    },

    /// Stream completed successfully
    Done {
        path: PluginPath,
    },
}

/// Hub's response to transport layer
pub enum HubResponse {
    /// Immediate response (non-streaming)
    Immediate(serde_json::Value),

    /// Streaming response
    Stream {
        stream_id: String,
        receiver: mpsc::Receiver<HubStreamItem>,
    },
}
```

---

### Layer 3: Plugin Path Tracking

**Location**: `src/hub/path.rs`

```rust
/// Tracks the call path through nested plugins
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct PluginPath {
    /// Ordered list of plugin names in call chain
    /// Example: ["agent", "mcp_client", "filesystem"]
    segments: Vec<String>,
}

impl PluginPath {
    pub fn root(plugin_name: impl Into<String>) -> Self;
    pub fn extend(&self, plugin_name: impl Into<String>) -> Self;
    pub fn depth(&self) -> usize;
    pub fn to_string(&self) -> String;
    pub fn parent(&self) -> Option<Self>;
    pub fn segments(&self) -> &[String];
}
```

---

### Layer 4: Plugin Interface Types

**Location**: `src/plugin/types.rs`

```rust
/// Trait that all plugin stream items must implement
pub trait PluginStreamItem: Serialize + Send + 'static {
    /// Convert to hub's unified type
    fn into_hub_item(self, path: PluginPath) -> HubStreamItem;
}

/// Wrapper for plugin-specific stream items
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TrackedItem<T> {
    pub path: PluginPath,
    pub item: T,
}

impl<T> PluginStreamItem for TrackedItem<T>
where
    T: Serialize + Send + 'static,
{
    fn into_hub_item(self, path: PluginPath) -> HubStreamItem {
        HubStreamItem::Data {
            path,
            content_type: std::any::type_name::<T>().to_string(),
            data: serde_json::to_value(self.item).unwrap(),
        }
    }
}
```

---

### Layer 5: Conversion Trait (Plugin -> Hub)

**Location**: `src/plugin/conversion.rs`

```rust
use jsonrpsee::PendingSubscription;
use jsonrpsee::types::SubscriptionResult;
use futures::Stream;

/// Trait that enforces Stream<T> can be converted to SubscriptionResult
pub trait IntoSubscription: Send + 'static {
    type Item: PluginStreamItem;

    /// Convert this stream into a jsonrpsee subscription
    async fn into_subscription(
        self,
        pending: PendingSubscription,
        path: PluginPath,
    ) -> SubscriptionResult;
}

/// Blanket implementation for any Stream<Item = T> where T: PluginStreamItem
impl<S, T> IntoSubscription for S
where
    S: Stream<Item = T> + Send + Unpin + 'static,
    T: PluginStreamItem,
{
    type Item = T;

    async fn into_subscription(
        self,
        pending: PendingSubscription,
        path: PluginPath,
    ) -> SubscriptionResult {
        let sink = pending.accept().await?;

        tokio::spawn(async move {
            pin_mut!(self);
            while let Some(item) = self.next().await {
                let hub_item = item.into_hub_item(path.clone());
                let serialized = serde_json::to_value(&hub_item).unwrap();

                if sink.send(&serialized).await.is_err() {
                    break; // Client disconnected
                }
            }
        });

        Ok(())
    }
}
```

---

### Layer 6: Plugin Context Types

**Location**: `src/plugin/context.rs`

```rust
/// System-wide context available to all plugins
pub struct SystemContext {
    pub config: Arc<SystemConfig>,
    pub event_bus: Arc<EventBus>,
    pub http_client: reqwest::Client,
    pub telemetry: Arc<TelemetryService>,
}

/// Per-plugin context with isolated database
pub struct PluginContext {
    pub plugin_name: String,
    pub db: SqlitePool,
    pub system: Arc<SystemContext>,
}

impl PluginContext {
    pub async fn new(
        plugin_name: impl Into<String>,
        system: Arc<SystemContext>,
    ) -> Result<Self, Error>;

    pub fn db(&self) -> &SqlitePool;
    pub fn system(&self) -> &SystemContext;

    /// Call another plugin from within this plugin
    pub async fn call_plugin<T>(
        &self,
        current_path: &PluginPath,
        plugin_name: &str,
        method: &str,
        params: serde_json::Value,
    ) -> Result<impl Stream<Item = T>, Error>
    where
        T: PluginStreamItem + DeserializeOwned;
}
```

---

### Layer 7: Example Plugin-Specific Types

**Location**: `src/plugins/agent/types.rs`

```rust
/// Agent plugin's specific stream item type
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", content = "data")]
pub enum AgentChatItem {
    Started,
    Thinking { message: String },
    ToolCall { name: String, args: serde_json::Value },
    ToolResult { result: serde_json::Value },
    Token { text: String },
    Done,
}

impl PluginStreamItem for AgentChatItem {
    fn into_hub_item(self, path: PluginPath) -> HubStreamItem {
        HubStreamItem::Data {
            path,
            content_type: "AgentChatItem".to_string(),
            data: serde_json::to_value(self).unwrap(),
        }
    }
}

/// Agent entity
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Agent {
    pub id: String,
    pub name: String,
    pub description: Option<String>,
    pub created_at: i64,
}
```

**Location**: `src/plugins/mcp/types.rs`

```rust
/// MCP plugin's stream item type
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", content = "data")]
pub enum McpToolItem {
    Executing { tool_name: String },
    Progress { message: String },
    Result { output: serde_json::Value },
    Error { error: String },
}

impl PluginStreamItem for McpToolItem {
    fn into_hub_item(self, path: PluginPath) -> HubStreamItem {
        HubStreamItem::Data {
            path,
            content_type: "McpToolItem".to_string(),
            data: serde_json::to_value(self).unwrap(),
        }
    }
}
```

---

## Complete Flow with Types

### Request Flow

```
┌─────────────────────────────────────────────────────────────────┐
│ 1. CLIENT                                                        │
│    TransportRequest                                              │
│    { method: "agent.chat", params: {...}, id: 1 }               │
└────────────────────┬────────────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────────────┐
│ 2. TRANSPORT LAYER (jsonrpsee server)                           │
│    Receives: TransportRequest                                   │
│    Converts to: HubRequest                                      │
│    - Parses method: "agent.chat" -> plugin="agent", method="chat"│
│    - Initializes path: PluginPath::root("agent")                │
└────────────────────┬────────────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────────────┐
│ 3. HUB                                                           │
│    Receives: HubRequest                                         │
│    Routes to: AgentPlugin                                       │
│    Calls: agent_plugin.chat(pending, params, path)             │
└────────────────────┬────────────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────────────┐
│ 4. PLUGIN (Agent)                                                │
│    Receives: PendingSubscription, params, PluginPath            │
│    Creates: Stream<AgentChatItem>                               │
│    Converts: Stream<AgentChatItem> -> SubscriptionResult        │
│              via IntoSubscription trait                          │
│    Each item: AgentChatItem.into_hub_item() -> HubStreamItem    │
└────────────────────┬────────────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────────────┐
│ 5. HUB (receives stream)                                         │
│    Receives: Stream<HubStreamItem>                              │
│    - HubStreamItem::Data { path: ["agent"], ... }              │
│    - HubStreamItem::Done { path: ["agent"] }                   │
└────────────────────┬────────────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────────────┐
│ 6. TRANSPORT LAYER (SSE)                                         │
│    Receives: Stream<HubStreamItem>                              │
│    Converts to: Stream<SseEvent>                                │
│    Sends:                                                        │
│    - event: chunk, data: {"path":"agent", "data": {...}}       │
│    - event: done, data: {"path":"agent"}                       │
└────────────────────┬────────────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────────────┐
│ 7. CLIENT                                                        │
│    Receives: SSE events                                          │
│    Deserializes: HubStreamItem based on content_type            │
└─────────────────────────────────────────────────────────────────┘
```

### Nested Plugin Call Flow

```
Agent Plugin calls MCP Plugin:

┌─────────────────────────────────────────────────────────────────┐
│ AGENT PLUGIN                                                     │
│ Path: ["agent"]                                                  │
│ Calls: ctx.call_plugin("mcp", "execute_tool", params)          │
└────────────────────┬────────────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────────────┐
│ HUB                                                              │
│ Creates nested path: ["agent", "mcp"]                           │
│ Routes to: McpPlugin                                             │
└────────────────────┬────────────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────────────┐
│ MCP PLUGIN                                                       │
│ Path: ["agent", "mcp"]                                           │
│ Creates: Stream<McpToolItem>                                    │
│ Converts: Stream<McpToolItem> -> Stream<HubStreamItem>          │
│           Each item tagged with path ["agent", "mcp"]           │
└────────────────────┬────────────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────────────┐
│ AGENT PLUGIN                                                     │
│ Receives: Stream<McpToolItem> (via type-safe client)           │
│ Processes items, yields own AgentChatItem stream                │
│ Path for own items: ["agent"]                                   │
└────────────────────┬────────────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────────────┐
│ CLIENT (receives interleaved stream)                             │
│ - HubStreamItem { path: ["agent", "mcp"], type: "McpToolItem" } │
│ - HubStreamItem { path: ["agent"], type: "AgentChatItem" }     │
│ - HubStreamItem { path: ["agent", "mcp"], ... }                │
│ - HubStreamItem { path: ["agent"], ... }                       │
└─────────────────────────────────────────────────────────────────┘
```

---

## Module Structure in Source

```
src/
├── lib.rs
├── transport/
│   ├── mod.rs
│   ├── types.rs       # TransportRequest, TransportResponse, SseEvent
│   ├── jsonrpc.rs     # JSON-RPC handler
│   └── sse.rs         # SSE streaming handler
├── hub/
│   ├── mod.rs
│   ├── types.rs       # HubRequest, HubStreamItem, HubResponse
│   ├── path.rs        # PluginPath
│   ├── router.rs      # Plugin routing logic
│   └── registry.rs    # Plugin registration
├── plugin/
│   ├── mod.rs
│   ├── types.rs       # PluginStreamItem, TrackedItem
│   ├── conversion.rs  # IntoSubscription trait
│   ├── context.rs     # SystemContext, PluginContext
│   └── traits.rs      # Plugin trait definition
└── plugins/
    ├── mod.rs
    ├── agent/
    │   ├── mod.rs
    │   ├── types.rs   # AgentChatItem, Agent
    │   ├── plugin.rs  # AgentPlugin implementation
    │   └── db.rs      # Agent database operations
    └── mcp/
        ├── mod.rs
        ├── types.rs   # McpToolItem
        ├── plugin.rs  # McpPlugin implementation
        └── client.rs  # MCP protocol client
```

---

## Type Conversion Summary

### Transport -> Hub
```rust
TransportRequest -> HubRequest
- Parse method string into (plugin_name, method_name)
- Initialize PluginPath::root(plugin_name)
```

### Hub -> Plugin
```rust
HubRequest -> (PendingSubscription, Params, PluginPath)
- Extract params as plugin-specific type
- Pass plugin path for tracking
```

### Plugin -> Hub
```rust
Stream<T: PluginStreamItem> -> Stream<HubStreamItem>
- Each T.into_hub_item(path) -> HubStreamItem
- Type erasure via serde_json::Value
- Path tracking preserved
```

### Hub -> Transport
```rust
HubStreamItem -> SseEvent
- Serialize HubStreamItem to JSON
- Wrap in SSE event structure
```

---

## Compile-Time Guarantees

1. **Plugin methods must return streams**: Enforced by `IntoSubscription` trait bound
2. **Stream items must be serializable**: `T: Serialize` bound
3. **Type-safe inter-plugin calls**: Generated client types from jsonrpsee
4. **Path tracking is automatic**: Built into `IntoSubscription` implementation
5. **Hub type is uniform**: All plugins convert to `HubStreamItem`

---

## Awaiting Approval

This type system is designed to support:
- ✅ Tightly-typed plugin implementations
- ✅ Compile-time verified stream conversions
- ✅ Uniform hub interface via `HubStreamItem`
- ✅ Automatic plugin path tracking
- ✅ Type-safe nested plugin calls
- ✅ Clean module boundaries

**Ready to implement once approved.**
