Skip to content

heimoshuiyu/opencode-share

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

24 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

opencode-share Rust

Rust implementation of the Opencode share service for sharing AI coding agent sessions. Built with Axum, SQLx, and PostgreSQL for maximum performance and type safety.

πŸš€ Features

Core Functionality

  • High Performance: Built with Rust and Axum async framework for maximum throughput
  • Type Safety: Compile-time checked SQL queries and strongly typed data structures
  • Event Sourcing: Efficient event-driven data synchronization with JSONB storage
  • Secret-based Authentication: Secure sharing without user account management
  • Rich Share Page: Interactive UI for viewing AI coding sessions

Frontend Features

  • πŸ”§ Tool Call Visualization: Beautiful cards showing bash commands, file operations, grep, etc.
  • πŸ“¦ Collapsible Reasoning Blocks: Display AI "thinking" with expand/collapse functionality
  • πŸ”’ Step Markers: Visual indicators for reasoning steps with token usage tracking
  • πŸ“ Split Diff Viewer: Side-by-side file change visualization with synchronized scrolling
  • 🎨 Syntax Highlighting: Code blocks with monospace fonts and proper styling
  • πŸ“Š Token Usage Tracking: Display message and session token consumption

Production Ready

  • Docker Support: Multi-stage builds for optimized container images
  • Structured Logging: Comprehensive request/response logging with emoji indicators
  • CORS Support: Full cross-origin support for frontend integration
  • Environment Configuration: Flexible configuration via environment variables
  • Client IP Extraction: Proper handling of reverse proxy headers

πŸ“‹ Architecture

Technology Stack

Component Technology Version
Language Rust 2021 edition
Web Framework Axum 0.7
Runtime Tokio 1.0 (async)
Database PostgreSQL (via SQLx)
ORM SQLx 0.7 (compile-time checked)
Serialization Serde 1.0
Frontend Vanilla ES6+ JavaScript + CSS3

Project Structure

opencode-share/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ main.rs              # Application entry point (84 lines)
β”‚   β”œβ”€β”€ models.rs            # Data models and ShareData enum (76 lines)
β”‚   β”œβ”€β”€ middleware.rs        # HTTP request logging middleware (119 lines)
β”‚   β”œβ”€β”€ core/
β”‚   β”‚   β”œβ”€β”€ mod.rs           # Core module definition
β”‚   β”‚   └── share.rs         # Business logic for share operations (178 lines)
β”‚   β”œβ”€β”€ database/
β”‚   β”‚   └── mod.rs           # PostgreSQL pool setup (13 lines)
β”‚   └── routes/
β”‚       β”œβ”€β”€ mod.rs           # Route module exports
β”‚       β”œβ”€β”€ api.rs           # REST API endpoints (183 lines)
β”‚       └── share.rs         # Share page rendering (82 lines)
β”œβ”€β”€ static/
β”‚   β”œβ”€β”€ share.js            # Client-side renderer (617 lines)
β”‚   β”œβ”€β”€ share.css           # Styling (978 lines)
β”‚   └── favicon/manifest    # PWA assets
β”œβ”€β”€ templates/
β”‚   └── share.html          # HTML template
β”œβ”€β”€ migrations/
β”‚   └── 001_initial.sql     # Database schema
β”œβ”€β”€ Cargo.toml               # Rust dependencies
β”œβ”€β”€ Dockerfile              # Multi-stage container build
β”œβ”€β”€ docker-compose.yaml     # Orchestration with PostgreSQL
└── README.md

Design Patterns

  • Layered Architecture: Routes β†’ Service β†’ Data layers
  • Repository Pattern: ShareService abstracts database operations
  • Event Sourcing: Data stored as events in JSONB format
  • State Pattern: AppState holds shared database pool
  • Tagged Unions: ShareData enum with serde for type-safe event handling

πŸ› οΈ Quick Start

Prerequisites

  • Rust 1.70+
  • PostgreSQL 12+
  • Docker (optional, for containerized deployment)

Using Docker Compose (Recommended)

# Start the service with PostgreSQL
docker-compose up -d

# View logs
docker-compose logs -f

# Stop the service
docker-compose down

The server will start at http://localhost:3006

Manual Development

# Set environment variables
export DATABASE_URL="postgres://postgres:password@localhost:5432/opencode_share"
export RUST_LOG="opencode_share=debug,tower_http=debug"
export PORT=3006

# Run the server
cargo run

Production Build

# Build the release binary
cargo build --release

# Run the binary
./target/release/opencode-share

πŸ“‘ API Endpoints

Create Share

POST /api/share
Content-Type: application/json

{
  "sessionID": "your-session-id"
}

Response:

{
  "id": "share-id",
  "secret": "uuid-secret",
  "url": "http://localhost:3006/share/share-id"
}

Sync Share Data

POST /api/share/{shareID}/sync
Content-Type: application/json

{
  "secret": "share-secret",
  "data": [
    {
      "type": "session",
      "data": {
        "id": "session-id",
        "title": "Session Title",
        "status": "active"
      }
    },
    {
      "type": "message",
      "data": {
        "id": "message-id",
        "role": "assistant",
        "content": "Response text"
      }
    }
  ]
}

Get Share Data

GET /api/share/{shareID}/data

Response: Array of ShareData objects

Remove Share

DELETE /api/share/{shareID}
Content-Type: application/json

