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.
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
- 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.
- Test before claim. Every handler must have an integration test with wiremock or sqlx in-memory SQLite. Run
cargo testandcargo clippybefore committing. - One phase at a time. Complete all tasks in the current phase before moving to the next. Each phase produces a compilable, testable artifact.
- Phase 1 is foundational. All later phases depend on it — project structure, config, DB pool, graceful shutdown, Dockerfile.
- Commit convention:
feat:for new functionality,fix:for bugs,refactor:for restructuring,docs:for documentation,test:for tests. - Rust standard:
cargo fmt,cargo clippy(deny warnings where practical),cargo test. Usethiserrorfor error types,anyhowfor binary-level errors. - Keep it lean. No unnecessary dependencies. Avoid heap allocations in hot paths. Use
Arc<AppState>for shared state, notlazy_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-devon Debian, or use bundled viarusqlitefeature) - 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 configurationsite_api_endpoints— Per-site API endpointssite_disabled_models— Disabled models per siteaccounts— Upstream accountsaccount_tokens— Account-level tokenscheckin_logs— Check-in historymodel_availability— Model availability per accounttoken_model_availability— Model availability per tokentoken_routes— Token routing rulesroute_group_sources— Route groupingroute_channels— Route-to-account bindingsoauth_route_units— OAuth route unitsoauth_route_unit_members— OAuth route unit member accountsproxy_logs— Proxy request logsproxy_debug_traces— Debug trace recordsproxy_debug_attempts— Per-attempt debug recordsproxy_video_tasks— Video generation tasksproxy_files— File storagesettings— Key-value settings storeadmin_snapshots— Cached admin dashboard dataanalytics_projection_checkpoints— Usage aggregation statesite_day_usage— Daily usage per sitesite_hour_usage— Hourly usage per sitemodel_day_usage— Daily usage per modeldownstream_api_keys— Downstream client API keyssite_announcements— Site announcementsusers— Admin usersevents— System eventsorganizations— SaaS organizationsorganization_members— Organization membershipinvitations— Org invitationsdata_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) -> Stringvalidate_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) -> Stringverify_api_key(key, hash) -> bool- Lookup key in
downstream_api_keystable
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:
ProxyTokenAuth— validatesAuthorization: Bearer <proxy_token>for/v1/*routesAuthTokenAuth— validatesAuthorization: Bearer <auth_token>or JWT for/api/*routesDownstreamApiKeyAuth— 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:
- Extracts downstream API key from header
- Checks rate limits
- Calls
token_router::resolve()to find best channel - Calls
conductor::execute()to proxy the request - 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
Authorizationheader 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 → CanonicalRequestoutbound: CanonicalResponse → OpenAI JSON responsestream: Parse OpenAI SSEdata: {...}→ CanonicalStreamEventrequest_bridge: CanonicalRequest → OpenAI HTTP requestresponse_bridge: OpenAI HTTP response → CanonicalResponse
Task 6.3: Anthropic Transformers (transformers/anthropic/)
inbound: CanonicalRequest → Anthropic MessagesRequestoutbound: Anthropic MessagesResponse → CanonicalResponsestream: Parse Anthropic SSE (event: content_block_delta, etc.)compatibility: Anthropic-specific compatibility flags
Task 6.4: Gemini Transformers (transformers/gemini/)
inbound: CanonicalRequest → Gemini GenerateContentRequestoutbound: Gemini GenerateContentResponse → CanonicalResponsestream: Parse Gemini SSEurl_resolver: Resolve Gemini API URLscli_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—OAuthProvidertraitsession.rs— OAuth session store (DB-backed)service.rs— OAuth orchestrationlocal_callback.rs— Temporary HTTP server for local OAuth callbackrefresh_scheduler.rs— Scheduled token refreshroute_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 benchfor transformer hot pathsohaorwrkfor 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
cargo clippy -- -D warnings— zero clippy warningscargo test— all tests passcargo fmt— code is formattedgit diff --cached— review for secrets (no real IPs, tokens, passwords)- 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:
- Subagent creates branch from
refactor/rust-backend - Subagent implements and tests on their branch
- Subagent pushes and reports commit SHA to coordinator
- Coordinator cherry-picks or merges into
refactor/rust-backend - Coordinator verifies compilation and test suite
- Branch is deleted
Coordinator Responsibilities
The coordinator (you, the main Hermes session) does:
- Phase 1 — Build scaffold single-threaded
- Branch management — Create subagent branches, merge back
- AppState integration — Merge new fields from subagents into
AppState - Router composition — Wire all route modules into
routes/mod.rs - Dependency reconciliation — Ensure Cargo.toml stays in sync
- Integration verification — Run
cargo check,cargo test,cargo clippyafter each merge - 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
- No two subagents edit the same file. The file ownership matrix is absolute.
- Subagents never edit
src/lib.rs,src/main.rs,Cargo.toml. They report needed changes, coordinator applies them. - 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)
- Coordinator merges sequentially — even though subagents run in parallel, their code lands one at a time to catch conflicts.
- After each merge, run
cargo check && cargo test && cargo clippybefore the next merge. - No shared mutable state — all shared state is in
AppState(Arc-wrapped). - 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.