Files
BossAI/plan/rust-backend-rewrite.md
T
boos b78b622f0c docs: add subagent team collaboration strategy to Rust plan
Adds workstream map, parallel phase timing, shared contracts (AppState
extension, router composition, error type patterns), file ownership matrix,
subagent task template, git branch strategy, and coordinator workflow for
multi-agent parallel development.
2026-05-16 11:27:42 +08:00

70 KiB

BoosAPI Rust Backend Rewrite — Agent Development Brief

For Hermes: Execute this plan phase-by-phase using delegate_task with full task context. Each phase is self-contained and builds on the previous.

Goal: Rewrite the entire Node.js/Fastify backend in Rust using Axum, producing a production-ready AI API gateway that is API-compatible with the existing frontend.

Architecture: Replace Fastify + Drizzle ORM + TypeScript with Axum + SQLx + tokio. The frontend (Vite + React SPA) stays unchanged. The Rust binary replaces the Node.js server process entirely.

Repo: https://git.boos.lat/boos/BoosAPI.git Branch: refactor/rust-backend Language: All code, comments, commit messages, and docs in English


Tech Stack

Layer Choice Rationale
HTTP framework Axum 0.8+ tokio-native, tower middleware, SSE/WS built-in, largest ecosystem
Database SQLx Compile-time SQL verification, SQLite/MySQL/Postgres in one crate
HTTP client reqwest Async streaming, connection pooling, TLS
Auth jsonwebtoken + argon2 JWT + password hashing
Serialization serde + serde_json Zero-cost JSON, derive macros
Streaming SSE axum::response::sse Native SSE support
WebSocket axum::extract::ws tokio-tungstenite under the hood
Logging tracing + tracing-subscriber Structured logging, tokio integration
Config figment + dotenvy Layered config (env → file → defaults)
CLI clap Admin commands (migrate, seed, etc.)
Testing rstest + wiremock Parameterized tests, HTTP mock server
Async runtime tokio (multi-threaded) Standard choice for Axum

Golden Rules

  1. API compatibility first. Every endpoint path, HTTP method, request body, response JSON, SSE event format, and error shape must match the existing Node.js implementation exactly. The frontend must work with zero changes.
  2. Test before claim. Every handler must have an integration test with wiremock or sqlx in-memory SQLite. Run cargo test and cargo clippy before committing.
  3. One phase at a time. Complete all tasks in the current phase before moving to the next. Each phase produces a compilable, testable artifact.
  4. Phase 1 is foundational. All later phases depend on it — project structure, config, DB pool, graceful shutdown, Dockerfile.
  5. Commit convention: feat: for new functionality, fix: for bugs, refactor: for restructuring, docs: for documentation, test: for tests.
  6. Rust standard: cargo fmt, cargo clippy (deny warnings where practical), cargo test. Use thiserror for error types, anyhow for binary-level errors.
  7. Keep it lean. No unnecessary dependencies. Avoid heap allocations in hot paths. Use Arc<AppState> for shared state, not lazy_static.

Architecture

                        ┌──────────────────┐
                        │   React SPA      │
                        │   (unchanged)    │
                        └────────┬─────────┘
                                 │ HTTP / SSE / WebSocket
                        ┌────────▼─────────┐
                        │  Rust Backend     │
                        │  (Axum + tokio)   │
                        │                   │
                        │  ┌───────────┐   │
                        │  │ Axum Router│   │
                        │  └─────┬─────┘   │
                        │        │          │
                        │  ┌─────▼─────┐   │
                        │  │  Proxy    │   │
                        │  │  Core     │   │
                        │  └─────┬─────┘   │
                        │        │          │
                        │  ┌─────▼─────┐   │
                        │  │Transformers│   │
                        │  └─────┬─────┘   │
                        │        │          │
                        │  ┌─────▼─────┐   │
                        │  │ Services  │   │
                        │  └─────┬─────┘   │
                        │        │          │
                        │  ┌─────▼─────┐   │
                        │  │ SQLx + DB │   │
                        │  └───────────┘   │
                        └──────────────────┘

Directory Layout

boosapi/
├── Cargo.toml
├── .env.example
├── docker/
│   └── Dockerfile
├── migrations/                    # SQLx migration files
│   ├── 20260516000001_initial.sql
│   └── ...
├── src/
│   ├── main.rs                    # Entry point
│   ├── lib.rs                     # Re-exports
│   ├── config.rs                  # App configuration
│   ├── error.rs                   # Unified error types
│   │
│   ├── routes/                    # Axum route modules (thin)
│   │   ├── mod.rs                 # Router construction
│   │   ├── health.rs              # GET /healthz
│   │   ├── proxy/                 # /v1/* proxy routes
│   │   │   ├── mod.rs
│   │   │   ├── chat.rs
│   │   │   ├── completions.rs
│   │   │   ├── models.rs
│   │   │   ├── images.rs
│   │   │   ├── audio.rs
│   │   │   ├── embeddings.rs
│   │   │   ├── files.rs
│   │   │   ├── responses.rs
│   │   │   ├── responses_ws.rs    # WebSocket for responses API
│   │   │   ├── search.rs
│   │   │   ├── videos.rs
│   │   │   ├── gemini.rs
│   │   │   └── middleware.rs      # API key auth for proxy
│   │   ├── admin/                 # /api/* management routes
│   │   │   ├── mod.rs
│   │   │   ├── sites.rs
│   │   │   ├── accounts.rs
│   │   │   ├── tokens.rs
│   │   │   ├── stats.rs
│   │   │   ├── settings.rs
│   │   │   ├── auth.rs
│   │   │   ├── users.rs
│   │   │   ├── downstream_keys.rs
│   │   │   ├── checkin.rs
│   │   │   ├── events.rs
│   │   │   ├── tasks.rs
│   │   │   ├── search.rs
│   │   │   ├── monitor.rs
│   │   │   ├── account_tokens.rs
│   │   │   ├── invitations.rs
│   │   │   ├── organizations.rs
│   │   │   ├── oauth.rs
│   │   │   ├── announcements.rs
│   │   │   ├── admin_users.rs
│   │   │   ├── update_center.rs
│   │   │   ├── comfyui.rs
│   │   │   ├── comfyui_agent.rs
│   │   │   └── video_agent.rs
│   │   └── user/                  # /v1/user/* routes
│   │       ├── mod.rs
│   │       ├── api_keys.rs
│   │       └── profile.rs
│   │
│   ├── proxy/                     # Proxy orchestration core
│   │   ├── mod.rs
│   │   ├── conductor.rs           # execute_endpoint_flow()
│   │   ├── channel.rs             # channel selection
│   │   ├── retry.rs               # retry policy
│   │   ├── service_tier.rs        # service tier policy
│   │   ├── first_byte_timeout.rs  # first byte timeout handling
│   │   ├── responses_preflight.rs # preflight check for responses API
│   │   ├── web_search.rs          # web search simulation
│   │   ├── downstream_context.rs  # downstream client context
│   │   ├── providers/             # Provider profiles & registry
│   │   │   ├── mod.rs
│   │   │   ├── registry.rs
│   │   │   ├── types.rs
│   │   │   ├── header_utils.rs
│   │   │   ├── openai.rs
│   │   │   ├── anthropic.rs
│   │   │   ├── gemini.rs
│   │   │   ├── codex.rs
│   │   │   ├── claude.rs
│   │   │   ├── gemini_cli.rs
│   │   │   ├── antigravity.rs
│   │   │   ├── new_api.rs
│   │   │   ├── one_api.rs
│   │   │   ├── one_hub.rs
│   │   │   ├── done_hub.rs
│   │   │   ├── sub2api.rs
│   │   │   └── veloera.rs
│   │   ├── executors/             # Runtime executors
│   │   │   ├── mod.rs
│   │   │   ├── types.rs
│   │   │   ├── claude_executor.rs
│   │   │   ├── codex_executor.rs
│   │   │   ├── gemini_cli_executor.rs
│   │   │   └── antigravity_executor.rs
│   │   ├── runtime/               # Runtime state & codex sessions
│   │   │   ├── mod.rs
│   │   │   ├── types.rs
│   │   │   ├── codex_http_session.rs
│   │   │   ├── codex_ws_session.rs
│   │   │   ├── codex_session_store.rs
│   │   │   ├── codex_ws_headers.rs
│   │   │   └── responses_sse.rs
│   │   ├── surfaces/              # API surface handling
│   │   │   ├── mod.rs
│   │   │   ├── shared.rs
│   │   │   ├── chat.rs
│   │   │   ├── files.rs
│   │   │   ├── input_files.rs
│   │   │   ├── models.rs
│   │   │   ├── openai_responses.rs
│   │   │   └── gemini.rs
│   │   ├── cli_profiles/          # CLI integration profiles
│   │   │   ├── mod.rs
│   │   │   ├── types.rs
│   │   │   ├── registry.rs
│   │   │   ├── claude_code.rs
│   │   │   ├── codex.rs
│   │   │   ├── gemini_cli.rs
│   │   │   └── generic.rs
│   │   └── capabilities/          # Platform capabilities
│   │       ├── mod.rs
│   │       ├── conversation_file.rs
│   │       └── responses_compact.rs
│   │
│   ├── transformers/              # Protocol transformers
│   │   ├── mod.rs
│   │   ├── contracts.rs
│   │   ├── canonical/             # Internal canonical format
│   │   │   ├── mod.rs
│   │   │   ├── types.rs
│   │   │   ├── request.rs
│   │   │   ├── response.rs
│   │   │   ├── attachments.rs
│   │   │   ├── reasoning.rs
│   │   │   ├── tools.rs
│   │   │   ├── envelope.rs
│   │   │   ├── openai_request_bridge.rs
│   │   │   └── continuation_bridge.rs
│   │   ├── openai/
│   │   │   ├── mod.rs
│   │   │   ├── chat/              # /v1/chat/completions transformers
│   │   │   │   ├── mod.rs
│   │   │   │   ├── inbound.rs
│   │   │   │   ├── outbound.rs
│   │   │   │   ├── stream.rs
│   │   │   │   ├── aggregator.rs
│   │   │   │   ├── request_bridge.rs
│   │   │   │   ├── response_bridge.rs
│   │   │   │   ├── stream_bridge.rs
│   │   │   │   ├── usage.rs
│   │   │   │   ├── model.rs
│   │   │   │   └── helpers.rs
│   │   │   └── responses/         # /v1/responses transformers
│   │   │       ├── mod.rs
│   │   │       ├── inbound.rs
│   │   │       ├── outbound.rs
│   │   │       ├── stream.rs
│   │   │       ├── aggregator.rs
│   │   │       ├── request_bridge.rs
│   │   │       ├── response_bridge.rs
│   │   │       ├── stream_bridge.rs
│   │   │       ├── usage.rs
│   │   │       ├── model.rs
│   │   │       ├── normalization.rs
│   │   │       ├── compatibility.rs
│   │   │       ├── codex_compatibility.rs
│   │   │       ├── mcp_compatibility.rs
│   │   │       ├── route_compatibility.rs
│   │   │       └── continuation.rs
│   │   ├── anthropic/
│   │   │   ├── mod.rs
│   │   │   └── messages/          # Anthropic Messages API transformers
│   │   │       ├── mod.rs
│   │   │       ├── inbound.rs
│   │   │       ├── outbound.rs
│   │   │       ├── stream.rs
│   │   │       ├── aggregator.rs
│   │   │       ├── request_bridge.rs
│   │   │       ├── response_bridge.rs
│   │   │       ├── stream_bridge.rs
│   │   │       ├── usage.rs
│   │   │       ├── compatibility.rs
│   │   │       └── conversion.rs
│   │   ├── gemini/
│   │   │   ├── mod.rs
│   │   │   └── generate_content/  # Gemini API transformers
│   │   │       ├── mod.rs
│   │   │       ├── inbound.rs
│   │   │       ├── outbound.rs
│   │   │       ├── stream.rs
│   │   │       ├── aggregator.rs
│   │   │       ├── request_bridge.rs
│   │   │       ├── response_bridge.rs
│   │   │       ├── stream_bridge.rs
│   │   │       ├── usage.rs
│   │   │       ├── conversion.rs
│   │   │       ├── convert.rs
│   │   │       ├── url_resolver.rs
│   │   │       └── cli_bridge.rs
│   │   └── shared/                # Shared transformer utilities
│   │       ├── mod.rs
│   │       ├── normalized.rs
│   │       ├── input_file.rs
│   │       ├── chat_formats.rs
│   │       ├── chat_endpoint.rs
│   │       ├── endpoint_compat.rs
│   │       ├── protocol_lifecycle.rs
│   │       ├── protocol_model.rs
│   │       ├── reasoning_transport.rs
│   │       ├── think_tag_parser.rs
│   │       └── tool_name_shortener.rs
│   │
│   ├── services/                  # Business logic services
│   │   ├── mod.rs
│   │   ├── auth.rs                # JWT + API key auth service
│   │   ├── account.rs             # Account CRUD
│   │   ├── account_token.rs       # Account token management
│   │   ├── account_credential.rs  # Credential management
│   │   ├── account_health.rs      # Account health checks
│   │   ├── account_mutation.rs    # Account mutation workflow
│   │   ├── account_update.rs      # Account update workflow
│   │   ├── account_extra_config.rs# Extra config handling
│   │   ├── accounts_overview.rs   # Dashboard overview aggregation
│   │   ├── site.rs                # Site CRUD
│   │   ├── site_api_endpoint.rs   # Site endpoint management
│   │   ├── site_api_key_migration.rs # API key → account migration
│   │   ├── site_proxy.rs          # Site proxy configuration
│   │   ├── site_detector.rs       # Platform auto-detection
│   │   ├── site_custom_headers.rs # Custom header management
│   │   ├── token_router.rs        # Token routing decision engine
│   │   ├── route_cooldown.rs      # Route cooldown management
│   │   ├── route_refresh.rs       # Route decision refresh
│   │   ├── route_decision_snapshot.rs
│   │   ├── routing_strategy.rs    # Routing strategy logic
│   │   ├── runtime_dispatch.rs    # Runtime dispatch service
│   │   ├── model.rs               # Model discovery & management
│   │   ├── model_analysis.rs      # Model analysis service
   │   ├── model_pricing.rs       # Model pricing & cost
│   │   ├── model_availability_probe.rs
│   │   ├── channel_recovery_probe.rs
│   │   ├── billing.rs             # Proxy billing & usage tracking
│   │   ├── usage_aggregation.rs   # Usage aggregation (hour/day/model)
│   │   ├── proxy_log.rs           # Proxy log CRUD
│   │   ├── proxy_log_retention.rs # Log cleanup & retention
│   │   ├── proxy_file.rs          # File store for proxy
│   │   ├── proxy_file_retention.rs# File retention cleanup
│   │   ├── proxy_video_task.rs    # Video task store
│   │   ├── proxy_input_file.rs    # Input file resolver
│   │   ├── proxy_debug_trace.rs   # Debug trace recording
│   │   ├── proxy_debug_trace_store.rs
│   │   ├── proxy_failure_judge.rs # Failure classification
│   │   ├── proxy_retry_policy.rs  # Proxy retry policy service
│   │   ├── proxy_channel_coordinator.rs
│   │   ├── proxy_channel_retry.rs # Channel retry fallback
│   │   ├── proxy_usage_fallback.rs
│   │   ├── proxy_usage_parser.rs  # Upstream usage response parser
│   │   ├── downstream_api_key.rs  # Downstream API key CRUD
│   │   ├── downstream_key_trend.rs
│   │   ├── downstream_policy.rs   # Downstream policy types
│   │   ├── checkin.rs             # Check-in logic
│   │   ├── checkin_scheduler.rs   # Scheduled check-in runner
│   │   ├── checkin_reward_parser.rs
│   │   ├── daily_summary.rs       # Daily summary generation
│   │   ├── today_income_reward.rs # Today's reward calculation
│   │   ├── balance.rs             # Balance refresh service
│   │   ├── user.rs                # User CRUD
│   │   ├── user_api_key.rs        # User API key management
│   │   ├── organization.rs        # Organization CRUD
│   │   ├── invitation.rs          # Invitation management
│   │   ├── plan.rs                # Plan/limits service
│   │   ├── event.rs               # Event log service
│   │   ├── alert.rs               # Alert rules engine
│   │   ├── alert_service.rs       # Alert dispatch
│   │   ├── notification_throttle.rs
│   │   ├── notify.rs              # Multi-channel notification
│   │   ├── brand_matcher.rs       # Client/brand identification
│   │   ├── payload_rules.rs       # Payload rule engine
│   │   ├── upstream_endpoint.rs   # Upstream endpoint derivation
│   │   ├── upstream_endpoint_runtime.rs
│   │   ├── upstream_endpoint_runtime_memory.rs
│   │   ├── upstream_request.rs    # Upstream request builder
│   │   ├── upstream_model_description.rs
│   │   ├── url_normalization.rs   # URL normalization utilities
│   │   ├── local_time.rs          # Local time utilities
│   │   ├── snapshot_cache.rs      # Snapshot cache service
│   │   ├── admin_snapshot_store.rs
│   │   ├── admin_snapshot_warm.rs # Warm snapshot generation
│   │   ├── dashboard_snapshot.rs  # Dashboard aggregation
│   │   ├── stats_shared.rs        # Shared stats utilities
│   │   ├── backup.rs              # WebDAV backup service
│   │   ├── factory_reset.rs       # Factory reset service
│   │   ├── database_migration.rs  # Runtime DB migration service
│   │   ├── default_site_seed.rs   # Default site seeding
│   │   ├── platform_discovery.rs  # Platform discovery registry
│   │   ├── startup_info.rs        # Startup summary
│   │   ├── cron/                  # Background schedulers
│   │   │   ├── mod.rs
│   │   │   ├── scheduler.rs       # Generic cron scheduler
│   │   │   └── registry.rs        # Scheduler registry
│   │   ├── oauth/                 # OAuth services
│   │   │   ├── mod.rs
│   │   │   ├── provider.rs        # OAuth provider trait
│   │   │   ├── providers.rs       # Provider implementations
│   │   │   ├── session.rs         # OAuth session store
│   │   │   ├── service.rs         # OAuth orchestration
│   │   │   ├── quota.rs           # OAuth quota management
│   │   │   ├── quota_types.rs
│   │   │   ├── route_unit.rs      # Route unit management
│   │   │   ├── account.rs         # OAuth account binding
│   │   │   ├── site_registry.rs   # OAuth site registration
│   │   │   ├── identity_backfill.rs
│   │   │   ├── refresh_scheduler.rs
│   │   │   ├── refresh_singleflight.rs
│   │   │   ├── request_proxy.rs   # OAuth proxied requests
│   │   │   ├── local_callback.rs  # Local OAuth callback server
│   │   │   ├── codex.rs           # Codex OAuth provider
│   │   │   ├── claude.rs          # Claude OAuth provider
│   │   │   ├── gemini_cli.rs      # Gemini CLI OAuth provider
│   │   │   └── antigravity.rs     # Antigravity OAuth provider
│   │   ├── update_center/         # Update center services
│   │   │   ├── mod.rs
│   │   │   ├── config.rs
│   │   │   ├── status.rs
│   │   │   ├── version.rs
│   │   │   ├── runtime_state.rs
│   │   │   ├── polling.rs
│   │   │   ├── deploy_guard.rs
│   │   │   ├── reminder.rs
│   │   │   ├── helper_client.rs
│   │   │   └── task_constants.rs
│   │   ├── announcement/          # Site announcement services
│   │   │   ├── mod.rs
│   │   │   ├── polling.rs
│   │   │   └── service.rs
│   │   ├── background_task.rs     # Background task service
│   │   ├── api_key_batch.rs       # API key batch operations
│   │   └── shared/                # Shared service utilities
│   │       └── api_key_batch_core.rs
│   │
│   ├── db/                        # Database layer
│   │   ├── mod.rs
│   │   ├── pool.rs                # Connection pool management
│   │   ├── migrate.rs             # Migration runner
│   │   ├── models/                # Rust structs matching DB tables
│   │   │   ├── mod.rs
│   │   │   ├── site.rs
│   │   │   ├── account.rs
│   │   │   ├── account_token.rs
│   │   │   ├── checkin_log.rs
│   │   │   ├── model_availability.rs
│   │   │   ├── token_model_availability.rs
│   │   │   ├── token_route.rs
│   │   │   ├── route_group_source.rs
│   │   │   ├── route_channel.rs
│   │   │   ├── oauth_route_unit.rs
│   │   │   ├── oauth_route_unit_member.rs
│   │   │   ├── site_api_endpoint.rs
│   │   │   ├── site_disabled_model.rs
│   │   │   ├── proxy_log.rs
│   │   │   ├── proxy_debug_trace.rs
│   │   │   ├── proxy_debug_attempt.rs
│   │   │   ├── proxy_file.rs
│   │   │   ├── proxy_video_task.rs
│   │   │   ├── setting.rs
│   │   │   ├── admin_snapshot.rs
│   │   │   ├── analytics_checkpoint.rs
│   │   │   ├── site_day_usage.rs
│   │   │   ├── site_hour_usage.rs
│   │   │   ├── model_day_usage.rs
│   │   │   ├── downstream_api_key.rs
│   │   │   ├── site_announcement.rs
│   │   │   ├── user.rs
│   │   │   ├── event.rs
│   │   │   ├── organization.rs
│   │   │   ├── organization_member.rs
│   │   │   ├── invitation.rs
│   │   │   └── data_plan.rs
│   │   └── queries/               # Typed query functions
│   │       ├── mod.rs
│   │       ├── sites.rs
│   │       ├── accounts.rs
│   │       ├── tokens.rs
│   │       ├── proxy_logs.rs
│   │       ├── usage.rs
│   │       ├── users.rs
│   │       └── settings.rs
│   │
│   ├── auth/                      # Authentication primitives
│   │   ├── mod.rs
│   │   ├── jwt.rs
│   │   ├── api_key.rs
│   │   └── password.rs
│   │
│   ├── desktop/                   # Electron desktop support
│   │   ├── mod.rs
│   │   ├── adapter.rs
│   │   └── runtime.rs
│   │
│   └── shared/                    # Shared across crate
│       ├── mod.rs
│       ├── sse.rs                 # SSE utility types
│       ├── stream.rs              # Streaming helpers
│       ├── pagination.rs          # Pagination helpers
│       ├── time.rs                # Time utilities
│       └── json.rs                # JSON helpers
│
└── tests/                         # Integration tests
    ├── common/
    │   └── mod.rs                 # Test helpers
    ├── health.rs
    ├── proxy/
    │   ├── chat.rs
    │   ├── models.rs
    │   └── streaming.rs
    ├── admin/
    │   ├── sites.rs
    │   ├── accounts.rs
    │   └── tokens.rs
    └── oauth/
        └── flow.rs

Getting Started (from scratch)

Prerequisites

  • Rust 1.85+ (install via rustup)
  • SQLite dev libs (libsqlite3-dev on Debian, or use bundled via rusqlite feature)
  • Docker (optional, for container builds)
  • cargo-nextest (optional, for faster test runs: cargo install cargo-nextest)

Bootstrap

# 1. Clone the repository
git clone https://git.boos.lat/boos/BoosAPI.git boosapi-rs
cd boosapi-rs

# 2. Checkout the rewrite branch
git checkout refactor/rust-backend

# 3. Verify Rust version
rustc --version  # must be 1.85+

# 4. Build and run tests
cargo build
cargo test

# 5. Quick smoke test
BOOSAPI_AUTH_TOKEN=admin BOOSAPI_PROXY_TOKEN=proxy \
  BOOSAPI_DB_URL=sqlite://./data/boosapi.db \
  cargo run &
sleep 3
curl -s http://localhost:4000/healthz
# Expected: {"status":"ok","database":"ok",...}
kill %1

# 6. Verify Phase 1 scaffold exists
ls src/main.rs src/config.rs src/db/pool.rs src/routes/mod.rs src/routes/health.rs
ls migrations/20260516000001_initial.sql docker/Dockerfile
# All should exist

Phases


Phase 1: Project Scaffold & Infrastructure

Scope: Cargo project init, dependencies, config loading, DB pool, Axum server skeleton, graceful shutdown, migration runner, Dockerfile.

Task 1.1: Initialize Cargo Project

cargo init --name boosapi

Cargo.toml:

[package]
name = "boosapi"
version = "1.3.0"
edition = "2024"

[dependencies]
# Web framework
axum = { version = "0.8", features = ["macros"] }
tower = "0.5"
tower-http = { version = "0.6", features = ["cors", "trace", "fs", "compression-gzip"] }

# Async runtime
tokio = { version = "1", features = ["full"] }

# Database
sqlx = { version = "0.8", features = [
    "runtime-tokio",
    "sqlite",
    "mysql",
    "postgres",
    "migrate",
    "chrono",
] }

# HTTP client
reqwest = { version = "0.12", features = ["stream", "json"] }

# Serialization
serde = { version = "1", features = ["derive"] }
serde_json = "1"

# Auth
jsonwebtoken = "9"
argon2 = "0.5"

# Config
figment = { version = "0.10", features = ["env", "toml"] }
dotenvy = "0.15"

# Logging
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] }

# Error handling
thiserror = "2"
anyhow = "1"

# CLI
clap = { version = "4", features = ["derive"] }

# Time
chrono = { version = "0.4", features = ["serde"] }

# UUID
uuid = { version = "1", features = ["v4", "serde"] }

# Streaming
futures = "0.3"
tokio-stream = "0.1"
bytes = "1"

# Misc
once_cell = "1"
parking_lot = "0.12"
mime = "0.3"

[dev-dependencies]
rstest = "0.24"
wiremock = "0.6"
tracing-test = "0.2"
tempfile = "3"

Task 1.2: Configuration (src/config.rs)

use figment::{
    Figment,
    providers::{Env, Toml, Format},
};
use serde::Deserialize;

#[derive(Debug, Deserialize, Clone)]
pub struct AppConfig {
    pub server: ServerConfig,
    pub db: DbConfig,
    pub auth: AuthConfig,
    pub proxy: ProxyConfig,
}

#[derive(Debug, Deserialize, Clone)]
pub struct ServerConfig {
    pub host: String,
    pub port: u16,
    pub log_level: String,
    pub cors_origins: Vec<String>,
}

#[derive(Debug, Deserialize, Clone)]
pub struct DbConfig {
    pub url: String,          // sqlite://data.db | mysql://... | postgres://...
    pub max_connections: u32,
    pub ssl: bool,
    pub runtime_db_url: Option<String>,
    pub runtime_db_dialect: Option<String>,
    pub runtime_db_ssl: Option<bool>,
}

#[derive(Debug, Deserialize, Clone)]
pub struct AuthConfig {
    pub jwt_secret: String,
    pub jwt_expiry_hours: i64,
    pub auth_token: String,    // BOOSAPI_AUTH_TOKEN
    pub proxy_token: String,   // BOOSAPI_PROXY_TOKEN
}

#[derive(Debug, Deserialize, Clone)]
pub struct ProxyConfig {
    pub upstream_timeout_secs: u64,
    pub first_byte_timeout_secs: u64,
    pub max_retries: u32,
    pub proxy_log_retention_days: u32,
}

impl AppConfig {
    pub fn load() -> anyhow::Result<Self> {
        Ok(Figment::new()
            .merge(Toml::file("config.toml").nested())
            .merge(Env::prefixed("BOOSAPI_").split("__"))
            .extract()?)
    }
}

.env.example:

BOOSAPI_SERVER__HOST=0.0.0.0
BOOSAPI_SERVER__PORT=4000
BOOSAPI_SERVER__LOG_LEVEL=info
BOOSAPI_DB__URL=sqlite://./data/boosapi.db
BOOSAPI_DB__MAX_CONNECTIONS=10
BOOSAPI_AUTH__JWT_SECRET=change-me
BOOSAPI_AUTH__AUTH_TOKEN=admin
BOOSAPI_AUTH__PROXY_TOKEN=proxy

Task 1.3: App State & Error Types

src/error.rs:

use axum::{
    http::StatusCode,
    response::{IntoResponse, Response, Json},
};
use serde_json::json;

#[derive(Debug, thiserror::Error)]
pub enum AppError {
    #[error("Not found: {0}")]
    NotFound(String),

    #[error("Unauthorized: {0}")]
    Unauthorized(String),

    #[error("Bad request: {0}")]
    BadRequest(String),

    #[error("Rate limited")]
    RateLimited,

    #[error("Internal error: {0}")]
    Internal(#[from] anyhow::Error),

    #[error("Database error: {0}")]
    Database(#[from] sqlx::Error),
}

impl IntoResponse for AppError {
    fn into_response(self) -> Response {
        let (status, message) = match &self {
            AppError::NotFound(msg) => (StatusCode::NOT_FOUND, msg.clone()),
            AppError::Unauthorized(msg) => (StatusCode::UNAUTHORIZED, msg.clone()),
            AppError::BadRequest(msg) => (StatusCode::BAD_REQUEST, msg.clone()),
            AppError::RateLimited => (StatusCode::TOO_MANY_REQUESTS, "rate_limited".into()),
            AppError::Internal(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()),
            AppError::Database(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()),
        };
        (
            status,
            Json(json!({
                "error": {
                    "message": message,
                    "type": status.canonical_reason(),
                }
            })),
        ).into_response()
    }
}

pub type AppResult<T> = Result<T, AppError>;

src/lib.rs:

pub mod config;
pub mod error;
pub mod db;
pub mod routes;
pub mod auth;
pub mod services;
pub mod transformers;
pub mod proxy;
pub mod shared;

use std::sync::Arc;
use sqlx::SqlitePool;

#[derive(Clone)]
pub struct AppState {
    pub config: config::AppConfig,
    pub db: Arc<sqlx::Pool<sqlx::Sqlite>>,
}

impl AppState {
    pub fn new(config: config::AppConfig, db: sqlx::Pool<sqlx::Sqlite>) -> Self {
        Self {
            config,
            db: Arc::new(db),
        }
    }
}

Task 1.4: Database Pool (src/db/pool.rs)

use sqlx::sqlite::{SqlitePoolOptions, SqlitePool};

pub async fn create_pool(database_url: &str, max_connections: u32) -> anyhow::Result<SqlitePool> {
    let pool = SqlitePoolOptions::new()
        .max_connections(max_connections)
        .connect(database_url)
        .await?;
    Ok(pool)
}

Note: For MySQL/Postgres support, use sqlx::mysql::MySqlPoolOptions or sqlx::postgres::PgPoolOptions and feature-gate or runtime-detect the database type.

Task 1.5: Main Entry Point (src/main.rs)

use std::net::SocketAddr;
use axum::serve;
use tokio::signal;
use tracing_subscriber::EnvFilter;
use boosapi::{config::AppConfig, db, AppState};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Initialize tracing
    tracing_subscriber::fmt()
        .with_env_filter(EnvFilter::from_default_env())
        .init();

    // Load config
    let config = AppConfig::load()?;
    tracing::info!("Loaded configuration");

    // Create database pool
    let pool = db::pool::create_pool(&config.db.url, config.db.max_connections).await?;
    tracing::info!("Database connected");

    // Run migrations
    sqlx::migrate!("./migrations").run(&pool).await?;
    tracing::info!("Database migrations applied");

    // Build app state
    let state = AppState::new(config.clone(), pool);

    // Build router
    let app = boosapi::routes::build_router(state);

    // Start server
    let addr = SocketAddr::new(
        config.server.host.parse()?,
        config.server.port,
    );
    tracing::info!("Starting server on {addr}");

    serve(
        tokio::net::TcpListener::bind(addr).await?,
        app.into_make_service(),
    )
    .with_graceful_shutdown(shutdown_signal())
    .await?;

    Ok(())
}

async fn shutdown_signal() {
    let ctrl_c = async {
        signal::ctrl_c()
            .await
            .expect("failed to install Ctrl+C handler");
    };

    let terminate = async {
        signal::unix::signal(signal::unix::SignalKind::terminate())
            .expect("failed to install SIGTERM handler")
            .recv()
            .await;
    };

    tokio::select! {
        _ = ctrl_c => {},
        _ = terminate => {},
    }

    tracing::info!("Shutdown signal received, starting graceful shutdown");
}

Task 1.6: Route Module & Health Check (src/routes/mod.rs + src/routes/health.rs)

src/routes/mod.rs:

use axum::{Router, routing::get};
use crate::AppState;

mod health;

pub fn build_router(state: AppState) -> Router {
    Router::new()
        .route("/healthz", get(health::health_check))
        .with_state(state)
}

src/routes/health.rs:

use axum::Json;
use serde_json::{json, Value};
use crate::{AppState, error::AppResult};

pub async fn health_check(
    state: axum::extract::State<AppState>,
) -> AppResult<Json<Value>> {
    // Simple DB ping
    sqlx::query("SELECT 1")
        .execute(&*state.db)
        .await?;

    Ok(Json(json!({
        "status": "ok",
        "database": "ok",
        "version": env!("CARGO_PKG_VERSION"),
    })))
}

Task 1.7: Initial Migration (migrations/20260516000001_initial.sql)

Create the initial migration with ALL tables from the Drizzle schema (see src/server/db/schema.ts for reference — 18 tables). Include all indexes, unique constraints, and foreign keys.

Key tables:

  • sites — Platform site configuration
  • site_api_endpoints — Per-site API endpoints
  • site_disabled_models — Disabled models per site
  • accounts — Upstream accounts
  • account_tokens — Account-level tokens
  • checkin_logs — Check-in history
  • model_availability — Model availability per account
  • token_model_availability — Model availability per token
  • token_routes — Token routing rules
  • route_group_sources — Route grouping
  • route_channels — Route-to-account bindings
  • oauth_route_units — OAuth route units
  • oauth_route_unit_members — OAuth route unit member accounts
  • proxy_logs — Proxy request logs
  • proxy_debug_traces — Debug trace records
  • proxy_debug_attempts — Per-attempt debug records
  • proxy_video_tasks — Video generation tasks
  • proxy_files — File storage
  • settings — Key-value settings store
  • admin_snapshots — Cached admin dashboard data
  • analytics_projection_checkpoints — Usage aggregation state
  • site_day_usage — Daily usage per site
  • site_hour_usage — Hourly usage per site
  • model_day_usage — Daily usage per model
  • downstream_api_keys — Downstream client API keys
  • site_announcements — Site announcements
  • users — Admin users
  • events — System events
  • organizations — SaaS organizations
  • organization_members — Organization membership
  • invitations — Org invitations
  • data_plans — Subscription plans

Task 1.8: Dockerfile (docker/Dockerfile)

# Build stage
FROM rust:1.85-slim-bookworm AS builder
WORKDIR /app
RUN apt-get update && apt-get install -y pkg-config libssl-dev
COPY Cargo.toml Cargo.lock ./
RUN mkdir src && echo "fn main() {}" > src/main.rs
RUN cargo build --release 2>/dev/null || true
COPY . .
RUN cargo build --release

# Runtime stage
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY --from=builder /app/target/release/boosapi /app/
COPY --from=builder /app/migrations /app/migrations
EXPOSE 4000
CMD ["/app/boosapi"]

Phase 2: Shared Types & Utilities

Scope: API response types, SSE utilities, streaming helpers, pagination, time utilities.

Task 2.1: Shared SSE Types (src/shared/sse.rs)

use axum::response::sse::Event;
use serde::Serialize;
use std::convert::Infallible;

pub fn sse_event<T: Serialize>(event_type: &str, data: &T) -> Result<Event, Infallible> {
    Event::default()
        .event(event_type)
        .data(serde_json::to_string(data).unwrap_or_default())
}

Task 2.2: Pagination Helpers (src/shared/pagination.rs)

use serde::{Serialize, Deserialize};

#[derive(Debug, Deserialize)]
pub struct PaginationParams {
    pub page: Option<i64>,
    pub page_size: Option<i64>,
    pub sort_by: Option<String>,
    pub sort_order: Option<String>,
}

#[derive(Debug, Serialize)]
pub struct PaginatedResponse<T: Serialize> {
    pub data: Vec<T>,
    pub total: i64,
    pub page: i64,
    pub page_size: i64,
    pub total_pages: i64,
}

Task 2.3: API Response Envelope (src/shared/mod.rs)

Standardized API response format matching the existing Node.js output.


Phase 3: Authentication

Scope: JWT generation/validation, API key hashing/verification, password hashing, auth middleware.

Task 3.1: JWT Service (src/auth/jwt.rs)

  • generate_jwt(secret, user_id, role) -> String
  • validate_jwt(secret, token) -> Claims
  • Claims struct: sub, role, exp, iat

Task 3.2: API Key Authentication (src/auth/api_key.rs)

  • Blake3/Argon2 hashing for stored API keys
  • hash_api_key(key) -> String
  • verify_api_key(key, hash) -> bool
  • Lookup key in downstream_api_keys table

Task 3.3: Password Hashing (src/auth/password.rs)

  • hash_password(password) -> String (argon2id)
  • verify_password(password, hash) -> bool

Task 3.4: Auth Middleware (Axum Layered Middleware)

Two auth layers:

  1. ProxyTokenAuth — validates Authorization: Bearer <proxy_token> for /v1/* routes
  2. AuthTokenAuth — validates Authorization: Bearer <auth_token> or JWT for /api/* routes
  3. DownstreamApiKeyAuth — validates downstream API keys for /v1/* proxy routes

Phase 4: Admin API Routes

Scope: All /api/* management endpoints.

Each route module follows this pattern:

pub fn routes(state: AppState) -> Router {
    Router::new()
        .route("/api/sites", get(list_sites).post(create_site))
        .route("/api/sites/{id}", get(get_site).put(update_site).delete(delete_site))
        .with_state(state)
}

Endpoints to implement:

Route Module Methods
/api/sites routes/admin/sites.rs GET, POST
/api/sites/{id} — GET, PUT, DELETE
/api/sites/{id}/refresh — POST
/api/sites/{id}/endpoints — GET, POST
/api/accounts routes/admin/accounts.rs GET, POST
/api/accounts/{id} — GET, PUT, DELETE
/api/tokens routes/admin/tokens.rs GET, POST
/api/tokens/{id} — GET, PUT, DELETE
/api/tokens/route-decision — GET
/api/tokens/refresh-route — POST
/api/stats/* routes/admin/stats.rs GET (multiple)
/api/settings routes/admin/settings.rs GET, PUT
/api/auth/* routes/admin/auth.rs POST login, POST register
/api/users routes/admin/users.rs GET, POST, PUT, DELETE
/api/downstream-api-keys routes/admin/downstream_keys.rs GET, POST, PUT, DELETE
/api/checkin routes/admin/checkin.rs POST, GET
/api/events routes/admin/events.rs GET
/api/tasks routes/admin/tasks.rs GET
/api/monitor routes/admin/monitor.rs GET
/api/account-tokens routes/admin/account_tokens.rs GET
/api/search routes/admin/search.rs POST
/api/invitations routes/admin/invitations.rs GET, POST
/api/organizations routes/admin/organizations.rs GET, POST
/api/oauth/* routes/admin/oauth.rs GET, POST
/api/site-announcements routes/admin/announcements.rs GET
/api/update-center/* routes/admin/update_center.rs GET, POST
/api/comfyui/* routes/admin/comfyui.rs Proxy passthrough
/api/comfyui-agent/* routes/admin/comfyui_agent.rs SSE chat
/api/video-agent/* routes/admin/video_agent.rs Video generation

Route registration in routes/mod.rs follows the same layered pattern as the current index.ts — group by path prefix, apply middleware.


Phase 5: Proxy Routes & Core

Scope: /v1/* proxy endpoints — the core of BoosAPI.

Task 5.1: Proxy Router

Register all /v1/* proxy routes with ProxyTokenAuth + DownstreamApiKeyAuth middleware:

Route Handler
POST /v1/chat/completions proxy_chat
POST /v1/completions proxy_completions
GET /v1/models proxy_models
POST /v1/images/generations proxy_image_generate
POST /v1/images/edits proxy_image_edit
POST /v1/embeddings proxy_embeddings
POST /v1/audio/transcriptions proxy_audio_transcribe
POST /v1/audio/translations proxy_audio_translate
POST /v1/audio/speech proxy_audio_speech
GET/POST /v1/files proxy_files
POST /v1/responses proxy_responses
GET /v1/responses/{id} proxy_responses_get
POST /v1/video/generations proxy_video
POST /v1/search proxy_search
WebSocket /v1/responses/ws proxy_responses_ws

Each handler:

  1. Extracts downstream API key from header
  2. Checks rate limits
  3. Calls token_router::resolve() to find best channel
  4. Calls conductor::execute() to proxy the request
  5. Logs the proxied request

Task 5.2: Token Router (services/token_router.rs)

Core routing decision engine:

  • resolve(model, downstream_key, options) -> RouteDecision
  • Route matching by model pattern
  • Channel selection with weighted random
  • OAuth route unit integration
  • Cooldown-aware selection
  • Session sticky routing

Task 5.3: Proxy Conductor (proxy/conductor.rs)

Request execution orchestration:

  • execute_endpoint_flow(request, channel) -> Response
  • Channel selection → upstream request → handle response
  • SSE streaming relay
  • First-byte timeout handling
  • Retry on failure (configurable retry policy)
  • Rate limit detection and backoff

Task 5.4: Channel Selection (proxy/channel.rs)

  • select_channel(route, options) -> SelectedChannel
  • Weighted random from available channels
  • Skip channels in cooldown
  • Skip disabled/unhealthy channels
  • Sort by priority, weight, latency

Task 5.5: Retry Policy (proxy/retry.rs)

  • should_retry(error, attempt) -> bool
  • Classification: transient vs permanent failures
  • Exponential backoff with jitter
  • Max retry count from config

Task 5.6: Upstream Request Building

  • Construct upstream URL from site + endpoint
  • Set Authorization header with account token
  • Pass through x-boosapi-* headers
  • Handle streaming vs non-streaming content negotiation

Phase 6: Protocol Transformers

Scope: Transform requests/responses between OpenAI-compatible format and upstream provider formats.

Task 6.1: Canonical Format (transformers/canonical/)

Define a unified internal request/response format:

pub struct CanonicalRequest {
    pub model: String,
    pub messages: Vec<CanonicalMessage>,
    pub stream: bool,
    pub max_tokens: Option<i32>,
    pub temperature: Option<f64>,
    pub top_p: Option<f64>,
    pub stop: Option<Vec<String>>,
    pub tools: Option<Vec<Tool>>,
    pub tool_choice: Option<ToolChoice>,
    pub reasoning: Option<ReasoningConfig>,
    // ... more fields
}

pub struct CanonicalResponse {
    pub id: String,
    pub model: String,
    pub choices: Vec<CanonicalChoice>,
    pub usage: Option<Usage>,
}

Task 6.2: OpenAI Transformers (transformers/openai/)

  • inbound: OpenAI JSON → CanonicalRequest
  • outbound: CanonicalResponse → OpenAI JSON response
  • stream: Parse OpenAI SSE data: {...} → CanonicalStreamEvent
  • request_bridge: CanonicalRequest → OpenAI HTTP request
  • response_bridge: OpenAI HTTP response → CanonicalResponse

Task 6.3: Anthropic Transformers (transformers/anthropic/)

  • inbound: CanonicalRequest → Anthropic MessagesRequest
  • outbound: Anthropic MessagesResponse → CanonicalResponse
  • stream: Parse Anthropic SSE (event: content_block_delta, etc.)
  • compatibility: Anthropic-specific compatibility flags

Task 6.4: Gemini Transformers (transformers/gemini/)

  • inbound: CanonicalRequest → Gemini GenerateContentRequest
  • outbound: Gemini GenerateContentResponse → CanonicalResponse
  • stream: Parse Gemini SSE
  • url_resolver: Resolve Gemini API URLs
  • cli_bridge: Gemini CLI integration

Phase 7: Provider Implementations

Scope: Actual HTTP calls to upstream AI providers.

Provider Crate module Features
OpenAI / OpenAI-compatible proxy/providers/openai.rs Chat, streaming, models, images, embeddings, audio, files, responses
Anthropic proxy/providers/anthropic.rs Messages, streaming, models
Gemini proxy/providers/gemini.rs Generate content, streaming, models
Codex proxy/providers/codex.rs Chat, streaming, WebSocket sessions
Claude proxy/providers/claude.rs Chat, streaming
Gemini CLI proxy/providers/gemini_cli.rs CLI bridge
Antigravity proxy/providers/antigravity.rs Custom provider
NewAPI proxy/providers/new_api.rs OpenAI-compatible
OneAPI proxy/providers/one_api.rs OpenAI-compatible
OneHub proxy/providers/one_hub.rs OpenAI-compatible
DoneHub proxy/providers/done_hub.rs OpenAI-compatible
Sub2API proxy/providers/sub2api.rs Subscription-based
Veloera proxy/providers/veloera.rs Custom provider

Provider interface:

#[async_trait]
pub trait Provider: Send + Sync {
    fn name(&self) -> &'static str;
    async fn chat_completion(&self, req: ChatRequest) -> Result<ChatResponse>;
    async fn chat_completion_stream(&self, req: ChatRequest) -> Result<StreamReceiver>;
    async fn models(&self) -> Result<Vec<Model>>;
}

Phase 8: Streaming — SSE & WebSocket

Scope: All streaming paths — the most performance-critical part.

Task 8.1: SSE Streaming Relay

// Axum handler for POST /v1/chat/completions with stream: true
async fn proxy_chat_stream(
    State(state): State<AppState>,
    Json(body): Json<serde_json::Value>,
) -> Result<Sse<impl Stream<Item = ...>>> {
    // 1. Parse body
    // 2. Resolve route via token_router
    // 3. Call conductor::execute_stream()
    // 4. Return Sse<impl Stream> that forwards upstream SSE chunks
}

Task 8.2: WebSocket Handler (routes/proxy/responses_ws.rs)

  • Axum WebSocket upgrade
  • Codex WebSocket session management
  • Bidirectional message forwarding

Task 8.3: Streaming Transformer Pipeline

For streaming requests, the pipeline is:

Client SSE ← Axum Sse Stream
  ← response_bridge (upstream → canonical)
    ← stream relay (reqwest stream → transformer events)
      ← request_bridge (canonical → upstream format)
        ← upstream provider

Phase 9: Background Services & Schedulers

Scope: All start*Scheduler() functions from the current codebase.

Implementation pattern — each scheduler is a tokio task:

pub fn start_scheduler(state: AppState, config: CronConfig) {
    tokio::spawn(async move {
        let mut interval = tokio::time::interval(config.interval);
        loop {
            interval.tick().await;
            if let Err(e) = run_job(&state).await {
                tracing::warn!("Scheduler job failed: {e}");
            }
        }
    });
}

Schedulers to implement:

Scheduler Interval Purpose
Check-in Config cron Auto check-in for accounts
Model availability probe Every 10min Probe model availability
Channel recovery probe Every 5min Recover channels from cooldown
Usage aggregation Every 1min Aggregate proxy logs → usage stats
Admin snapshot warm Every 30s Warm admin dashboard caches
OAuth refresh Per-account schedule Refresh OAuth tokens
Sub2API refresh Config interval Refresh subscription tokens
Site announcement polling Config interval Poll site announcements
Update center polling Config interval Poll for updates
Proxy log retention Config cron Clean old proxy logs
Proxy file retention Config cron Clean expired files
Backup (WebDAV) Config cron Scheduled backups

Phase 10: OAuth Flow

Scope: Multi-provider OAuth authentication for upstream AI platforms.

Supported Providers

Provider Auth Type Refresh
Claude OAuth PKCE Token refresh
Codex OAuth PKCE Token refresh
Gemini CLI OAuth Token refresh
Antigravity OAuth Token refresh

Components

services/oauth/:

  • provider.rs — OAuthProvider trait
  • session.rs — OAuth session store (DB-backed)
  • service.rs — OAuth orchestration
  • local_callback.rs — Temporary HTTP server for local OAuth callback
  • refresh_scheduler.rs — Scheduled token refresh
  • route_unit.rs — OAuth route unit → channel binding

Phase 11: Notification Service

Scope: Multi-channel notification dispatch, used by schedulers and alert rules.

Channels:

  • Telegram Bot API
  • WeChat Work Webhook
  • Feishu Webhook
  • Generic Webhook
  • Bark (iOS)
  • ServerChan
  • SMTP Email

Phase 12: Edge Features

12.1: ComfyUI Reverse Proxy

Reverse proxy to a running ComfyUI instance, similar to current src/server/routes/api/comfyui.ts.

  • /api/comfyui/prompt → POST /prompt
  • /api/comfyui/history → GET /history
  • /api/comfyui/view → GET /view (image proxy)
  • /api/comfyui/proxy/{path} → GET /{path} with HTML rewriting for iframe embedding

12.2: Video Agent

One-shot script-to-video pipeline. Uses BoosAPI's own /v1/chat/completions and /v1/images/generations and /v1/audio/speech endpoints.

  • Script analysis → character prompts → scene descriptions → TTS

12.3: ComfyUI Agent (SSE Chat)

Conversational agent for interactive ComfyUI workflow generation via SSE streaming.


Phase 13: Integration Testing & Polish

Task 13.1: Test Infrastructure

  • tests/common/mod.rs — Test helpers, test database setup, mock servers
  • SQLx in-memory SQLite for fast tests

Task 13.2: Health & Smoke Tests

Task 13.3: Proxy Route Integration Tests

Use wiremock to mock upstream providers, test:

  • Chat completion (non-streaming + streaming)
  • Model listing
  • Image generation
  • Error handling (4xx, 5xx, timeout)

Task 13.4: Admin API Integration Tests

Test CRUD for all admin endpoints with a real SQLite database.

Task 13.5: Transformer Unit Tests

Test each conversion path with serialized fixtures:

  • openai/chat/inbound → canonical
  • canonical → anthropic/messages/outbound
  • gemini/content/inbound → canonical
  • Round-trip tests

Task 13.6: OAuth Flow Tests

Task 13.7: Performance & Benchmark

  • cargo bench for transformer hot paths
  • oha or wrk for end-to-end proxy latency tests
  • Memory profiling with tokio-console

Testing Requirements

Every handler must have a test:

#[tokio::test]
async fn test_chat_completion_non_streaming() {
    // Setup mock upstream
    let mock_server = MockServer::start().await;
    Mock::given(method("POST"))
        .and(path("/v1/chat/completions"))
        .respond_with(
            ResponseTemplate::new(200)
                .set_body_json(serde_json::json!({
                    "id": "chatcmpl-xxx",
                    "model": "gpt-4",
                    "choices": [{
                        "index": 0,
                        "message": {"role": "assistant", "content": "Hello!"},
                        "finish_reason": "stop"
                    }],
                    "usage": {"prompt_tokens": 10, "completion_tokens": 5, "total_tokens": 15}
                }))
        )
        .mount(&mock_server)
        .await;

    // Setup app with test DB
    let pool = SqlitePool::connect("sqlite::memory:").await.unwrap();
    sqlx::migrate!("./migrations").run(&pool).await.unwrap();
    let state = AppState::new(test_config(mock_server.uri()), pool);

    // Build test app
    let app = build_router(state);

    // Send proxy request
    let response = app
        .oneshot(
            Request::builder()
                .method("POST")
                .uri("/v1/chat/completions")
                .header("Authorization", "Bearer test-proxy-token")
                .header("Content-Type", "application/json")
                .body(Body::from(r#"{"model":"gpt-4","messages":[{"role":"user","content":"hi"}]}"#))
                .unwrap()
        )
        .await
        .unwrap();

    assert_eq!(response.status(), StatusCode::OK);
    let body: Value = serde_json::from_slice(&hyper::body::to_bytes(response.into_body()).await.unwrap()).unwrap();
    assert_eq!(body["model"], "gpt-4");
    assert_eq!(body["choices"][0]["message"]["content"], "Hello!");
}

Test Coverage Targets

Module Target
Transformers (pure functions) 90%+
Auth utilities 90%+
Proxy routes (wiremock) 80%+
Admin API routes (sqlite) 80%+
Services 70%+
Overall 70%+

Build & Run

# Build
cargo build --release

# Run (with SQLite)
cp .env.example .env
# edit .env with your config
cargo run --release

# Test
cargo test
cargo clippy -- -D warnings

# Docker
docker build -f docker/Dockerfile -t boosapi:latest .
docker run -p 4000:4000 \
  -e BOOSAPI_AUTH__AUTH_TOKEN=admin \
  -e BOOSAPI_AUTH__PROXY_TOKEN=proxy \
  -e BOOSAPI_DB__URL=sqlite:///app/data/boosapi.db \
  boosapi:latest

Before Committing

  1. cargo clippy -- -D warnings — zero clippy warnings
  2. cargo test — all tests pass
  3. cargo fmt — code is formatted
  4. git diff --cached — review for secrets (no real IPs, tokens, passwords)
  5. Commit message follows convention: feat:, fix:, refactor:, docs:, test:

Provider Implementation Priority

Provider Priority Notes
OpenAI (Chat, Models) P0 Core proxy path
OpenAI (Images, Embeddings, Audio, Files) P0 Standard proxy
Anthropic Messages P0 Different auth + streaming format
Google Gemini P1 Different API structure
NewAPI / OneAPI P1 OpenAI-compatible, low effort
Codex / Claude P1 OAuth-based
Sub2API P2 Subscription-based
Veloera / DoneHub / OneHub P2 Open source backends
Antigravity P2 Custom provider

Execution Order

Phase What Est. tasks Dependencies
1 Scaffold & infrastructure 8 None
2 Shared types & utilities 3 Phase 1
3 Authentication 4 Phase 1
4 Admin API routes ~25 Phase 2, 3
5 Proxy routes & core 6 Phase 1, 2, 3
6 Protocol transformers 4 Phase 2
7 Provider implementations ~14 Phase 5, 6
8 Streaming (SSE/WS) 3 Phase 5, 6, 7
9 Background schedulers ~14 Phase 1, 3
10 OAuth flow 1 (sub-packages) Phase 3
11 Notification service 1 None
12 Edge features (ComfyUI, agents) 3 Phase 5, 6
13 Integration testing & polish 7 All phases

Total estimated file count: ~180+ source files


Subagent Team Collaboration

This project is designed for parallel subagent development. After Phase 1 (scaffold) is built by the coordinator, multiple subagents work concurrently on independent workstreams.

Workstream Map

Coordinator (Phase 1: Scaffold)
    │
    ├── Workstream A: Backend Core ─────────────────────────────
    │   ├── Phase 2: Shared types & utilities
    │   ├── Phase 3: Authentication
    │   ├── Phase 5: Proxy core (conductor, channel, retry)
    │   └── Phase 8: Streaming (SSE/WS)
    │
    ├── Workstream B: Admin API ─────────────────────────────────
    │   ├── Phase 4a: Sites, Accounts, Tokens routes
    │   ├── Phase 4b: Settings, Stats, Users routes
    │   └── Phase 4c: OAuth, Checkin, remaining admin routes
    │
    ├── Workstream C: Protocol Transformers ─────────────────────
    │   ├── Phase 6a: Canonical format + OpenAI transformers
    │   ├── Phase 6b: Anthropic transformers
    │   └── Phase 6c: Gemini transformers
    │
    ├── Workstream D: Provider Implementations ──────────────────
    │   ├── Phase 7a: OpenAI provider (chat, models, images, etc.)
    │   ├── Phase 7b: Anthropic provider
    │   ├── Phase 7c: Gemini provider
    │   └── Phase 7d: Minor providers (OneAPI, NewAPI, Sub2API, etc.)
    │
    ├── Workstream E: Platform Services ─────────────────────────
    │   ├── Phase 9: Background schedulers
    │   ├── Phase 10: OAuth flows
    │   └── Phase 11: Notification service
    │
    ├── Workstream F: Edge Features ─────────────────────────────
    │   ├── Phase 12a: ComfyUI reverse proxy + agent
    │   └── Phase 12b: Video agent
    │
    └── Workstream G: QA & Polish ──────────────────────────────
        └── Phase 13: Integration tests, benchmarks, edge cases

Phase Timing & Parallelism

timeline
Phase 1  ████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
Phase 2  ░░░░░░░░████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░  (after P1)
Phase 3  ░░░░░░░░░░████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░  (after P1, parallel with P2)
Phase 4a ░░░░░░░░░░░░░░████████░░░░░░░░░░░░░░░░░░░░░░░  (after P2+P3)
Phase 4b ░░░░░░░░░░░░░░████████░░░░░░░░░░░░░░░░░░░░░░░  (parallel with 4a)
Phase 4c ░░░░░░░░░░░░░░████████░░░░░░░░░░░░░░░░░░░░░░░  (parallel with 4a)
Phase 5  ░░░░░░░░░░░░░░████████████░░░░░░░░░░░░░░░░░░░  (after P2+P3)
Phase 6a ░░░░░░░░░░░░░░████░░░░░░░░░░░░░░░░░░░░░░░░░░░  (after P2)
Phase 6b ░░░░░░░░░░░░░░░░████░░░░░░░░░░░░░░░░░░░░░░░░░  (parallel with 6a)
Phase 6c ░░░░░░░░░░░░░░░░░░████░░░░░░░░░░░░░░░░░░░░░░░  (parallel with 6a)
Phase 7a ░░░░░░░░░░░░░░░░░░░░░░████████░░░░░░░░░░░░░░░  (after P5+P6a)
Phase 7b ░░░░░░░░░░░░░░░░░░░░░░░░████████░░░░░░░░░░░░░  (parallel with 7a)
Phase 7c ░░░░░░░░░░░░░░░░░░░░░░░░░░████████░░░░░░░░░░░  (parallel with 7a)
Phase 7d ░░░░░░░░░░░░░░░░░░░░░░░░░░░░██████░░░░░░░░░░░  (parallel with 7a)
Phase 8  ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░████░░░░░░░░  (after P5+P7)
Phase 9  ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░███████░░░░░░░  (after P3)
Phase 10 ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░███████░░░░░░░  (parallel with P9)
Phase 11 ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░████░░░░░░░░  (parallel with P9)
Phase 12 ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░██████░░░░  (after P5+P6)
Phase 13 ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░██████  (after all)

Shared Contracts (Subagent Interface)

Every subagent MUST follow these contracts to avoid conflicts:

1. AppState Extension Pattern

The AppState struct in src/lib.rs is the shared dependency. After Phase 1, it holds config and db. Each workstream that needs additional state must extend AppState, not replace it:

// Phase 1 defines:
#[derive(Clone)]
pub struct AppState {
    pub config: AppConfig,
    pub db: Pool<Sqlite>,
}

// Phase 5 (proxy core) adds to AppState:
#[derive(Clone)]
pub struct AppState {
    pub config: AppConfig,
    pub db: Pool<Sqlite>,
    pub proxy_core: Arc<ProxyCoreState>,  // added by Phase 5
}

Rule: Only the coordinator merges AppState. Subagents return their new state struct/s in their summary, and the coordinator integrates them.

2. Router Composition Pattern

All routes are composed in src/routes/mod.rs. Each subagent creates a module that returns a Router:

// Each route module:
pub fn routes(state: AppState) -> Router {
    Router::new()
        .route("/api/sites", get(list_sites))
        .with_state(state)
}

The coordinator merges them:

pub fn build_router(state: AppState) -> Router {
    Router::new()
        .merge(routes::admin::sites::routes(state.clone()))
        .merge(routes::admin::accounts::routes(state.clone()))
        .merge(routes::proxy::chat::routes(state.clone()))
        // ...
}

3. Error Type Extension

All errors go through AppError in src/error.rs. Subagents define domain-specific errors that convert into AppError:

// In proxy/mod.rs:
#[derive(thiserror::Error)]
pub enum ProxyError {
    #[error("Channel unavailable: {0}")]
    ChannelUnavailable(String),
    #[error("Upstream error: {status} {body}")]
    UpstreamError { status: u16, body: String },
}

impl From<ProxyError> for AppError {
    fn from(e: ProxyError) -> Self {
        match e {
            ProxyError::ChannelUnavailable(msg) => AppError::BadRequest(msg),
            ProxyError::UpstreamError { .. } => AppError::Internal(e.into()),
        }
    }
}

4. Module Interface Pattern

Each workstream module must expose:

// lib.rs or mod.rs
pub mod routes;     // axum route modules
pub mod service;    // business logic
pub mod types;      // public types

pub use service::MyService;

This lets the coordinator import and wire things together without knowing internals.

File Ownership Matrix

To prevent conflicts, each subagent owns specific file paths. Two subagents never touch the same file:

Workstream Owned directories Coordination
Coordinator (Phase 1) Cargo.toml, src/main.rs, src/lib.rs, src/config.rs, src/error.rs, src/db/, src/routes/mod.rs, src/routes/health.rs, migrations/, docker/, .env.example Base — all others depend on this
Workstream A (Core) src/auth/, src/shared/, src/proxy/conductor.rs, src/proxy/channel.rs, src/proxy/retry.rs Phase 2 + 3 + 5 core
Workstream B (Admin) src/routes/admin/*.rs, src/services/site.rs, src/services/account.rs, etc. Phase 4 only
Workstream C (Transformers) src/transformers/ Phase 6 only
Workstream D (Providers) src/proxy/providers/, src/proxy/executors/, src/proxy/runtime/ Phase 7 only
Workstream E (Services) src/services/oauth/, src/services/cron/, src/services/notify.rs Phase 9-11
Workstream F (Edge) src/routes/admin/comfyui.rs, src/routes/admin/video_agent.rs Phase 12
Workstream G (QA) tests/ Phase 13

Exception: src/lib.rs and src/routes/mod.rs are coordinator-owned. Subagents send their module registrations to the coordinator who integrates them.

Subagent Task Template

Each subagent task follows this template:

GOAL: [Phase X Task description]

PROJECT CONTEXT:
- This is a Rust Axum project at /project/boosapi
- Shared AppState in src/lib.rs has: config, db
- Errors go through AppError in src/error.rs
- DB models are in src/db/models/
- SQLx queries use compile-time checking with query!() macro
- Use tracing for logging (info!, warn!, error!, debug!)
- All code must compile with: cargo check
- All tests must pass with: cargo test
- Code must pass: cargo clippy -- -D warnings
- Format with: cargo fmt

MODULE INTERFACE:
- Create `pub fn routes(state: AppState) -> Router` in your module
- Export public types and services via mod.rs
- Return changes/new-types to coordinator for AppState integration

OWNED FILES (only touch these):
- src/routes/admin/sites.rs
- src/services/site.rs
- Tests in tests/admin/sites.rs

DO NOT TOUCH:
- src/lib.rs, src/main.rs, src/config.rs (coordinator-owned)
- Any file outside your owned list

TASK DETAILS:
[Full task description from the plan phase]

VERIFICATION:
1. cargo check -- no errors
2. cargo clippy -- no warnings
3. cargo test -- relevant tests pass
4. git diff --stat -- only your owned files changed

Git Branch Strategy

Each subagent works on a short-lived feature branch:

refactor/rust-backend           (main coordination branch)
├── ws-a-core                   (Workstream A)  
├── ws-b-admin-sites            (Workstream B - sub-task)
├── ws-b-admin-accounts         (Workstream B - sub-task)
├── ws-c-transformers-openai    (Workstream C)
├── ws-c-transformers-anthropic (Workstream C)
├── ws-d-provider-openai        (Workstream D)
├── ws-d-provider-anthropic     (Workstream D)
└── ...

Merge flow:

  1. Subagent creates branch from refactor/rust-backend
  2. Subagent implements and tests on their branch
  3. Subagent pushes and reports commit SHA to coordinator
  4. Coordinator cherry-picks or merges into refactor/rust-backend
  5. Coordinator verifies compilation and test suite
  6. Branch is deleted

Coordinator Responsibilities

The coordinator (you, the main Hermes session) does:

  1. Phase 1 — Build scaffold single-threaded
  2. Branch management — Create subagent branches, merge back
  3. AppState integration — Merge new fields from subagents into AppState
  4. Router composition — Wire all route modules into routes/mod.rs
  5. Dependency reconciliation — Ensure Cargo.toml stays in sync
  6. Integration verification — Run cargo check, cargo test, cargo clippy after each merge
  7. Final testing — Run full test suite and integration tests after all merges

Example Coordination Workflow

Coordinator:
  1. Complete Phase 1 scaffold
  2. Create branches: ws-a-core, ws-b-admin, ws-c-transformers
  3. Dispatch 3 parallel subagents:

  delegate_task(
    goal="Workstream A: Implement Phase 2 (shared types) + Phase 3 (auth)",
    context="Full task context + owned files + contracts..."
  )
  delegate_task(
    goal="Workstream B: Implement Phase 4 admin routes for sites, accounts, tokens",
    context="Full task context + owned files + contracts..."
  )
  delegate_task(
    goal="Workstream C: Implement Phase 6 canonical format + OpenAI transformers",
    context="Full task context + owned files + contracts..."
  )

  4. Wait for all 3 to complete
  5. Merge each branch, verify compilation
  6. Update AppState and routes/mod.rs
  7. Dispatch next wave of parallel tasks...

Critical Rules for Subagent Collaboration

  1. No two subagents edit the same file. The file ownership matrix is absolute.
  2. Subagents never edit src/lib.rs, src/main.rs, Cargo.toml. They report needed changes, coordinator applies them.
  3. Subagents must return a summary of:
    • New files created
    • New public types/structs (for AppState extension)
    • New route registrations needed
    • New dependencies needed (for Cargo.toml)
  4. Coordinator merges sequentially — even though subagents run in parallel, their code lands one at a time to catch conflicts.
  5. After each merge, run cargo check && cargo test && cargo clippy before the next merge.
  6. No shared mutable state — all shared state is in AppState (Arc-wrapped).
  7. If a subagent hits a blocker (missing type, wrong interface), it should report the exact issue. The coordinator fixes the shared type, then resumes the subagent.