{
  "secret": "share-secret"
}

View Share Page

GET /share/{shareID}

πŸ—„οΈ Database Schema

Shares Table

CREATE TABLE shares (
    id TEXT PRIMARY KEY,
    secret TEXT NOT NULL,
    session_id TEXT NOT NULL,
    data JSONB DEFAULT '[]',
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW()
);

Indexes

CREATE INDEX idx_shares_session_id ON shares(session_id);
CREATE INDEX idx_shares_created_at ON shares(created_at);
CREATE INDEX idx_shares_updated_at ON shares(updated_at);
CREATE INDEX idx_shares_data_gin ON shares USING GIN (data);

ShareData Types

Data is stored as a discriminated union in JSONB:

  • session: Session metadata and status
  • message: User/assistant messages
  • part: Message parts (text, code blocks, tool outputs)
  • session_diff: File changes made during session
  • model: Model information (provider, name)

βš™οΈ Configuration

Environment Variables

Variable Description Default
DATABASE_URL PostgreSQL connection string -
RUST_LOG Log level opencode_share=info,tower_http=info
PORT Server port 3006
HOST Server host 0.0.0.0

Docker Compose Configuration

services:
  postgres:
    image: postgres:15
    environment:
      POSTGRES_PASSWORD: password
      POSTGRES_DB: opencode_share
    volumes:
      - postgres_data:/var/lib/postgresql/data
    ports:
      - "5432:5432"

  opencode-share:
    build: .
    environment:
      DATABASE_URL: postgres://postgres:password@postgres:5432/opencode_share
      RUST_LOG: opencode_share=debug,tower_http=debug
      PORT: 3006
    ports:
      - "3006:3006"
    depends_on:
      - postgres
    restart: unless-stopped

volumes:
  postgres_data:

πŸ”Œ Integrating with OpenCode

Step 1: Configure OpenCode

Create or update opencode.json in your OpenCode project:

{
  "$schema": "https://opencode.ai/config.json",
  "enterprise": {
    "url": "http://localhost:3006"
  },
  "share": "manual"
}

Step 2: Share a Session

In the OpenCode TUI/CLI:

/share

This will create a share and provide you with a URL to view the session.

Step 3: Access Shared Session

Navigate to the provided URL in your browser to view the shared session.

🎨 Customization

Customizing the Share Page

The share page can be customized by modifying:

  • HTML Template: templates/share.html
  • CSS Styling: static/share.css
  • JavaScript Renderer: static/share.js

Adding Custom Endpoints

  1. Define your route in src/routes/api.rs:
pub fn api_routes() -> Router<AppState> {
    Router::new()
        .route("/api/share/:id/custom", post(custom_endpoint))
        .with_state(app_state)
}

async fn custom_endpoint(
    State(state): State<AppState>,
    Path(id): Path<String>,
    Json(payload): Json<Value>,
) -> Result<Json<Value>, AppError> {
    // Your custom logic
    Ok(Json(json!({ "result": "success" })))
}
  1. Add business logic in src/core/share.rs:
impl ShareService {
    pub async fn custom_operation(
        &self,
        id: &str,
    ) -> Result<ShareData, anyhow::Error> {
        // Implementation
    }
}

Custom Data Types

Add new data types to the ShareData enum in src/models.rs:

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum ShareData {
    #[serde(rename = "session")]
    Session { data: Value },

    #[serde(rename = "custom")]
    Custom { data: YourCustomType },
}

πŸ§ͺ Testing

# Run all tests
cargo test

# Run tests with database
cargo test --features test-db

# Run specific test
cargo test test_create_share

πŸ“Š Performance Benefits

  • Memory Safety: No garbage collection pauses
  • Zero-Cost Abstractions: Compile-time optimizations
  • Async Runtime: Efficient concurrent request handling with Tokio
  • PostgreSQL + JSONB: Fast queries with flexible schema
  • SQLx: Compile-time checked SQL queries prevent runtime errors

πŸ” Security

  • Secret-based Authentication: Each share has a unique secret key
  • SQL Injection Prevention: Parameterized queries via SQLx
  • Input Validation: Type checking through Serde
  • CORS: Configurable cross-origin resource sharing

πŸ“ˆ Migration from TypeScript

This implementation maintains full API compatibility with the original TypeScript version:

  • βœ… Same endpoint URLs and request/response formats
  • βœ… Compatible data structures
  • βœ… Same event sourcing logic
  • βœ… Equivalent share page functionality

🀝 Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

πŸ“ Development Tips

Code Organization

  • Models: Define data types in src/models.rs
  • Core Logic: Implement business logic in src/core/share.rs
  • Routes: Add API endpoints in src/routes/api.rs
  • Database: Add migrations in migrations/

Logging

The service uses structured logging with tracing:

use tracing::{info, warn, error};

info!("share_created", id = %share_id);
warn!("sync_failed", share_id = %id, error = %err);

Error Handling

Use anyhow::Error for error propagation:

pub async fn create_share(&self, session_id: &str) -> Result<ShareInfo, anyhow::Error> {
    let share = sqlx::query_as::<_, ShareInfo>(...)
        .fetch_one(&self.pool)
        .await?;
    Ok(share)
}

πŸ“„ License

Same as the original Opencode project.

πŸ”— Links

About

A self-hosted opencode session share service, written in Rust + Plain JavaScript + PostgreSQL. Deploy with docker.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors