# 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` 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 ```bash # 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 ```bash cargo init --name boosapi ``` **Cargo.toml:** ```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`) ```rust 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, } #[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, pub runtime_db_dialect: Option, pub runtime_db_ssl: Option, } #[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 { 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`:** ```rust 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 = Result; ``` **`src/lib.rs`:** ```rust 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>, } impl AppState { pub fn new(config: config::AppConfig, db: sqlx::Pool) -> Self { Self { config, db: Arc::new(db), } } } ``` ### Task 1.4: Database Pool (`src/db/pool.rs`) ```rust use sqlx::sqlite::{SqlitePoolOptions, SqlitePool}; pub async fn create_pool(database_url: &str, max_connections: u32) -> anyhow::Result { 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`) ```rust 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`:** ```rust 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`:** ```rust use axum::Json; use serde_json::{json, Value}; use crate::{AppState, error::AppResult}; pub async fn health_check( state: axum::extract::State, ) -> AppResult> { // 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`) ```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`) ```rust use axum::response::sse::Event; use serde::Serialize; use std::convert::Infallible; pub fn sse_event(event_type: &str, data: &T) -> Result { Event::default() .event(event_type) .data(serde_json::to_string(data).unwrap_or_default()) } ``` ### Task 2.2: Pagination Helpers (`src/shared/pagination.rs`) ```rust use serde::{Serialize, Deserialize}; #[derive(Debug, Deserialize)] pub struct PaginationParams { pub page: Option, pub page_size: Option, pub sort_by: Option, pub sort_order: Option, } #[derive(Debug, Serialize)] pub struct PaginatedResponse { pub data: Vec, 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 ` for `/v1/*` routes 2. `AuthTokenAuth` — validates `Authorization: Bearer ` 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: ```rust 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: ```rust pub struct CanonicalRequest { pub model: String, pub messages: Vec, pub stream: bool, pub max_tokens: Option, pub temperature: Option, pub top_p: Option, pub stop: Option>, pub tools: Option>, pub tool_choice: Option, pub reasoning: Option, // ... more fields } pub struct CanonicalResponse { pub id: String, pub model: String, pub choices: Vec, pub usage: Option, } ``` ### 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:** ```rust #[async_trait] pub trait Provider: Send + Sync { fn name(&self) -> &'static str; async fn chat_completion(&self, req: ChatRequest) -> Result; async fn chat_completion_stream(&self, req: ChatRequest) -> Result; async fn models(&self) -> Result>; } ``` --- ## Phase 8: Streaming — SSE & WebSocket **Scope:** All streaming paths — the most performance-critical part. ### Task 8.1: SSE Streaming Relay ```rust // Axum handler for POST /v1/chat/completions with stream: true async fn proxy_chat_stream( State(state): State, Json(body): Json, ) -> Result>> { // 1. Parse body // 2. Resolve route via token_router // 3. Call conductor::execute_stream() // 4. Return Sse 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: ```rust 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: ```rust #[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 ```bash # 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: ```rust // Phase 1 defines: #[derive(Clone)] pub struct AppState { pub config: AppConfig, pub db: Pool, } // Phase 5 (proxy core) adds to AppState: #[derive(Clone)] pub struct AppState { pub config: AppConfig, pub db: Pool, pub proxy_core: Arc, // 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`: ```rust // Each route module: pub fn routes(state: AppState) -> Router { Router::new() .route("/api/sites", get(list_sites)) .with_state(state) } ``` The coordinator merges them: ```rust 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`: ```rust // 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 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: ```rust // 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.