Skip to content

OpenAgentPlatform/libdive-desktop

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

libdive-desktop

A cross-platform IPC (Inter-Process Communication) library using tonic gRPC over Unix Domain Sockets (Linux/Mac) and Named Pipes (Windows).

Features

  • Cross-platform IPC communication
  • gRPC-based protocol using tonic
  • Bidirectional streaming for real-time communication
  • Unix Domain Sockets for Linux/Mac
  • Named Pipes for Windows
  • Async/await support with tokio
  • Type-safe communication with Protocol Buffers
  • MCP Elicitation protocol support

Installation

Add this to your Cargo.toml:

[dependencies]
libdive-desktop = { path = "." }
tokio = { version = "1.0", features = ["rt-multi-thread", "macros"] }
tonic = "0.12"
tokio-stream = "0.1"

Usage

Server Example

use libdive_desktop::proto::ipc_service_server::{IpcService, IpcServiceServer};
use libdive_desktop::proto::{
    McpStreamRequest, McpStreamResponse, RequestType, ResponseType,
    ElicitationResult, EndResponse, elicitation_result::Action,
};
use tonic::{Request, Response, Status};
use tokio_stream::wrappers::ReceiverStream;
use tokio_stream::StreamExt;

#[derive(Debug, Default)]
pub struct MyIpcService;

#[tonic::async_trait]
impl IpcService for MyIpcService {
    type McpStreamStream = ReceiverStream<Result<McpStreamResponse, Status>>;

    async fn stream(
        &self,
        request: Request<tonic::Streaming<McpStreamRequest>>,
    ) -> Result<Response<Self::StreamStream>, Status> {
        let mut in_stream = request.into_inner();
        let (tx, rx) = tokio::sync::mpsc::channel(128);

        tokio::spawn(async move {
            while let Some(Ok(msg)) = in_stream.next().await {
                let response = match msg.r#type() {
                    RequestType::Elicitation => {
                        // Handle elicitation request
                        let mut content = std::collections::HashMap::new();
                        content.insert("username".to_string(), "alice".to_string());

                        McpStreamResponse {
                            r#type: ResponseType::Elicitation as i32,
                            payload: Some(libdive_desktop::proto::mcp_stream_response::Payload::Elicitation(
                                ElicitationResult {
                                    action: Action::Accept as i32,
                                    content,
                                }
                            )),
                        }
                    }
                    RequestType::End => {
                        McpStreamResponse {
                            r#type: ResponseType::End as i32,
                            payload: Some(libdive_desktop::proto::mcp_stream_response::Payload::End(
                                EndResponse { acknowledged: true }
                            )),
                        }
                    }
                    _ => continue,
                };

                if tx.send(Ok(response)).await.is_err() {
                    break;
                }
            }
        });

        Ok(Response::new(ReceiverStream::new(rx)))
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let service = MyIpcService::default();
    let incoming = libdive_desktop::serve().await?;

    tonic::transport::Server::builder()
        .add_service(IpcServiceServer::new(service))
        .serve_with_incoming(incoming)
        .await?;

    Ok(())
}

Client Example

use libdive_desktop::proto::ipc_service_client::IpcServiceClient;
use libdive_desktop::proto::{
    McpStreamRequest, RequestType, ResponseType,
    ElicitationRequest, RequestedSchema, SchemaProperty, EndRequest,
    elicitation_result::Action,
};
use std::collections::HashMap;
use tokio_stream::StreamExt;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let channel = libdive_desktop::connect().await?;
    let mut client = IpcServiceClient::new(channel);

    let (tx, rx) = tokio::sync::mpsc::channel(128);
    let stream = tokio_stream::wrappers::ReceiverStream::new(rx);
    let mut response_stream = client.mcp_stream(stream).await?.into_inner();

    // Handle responses in background
    tokio::spawn(async move {
        while let Some(Ok(msg)) = response_stream.next().await {
            match msg.r#type() {
                ResponseType::Elicitation => println!("Got elicitation response"),
                ResponseType::End => break,
                _ => {}
            }
        }
    });

    // Send elicitation request
    let mut properties = HashMap::new();
    properties.insert("username".to_string(), SchemaProperty {
        r#type: "string".to_string(),
        ..Default::default()
    });

    tx.send(McpStreamRequest {
        r#type: RequestType::Elicitation as i32,
        payload: Some(libdive_desktop::proto::mcp_stream_request::Payload::Elicitation(
            ElicitationRequest {
                message: "Please provide info".to_string(),
                requested_schema: Some(RequestedSchema { properties, required: vec![] }),
            }
        )),
    }).await?;

    // Send end request
    tx.send(McpStreamRequest {
        r#type: RequestType::End as i32,
        payload: Some(libdive_desktop::proto::mcp_stream_request::Payload::End(
            EndRequest { reason: Some("Done".to_string()) }
        )),
    }).await?;

    Ok(())
}

Protocol

Message Types

Request Type Response Type Description
ELICITATION ELICITATION Request user input (MCP standard)
END END End the stream

Message Structure

McpStreamRequest {
    type: RequestType,
    payload: oneof {
        elicitation: ElicitationRequest,
        end: EndRequest,
    }
}

McpStreamResponse {
    type: ResponseType,
    payload: oneof {
        elicitation: ElicitationResult,
        end: EndResponse,
    }
}

MCP Elicitation

The elicitation protocol follows the MCP standard:

Schema Type Constraints
string format, minLength, maxLength
number minimum, maximum
integer minimum, maximum
boolean -
Action Description
Accept User approved and submitted data
Decline User explicitly declined
Cancel User dismissed without choice

Running Examples

# Build
cargo build --examples

# Run server
cargo run --example unified_server

# Run client (in another terminal)
cargo run --example unified_client

Architecture

┌─────────────────────────────────────────────────────────┐
│                    Your Application                      │
├─────────────────────────────────────────────────────────┤
│  connect() → Channel       │  serve() → Stream          │
│  (Client)                  │  (Server)                  │
├─────────────────────────────────────────────────────────┤
│              libdive-desktop                             │
│  • Bidirectional streaming                               │
│  • Cross-platform transport                              │
│  • MCP Elicitation protocol                              │
├─────────────────────────────────────────────────────────┤
│  Unix: UnixStream          │  Windows: Named Pipe       │
└─────────────────────────────────────────────────────────┘

License

MIT

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages