b78b622f0c
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.
1908 lines
70 KiB
Markdown
1908 lines
70 KiB
Markdown
# BoosAPI Rust Backend Rewrite — Agent Development Brief
|
|
|
|
> **For Hermes:** Execute this plan phase-by-phase using delegate_task with full task context. Each phase is self-contained and builds on the previous.
|
|
|
|
**Goal:** Rewrite the entire Node.js/Fastify backend in Rust using Axum, producing a production-ready AI API gateway that is API-compatible with the existing frontend.
|
|
|
|
**Architecture:** Replace Fastify + Drizzle ORM + TypeScript with Axum + SQLx + tokio. The frontend (Vite + React SPA) stays unchanged. The Rust binary replaces the Node.js server process entirely.
|
|
|
|
**Repo:** `https://git.boos.lat/boos/BoosAPI.git`
|
|
**Branch:** `refactor/rust-backend`
|
|
**Language:** All code, comments, commit messages, and docs in **English**
|
|
|
|
---
|
|
|
|
## Tech Stack
|
|
|
|
| Layer | Choice | Rationale |
|
|
|-------|--------|-----------|
|
|
| HTTP framework | **Axum 0.8+** | tokio-native, tower middleware, SSE/WS built-in, largest ecosystem |
|
|
| Database | **SQLx** | Compile-time SQL verification, SQLite/MySQL/Postgres in one crate |
|
|
| HTTP client | **reqwest** | Async streaming, connection pooling, TLS |
|
|
| Auth | **jsonwebtoken** + **argon2** | JWT + password hashing |
|
|
| Serialization | **serde** + **serde_json** | Zero-cost JSON, derive macros |
|
|
| Streaming SSE | **axum::response::sse** | Native SSE support |
|
|
| WebSocket | **axum::extract::ws** | tokio-tungstenite under the hood |
|
|
| Logging | **tracing** + **tracing-subscriber** | Structured logging, tokio integration |
|
|
| Config | **figment** + **dotenvy** | Layered config (env → file → defaults) |
|
|
| CLI | **clap** | Admin commands (migrate, seed, etc.) |
|
|
| Testing | **rstest** + **wiremock** | Parameterized tests, HTTP mock server |
|
|
| Async runtime | **tokio** (multi-threaded) | Standard choice for Axum |
|
|
|
|
---
|
|
|
|
## Golden Rules
|
|
|
|
1. **API compatibility first.** Every endpoint path, HTTP method, request body, response JSON, SSE event format, and error shape must match the existing Node.js implementation exactly. The frontend must work with zero changes.
|
|
2. **Test before claim.** Every handler must have an integration test with wiremock or sqlx in-memory SQLite. Run `cargo test` and `cargo clippy` before committing.
|
|
3. **One phase at a time.** Complete all tasks in the current phase before moving to the next. Each phase produces a compilable, testable artifact.
|
|
4. **Phase 1 is foundational.** All later phases depend on it — project structure, config, DB pool, graceful shutdown, Dockerfile.
|
|
5. **Commit convention:** `feat:` for new functionality, `fix:` for bugs, `refactor:` for restructuring, `docs:` for documentation, `test:` for tests.
|
|
6. **Rust standard:** `cargo fmt`, `cargo clippy` (deny warnings where practical), `cargo test`. Use `thiserror` for error types, `anyhow` for binary-level errors.
|
|
7. **Keep it lean.** No unnecessary dependencies. Avoid heap allocations in hot paths. Use `Arc<AppState>` for shared state, not `lazy_static`.
|
|
|
|
---
|
|
|
|
## Architecture
|
|
|
|
```
|
|
┌──────────────────┐
|
|
│ React SPA │
|
|
│ (unchanged) │
|
|
└────────┬─────────┘
|
|
│ HTTP / SSE / WebSocket
|
|
┌────────▼─────────┐
|
|
│ Rust Backend │
|
|
│ (Axum + tokio) │
|
|
│ │
|
|
│ ┌───────────┐ │
|
|
│ │ Axum Router│ │
|
|
│ └─────┬─────┘ │
|
|
│ │ │
|
|
│ ┌─────▼─────┐ │
|
|
│ │ Proxy │ │
|
|
│ │ Core │ │
|
|
│ └─────┬─────┘ │
|
|
│ │ │
|
|
│ ┌─────▼─────┐ │
|
|
│ │Transformers│ │
|
|
│ └─────┬─────┘ │
|
|
│ │ │
|
|
│ ┌─────▼─────┐ │
|
|
│ │ Services │ │
|
|
│ └─────┬─────┘ │
|
|
│ │ │
|
|
│ ┌─────▼─────┐ │
|
|
│ │ SQLx + DB │ │
|
|
│ └───────────┘ │
|
|
└──────────────────┘
|
|
```
|
|
|
|
### Directory Layout
|
|
|
|
```
|
|
boosapi/
|
|
├── Cargo.toml
|
|
├── .env.example
|
|
├── docker/
|
|
│ └── Dockerfile
|
|
├── migrations/ # SQLx migration files
|
|
│ ├── 20260516000001_initial.sql
|
|
│ └── ...
|
|
├── src/
|
|
│ ├── main.rs # Entry point
|
|
│ ├── lib.rs # Re-exports
|
|
│ ├── config.rs # App configuration
|
|
│ ├── error.rs # Unified error types
|
|
│ │
|
|
│ ├── routes/ # Axum route modules (thin)
|
|
│ │ ├── mod.rs # Router construction
|
|
│ │ ├── health.rs # GET /healthz
|
|
│ │ ├── proxy/ # /v1/* proxy routes
|
|
│ │ │ ├── mod.rs
|
|
│ │ │ ├── chat.rs
|
|
│ │ │ ├── completions.rs
|
|
│ │ │ ├── models.rs
|
|
│ │ │ ├── images.rs
|
|
│ │ │ ├── audio.rs
|
|
│ │ │ ├── embeddings.rs
|
|
│ │ │ ├── files.rs
|
|
│ │ │ ├── responses.rs
|
|
│ │ │ ├── responses_ws.rs # WebSocket for responses API
|
|
│ │ │ ├── search.rs
|
|
│ │ │ ├── videos.rs
|
|
│ │ │ ├── gemini.rs
|
|
│ │ │ └── middleware.rs # API key auth for proxy
|
|
│ │ ├── admin/ # /api/* management routes
|
|
│ │ │ ├── mod.rs
|
|
│ │ │ ├── sites.rs
|
|
│ │ │ ├── accounts.rs
|
|
│ │ │ ├── tokens.rs
|
|
│ │ │ ├── stats.rs
|
|
│ │ │ ├── settings.rs
|
|
│ │ │ ├── auth.rs
|
|
│ │ │ ├── users.rs
|
|
│ │ │ ├── downstream_keys.rs
|
|
│ │ │ ├── checkin.rs
|
|
│ │ │ ├── events.rs
|
|
│ │ │ ├── tasks.rs
|
|
│ │ │ ├── search.rs
|
|
│ │ │ ├── monitor.rs
|
|
│ │ │ ├── account_tokens.rs
|
|
│ │ │ ├── invitations.rs
|
|
│ │ │ ├── organizations.rs
|
|
│ │ │ ├── oauth.rs
|
|
│ │ │ ├── announcements.rs
|
|
│ │ │ ├── admin_users.rs
|
|
│ │ │ ├── update_center.rs
|
|
│ │ │ ├── comfyui.rs
|
|
│ │ │ ├── comfyui_agent.rs
|
|
│ │ │ └── video_agent.rs
|
|
│ │ └── user/ # /v1/user/* routes
|
|
│ │ ├── mod.rs
|
|
│ │ ├── api_keys.rs
|
|
│ │ └── profile.rs
|
|
│ │
|
|
│ ├── proxy/ # Proxy orchestration core
|
|
│ │ ├── mod.rs
|
|
│ │ ├── conductor.rs # execute_endpoint_flow()
|
|
│ │ ├── channel.rs # channel selection
|
|
│ │ ├── retry.rs # retry policy
|
|
│ │ ├── service_tier.rs # service tier policy
|
|
│ │ ├── first_byte_timeout.rs # first byte timeout handling
|
|
│ │ ├── responses_preflight.rs # preflight check for responses API
|
|
│ │ ├── web_search.rs # web search simulation
|
|
│ │ ├── downstream_context.rs # downstream client context
|
|
│ │ ├── providers/ # Provider profiles & registry
|
|
│ │ │ ├── mod.rs
|
|
│ │ │ ├── registry.rs
|
|
│ │ │ ├── types.rs
|
|
│ │ │ ├── header_utils.rs
|
|
│ │ │ ├── openai.rs
|
|
│ │ │ ├── anthropic.rs
|
|
│ │ │ ├── gemini.rs
|
|
│ │ │ ├── codex.rs
|
|
│ │ │ ├── claude.rs
|
|
│ │ │ ├── gemini_cli.rs
|
|
│ │ │ ├── antigravity.rs
|
|
│ │ │ ├── new_api.rs
|
|
│ │ │ ├── one_api.rs
|
|
│ │ │ ├── one_hub.rs
|
|
│ │ │ ├── done_hub.rs
|
|
│ │ │ ├── sub2api.rs
|
|
│ │ │ └── veloera.rs
|
|
│ │ ├── executors/ # Runtime executors
|
|
│ │ │ ├── mod.rs
|
|
│ │ │ ├── types.rs
|
|
│ │ │ ├── claude_executor.rs
|
|
│ │ │ ├── codex_executor.rs
|
|
│ │ │ ├── gemini_cli_executor.rs
|
|
│ │ │ └── antigravity_executor.rs
|
|
│ │ ├── runtime/ # Runtime state & codex sessions
|
|
│ │ │ ├── mod.rs
|
|
│ │ │ ├── types.rs
|
|
│ │ │ ├── codex_http_session.rs
|
|
│ │ │ ├── codex_ws_session.rs
|
|
│ │ │ ├── codex_session_store.rs
|
|
│ │ │ ├── codex_ws_headers.rs
|
|
│ │ │ └── responses_sse.rs
|
|
│ │ ├── surfaces/ # API surface handling
|
|
│ │ │ ├── mod.rs
|
|
│ │ │ ├── shared.rs
|
|
│ │ │ ├── chat.rs
|
|
│ │ │ ├── files.rs
|
|
│ │ │ ├── input_files.rs
|
|
│ │ │ ├── models.rs
|
|
│ │ │ ├── openai_responses.rs
|
|
│ │ │ └── gemini.rs
|
|
│ │ ├── cli_profiles/ # CLI integration profiles
|
|
│ │ │ ├── mod.rs
|
|
│ │ │ ├── types.rs
|
|
│ │ │ ├── registry.rs
|
|
│ │ │ ├── claude_code.rs
|
|
│ │ │ ├── codex.rs
|
|
│ │ │ ├── gemini_cli.rs
|
|
│ │ │ └── generic.rs
|
|
│ │ └── capabilities/ # Platform capabilities
|
|
│ │ ├── mod.rs
|
|
│ │ ├── conversation_file.rs
|
|
│ │ └── responses_compact.rs
|
|
│ │
|
|
│ ├── transformers/ # Protocol transformers
|
|
│ │ ├── mod.rs
|
|
│ │ ├── contracts.rs
|
|
│ │ ├── canonical/ # Internal canonical format
|
|
│ │ │ ├── mod.rs
|
|
│ │ │ ├── types.rs
|
|
│ │ │ ├── request.rs
|
|
│ │ │ ├── response.rs
|
|
│ │ │ ├── attachments.rs
|
|
│ │ │ ├── reasoning.rs
|
|
│ │ │ ├── tools.rs
|
|
│ │ │ ├── envelope.rs
|
|
│ │ │ ├── openai_request_bridge.rs
|
|
│ │ │ └── continuation_bridge.rs
|
|
│ │ ├── openai/
|
|
│ │ │ ├── mod.rs
|
|
│ │ │ ├── chat/ # /v1/chat/completions transformers
|
|
│ │ │ │ ├── mod.rs
|
|
│ │ │ │ ├── inbound.rs
|
|
│ │ │ │ ├── outbound.rs
|
|
│ │ │ │ ├── stream.rs
|
|
│ │ │ │ ├── aggregator.rs
|
|
│ │ │ │ ├── request_bridge.rs
|
|
│ │ │ │ ├── response_bridge.rs
|
|
│ │ │ │ ├── stream_bridge.rs
|
|
│ │ │ │ ├── usage.rs
|
|
│ │ │ │ ├── model.rs
|
|
│ │ │ │ └── helpers.rs
|
|
│ │ │ └── responses/ # /v1/responses transformers
|
|
│ │ │ ├── mod.rs
|
|
│ │ │ ├── inbound.rs
|
|
│ │ │ ├── outbound.rs
|
|
│ │ │ ├── stream.rs
|
|
│ │ │ ├── aggregator.rs
|
|
│ │ │ ├── request_bridge.rs
|
|
│ │ │ ├── response_bridge.rs
|
|
│ │ │ ├── stream_bridge.rs
|
|
│ │ │ ├── usage.rs
|
|
│ │ │ ├── model.rs
|
|
│ │ │ ├── normalization.rs
|
|
│ │ │ ├── compatibility.rs
|
|
│ │ │ ├── codex_compatibility.rs
|
|
│ │ │ ├── mcp_compatibility.rs
|
|
│ │ │ ├── route_compatibility.rs
|
|
│ │ │ └── continuation.rs
|
|
│ │ ├── anthropic/
|
|
│ │ │ ├── mod.rs
|
|
│ │ │ └── messages/ # Anthropic Messages API transformers
|
|
│ │ │ ├── mod.rs
|
|
│ │ │ ├── inbound.rs
|
|
│ │ │ ├── outbound.rs
|
|
│ │ │ ├── stream.rs
|
|
│ │ │ ├── aggregator.rs
|
|
│ │ │ ├── request_bridge.rs
|
|
│ │ │ ├── response_bridge.rs
|
|
│ │ │ ├── stream_bridge.rs
|
|
│ │ │ ├── usage.rs
|
|
│ │ │ ├── compatibility.rs
|
|
│ │ │ └── conversion.rs
|
|
│ │ ├── gemini/
|
|
│ │ │ ├── mod.rs
|
|
│ │ │ └── generate_content/ # Gemini API transformers
|
|
│ │ │ ├── mod.rs
|
|
│ │ │ ├── inbound.rs
|
|
│ │ │ ├── outbound.rs
|
|
│ │ │ ├── stream.rs
|
|
│ │ │ ├── aggregator.rs
|
|
│ │ │ ├── request_bridge.rs
|
|
│ │ │ ├── response_bridge.rs
|
|
│ │ │ ├── stream_bridge.rs
|
|
│ │ │ ├── usage.rs
|
|
│ │ │ ├── conversion.rs
|
|
│ │ │ ├── convert.rs
|
|
│ │ │ ├── url_resolver.rs
|
|
│ │ │ └── cli_bridge.rs
|
|
│ │ └── shared/ # Shared transformer utilities
|
|
│ │ ├── mod.rs
|
|
│ │ ├── normalized.rs
|
|
│ │ ├── input_file.rs
|
|
│ │ ├── chat_formats.rs
|
|
│ │ ├── chat_endpoint.rs
|
|
│ │ ├── endpoint_compat.rs
|
|
│ │ ├── protocol_lifecycle.rs
|
|
│ │ ├── protocol_model.rs
|
|
│ │ ├── reasoning_transport.rs
|
|
│ │ ├── think_tag_parser.rs
|
|
│ │ └── tool_name_shortener.rs
|
|
│ │
|
|
│ ├── services/ # Business logic services
|
|
│ │ ├── mod.rs
|
|
│ │ ├── auth.rs # JWT + API key auth service
|
|
│ │ ├── account.rs # Account CRUD
|
|
│ │ ├── account_token.rs # Account token management
|
|
│ │ ├── account_credential.rs # Credential management
|
|
│ │ ├── account_health.rs # Account health checks
|
|
│ │ ├── account_mutation.rs # Account mutation workflow
|
|
│ │ ├── account_update.rs # Account update workflow
|
|
│ │ ├── account_extra_config.rs# Extra config handling
|
|
│ │ ├── accounts_overview.rs # Dashboard overview aggregation
|
|
│ │ ├── site.rs # Site CRUD
|
|
│ │ ├── site_api_endpoint.rs # Site endpoint management
|
|
│ │ ├── site_api_key_migration.rs # API key → account migration
|
|
│ │ ├── site_proxy.rs # Site proxy configuration
|
|
│ │ ├── site_detector.rs # Platform auto-detection
|
|
│ │ ├── site_custom_headers.rs # Custom header management
|
|
│ │ ├── token_router.rs # Token routing decision engine
|
|
│ │ ├── route_cooldown.rs # Route cooldown management
|
|
│ │ ├── route_refresh.rs # Route decision refresh
|
|
│ │ ├── route_decision_snapshot.rs
|
|
│ │ ├── routing_strategy.rs # Routing strategy logic
|
|
│ │ ├── runtime_dispatch.rs # Runtime dispatch service
|
|
│ │ ├── model.rs # Model discovery & management
|
|
│ │ ├── model_analysis.rs # Model analysis service
|
|
│ ├── model_pricing.rs # Model pricing & cost
|
|
│ │ ├── model_availability_probe.rs
|
|
│ │ ├── channel_recovery_probe.rs
|
|
│ │ ├── billing.rs # Proxy billing & usage tracking
|
|
│ │ ├── usage_aggregation.rs # Usage aggregation (hour/day/model)
|
|
│ │ ├── proxy_log.rs # Proxy log CRUD
|
|
│ │ ├── proxy_log_retention.rs # Log cleanup & retention
|
|
│ │ ├── proxy_file.rs # File store for proxy
|
|
│ │ ├── proxy_file_retention.rs# File retention cleanup
|
|
│ │ ├── proxy_video_task.rs # Video task store
|
|
│ │ ├── proxy_input_file.rs # Input file resolver
|
|
│ │ ├── proxy_debug_trace.rs # Debug trace recording
|
|
│ │ ├── proxy_debug_trace_store.rs
|
|
│ │ ├── proxy_failure_judge.rs # Failure classification
|
|
│ │ ├── proxy_retry_policy.rs # Proxy retry policy service
|
|
│ │ ├── proxy_channel_coordinator.rs
|
|
│ │ ├── proxy_channel_retry.rs # Channel retry fallback
|
|
│ │ ├── proxy_usage_fallback.rs
|
|
│ │ ├── proxy_usage_parser.rs # Upstream usage response parser
|
|
│ │ ├── downstream_api_key.rs # Downstream API key CRUD
|
|
│ │ ├── downstream_key_trend.rs
|
|
│ │ ├── downstream_policy.rs # Downstream policy types
|
|
│ │ ├── checkin.rs # Check-in logic
|
|
│ │ ├── checkin_scheduler.rs # Scheduled check-in runner
|
|
│ │ ├── checkin_reward_parser.rs
|
|
│ │ ├── daily_summary.rs # Daily summary generation
|
|
│ │ ├── today_income_reward.rs # Today's reward calculation
|
|
│ │ ├── balance.rs # Balance refresh service
|
|
│ │ ├── user.rs # User CRUD
|
|
│ │ ├── user_api_key.rs # User API key management
|
|
│ │ ├── organization.rs # Organization CRUD
|
|
│ │ ├── invitation.rs # Invitation management
|
|
│ │ ├── plan.rs # Plan/limits service
|
|
│ │ ├── event.rs # Event log service
|
|
│ │ ├── alert.rs # Alert rules engine
|
|
│ │ ├── alert_service.rs # Alert dispatch
|
|
│ │ ├── notification_throttle.rs
|
|
│ │ ├── notify.rs # Multi-channel notification
|
|
│ │ ├── brand_matcher.rs # Client/brand identification
|
|
│ │ ├── payload_rules.rs # Payload rule engine
|
|
│ │ ├── upstream_endpoint.rs # Upstream endpoint derivation
|
|
│ │ ├── upstream_endpoint_runtime.rs
|
|
│ │ ├── upstream_endpoint_runtime_memory.rs
|
|
│ │ ├── upstream_request.rs # Upstream request builder
|
|
│ │ ├── upstream_model_description.rs
|
|
│ │ ├── url_normalization.rs # URL normalization utilities
|
|
│ │ ├── local_time.rs # Local time utilities
|
|
│ │ ├── snapshot_cache.rs # Snapshot cache service
|
|
│ │ ├── admin_snapshot_store.rs
|
|
│ │ ├── admin_snapshot_warm.rs # Warm snapshot generation
|
|
│ │ ├── dashboard_snapshot.rs # Dashboard aggregation
|
|
│ │ ├── stats_shared.rs # Shared stats utilities
|
|
│ │ ├── backup.rs # WebDAV backup service
|
|
│ │ ├── factory_reset.rs # Factory reset service
|
|
│ │ ├── database_migration.rs # Runtime DB migration service
|
|
│ │ ├── default_site_seed.rs # Default site seeding
|
|
│ │ ├── platform_discovery.rs # Platform discovery registry
|
|
│ │ ├── startup_info.rs # Startup summary
|
|
│ │ ├── cron/ # Background schedulers
|
|
│ │ │ ├── mod.rs
|
|
│ │ │ ├── scheduler.rs # Generic cron scheduler
|
|
│ │ │ └── registry.rs # Scheduler registry
|
|
│ │ ├── oauth/ # OAuth services
|
|
│ │ │ ├── mod.rs
|
|
│ │ │ ├── provider.rs # OAuth provider trait
|
|
│ │ │ ├── providers.rs # Provider implementations
|
|
│ │ │ ├── session.rs # OAuth session store
|
|
│ │ │ ├── service.rs # OAuth orchestration
|
|
│ │ │ ├── quota.rs # OAuth quota management
|
|
│ │ │ ├── quota_types.rs
|
|
│ │ │ ├── route_unit.rs # Route unit management
|
|
│ │ │ ├── account.rs # OAuth account binding
|
|
│ │ │ ├── site_registry.rs # OAuth site registration
|
|
│ │ │ ├── identity_backfill.rs
|
|
│ │ │ ├── refresh_scheduler.rs
|
|
│ │ │ ├── refresh_singleflight.rs
|
|
│ │ │ ├── request_proxy.rs # OAuth proxied requests
|
|
│ │ │ ├── local_callback.rs # Local OAuth callback server
|
|
│ │ │ ├── codex.rs # Codex OAuth provider
|
|
│ │ │ ├── claude.rs # Claude OAuth provider
|
|
│ │ │ ├── gemini_cli.rs # Gemini CLI OAuth provider
|
|
│ │ │ └── antigravity.rs # Antigravity OAuth provider
|
|
│ │ ├── update_center/ # Update center services
|
|
│ │ │ ├── mod.rs
|
|
│ │ │ ├── config.rs
|
|
│ │ │ ├── status.rs
|
|
│ │ │ ├── version.rs
|
|
│ │ │ ├── runtime_state.rs
|
|
│ │ │ ├── polling.rs
|
|
│ │ │ ├── deploy_guard.rs
|
|
│ │ │ ├── reminder.rs
|
|
│ │ │ ├── helper_client.rs
|
|
│ │ │ └── task_constants.rs
|
|
│ │ ├── announcement/ # Site announcement services
|
|
│ │ │ ├── mod.rs
|
|
│ │ │ ├── polling.rs
|
|
│ │ │ └── service.rs
|
|
│ │ ├── background_task.rs # Background task service
|
|
│ │ ├── api_key_batch.rs # API key batch operations
|
|
│ │ └── shared/ # Shared service utilities
|
|
│ │ └── api_key_batch_core.rs
|
|
│ │
|
|
│ ├── db/ # Database layer
|
|
│ │ ├── mod.rs
|
|
│ │ ├── pool.rs # Connection pool management
|
|
│ │ ├── migrate.rs # Migration runner
|
|
│ │ ├── models/ # Rust structs matching DB tables
|
|
│ │ │ ├── mod.rs
|
|
│ │ │ ├── site.rs
|
|
│ │ │ ├── account.rs
|
|
│ │ │ ├── account_token.rs
|
|
│ │ │ ├── checkin_log.rs
|
|
│ │ │ ├── model_availability.rs
|
|
│ │ │ ├── token_model_availability.rs
|
|
│ │ │ ├── token_route.rs
|
|
│ │ │ ├── route_group_source.rs
|
|
│ │ │ ├── route_channel.rs
|
|
│ │ │ ├── oauth_route_unit.rs
|
|
│ │ │ ├── oauth_route_unit_member.rs
|
|
│ │ │ ├── site_api_endpoint.rs
|
|
│ │ │ ├── site_disabled_model.rs
|
|
│ │ │ ├── proxy_log.rs
|
|
│ │ │ ├── proxy_debug_trace.rs
|
|
│ │ │ ├── proxy_debug_attempt.rs
|
|
│ │ │ ├── proxy_file.rs
|
|
│ │ │ ├── proxy_video_task.rs
|
|
│ │ │ ├── setting.rs
|
|
│ │ │ ├── admin_snapshot.rs
|
|
│ │ │ ├── analytics_checkpoint.rs
|
|
│ │ │ ├── site_day_usage.rs
|
|
│ │ │ ├── site_hour_usage.rs
|
|
│ │ │ ├── model_day_usage.rs
|
|
│ │ │ ├── downstream_api_key.rs
|
|
│ │ │ ├── site_announcement.rs
|
|
│ │ │ ├── user.rs
|
|
│ │ │ ├── event.rs
|
|
│ │ │ ├── organization.rs
|
|
│ │ │ ├── organization_member.rs
|
|
│ │ │ ├── invitation.rs
|
|
│ │ │ └── data_plan.rs
|
|
│ │ └── queries/ # Typed query functions
|
|
│ │ ├── mod.rs
|
|
│ │ ├── sites.rs
|
|
│ │ ├── accounts.rs
|
|
│ │ ├── tokens.rs
|
|
│ │ ├── proxy_logs.rs
|
|
│ │ ├── usage.rs
|
|
│ │ ├── users.rs
|
|
│ │ └── settings.rs
|
|
│ │
|
|
│ ├── auth/ # Authentication primitives
|
|
│ │ ├── mod.rs
|
|
│ │ ├── jwt.rs
|
|
│ │ ├── api_key.rs
|
|
│ │ └── password.rs
|
|
│ │
|
|
│ ├── desktop/ # Electron desktop support
|
|
│ │ ├── mod.rs
|
|
│ │ ├── adapter.rs
|
|
│ │ └── runtime.rs
|
|
│ │
|
|
│ └── shared/ # Shared across crate
|
|
│ ├── mod.rs
|
|
│ ├── sse.rs # SSE utility types
|
|
│ ├── stream.rs # Streaming helpers
|
|
│ ├── pagination.rs # Pagination helpers
|
|
│ ├── time.rs # Time utilities
|
|
│ └── json.rs # JSON helpers
|
|
│
|
|
└── tests/ # Integration tests
|
|
├── common/
|
|
│ └── mod.rs # Test helpers
|
|
├── health.rs
|
|
├── proxy/
|
|
│ ├── chat.rs
|
|
│ ├── models.rs
|
|
│ └── streaming.rs
|
|
├── admin/
|
|
│ ├── sites.rs
|
|
│ ├── accounts.rs
|
|
│ └── tokens.rs
|
|
└── oauth/
|
|
└── flow.rs
|
|
```
|
|
|
|
---
|
|
|
|
## Getting Started (from scratch)
|
|
|
|
### Prerequisites
|
|
|
|
- Rust 1.85+ (install via `rustup`)
|
|
- SQLite dev libs (`libsqlite3-dev` on Debian, or use bundled via `rusqlite` feature)
|
|
- Docker (optional, for container builds)
|
|
- `cargo-nextest` (optional, for faster test runs: `cargo install cargo-nextest`)
|
|
|
|
### Bootstrap
|
|
|
|
```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<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`:**
|
|
|
|
```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<T> = Result<T, AppError>;
|
|
```
|
|
|
|
**`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<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`)
|
|
|
|
```rust
|
|
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`)
|
|
|
|
```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<AppState>,
|
|
) -> AppResult<Json<Value>> {
|
|
// Simple DB ping
|
|
sqlx::query("SELECT 1")
|
|
.execute(&*state.db)
|
|
.await?;
|
|
|
|
Ok(Json(json!({
|
|
"status": "ok",
|
|
"database": "ok",
|
|
"version": env!("CARGO_PKG_VERSION"),
|
|
})))
|
|
}
|
|
```
|
|
|
|
### Task 1.7: Initial Migration (`migrations/20260516000001_initial.sql`)
|
|
|
|
Create the initial migration with ALL tables from the Drizzle schema (see `src/server/db/schema.ts` for reference — 18 tables). Include all indexes, unique constraints, and foreign keys.
|
|
|
|
Key tables:
|
|
- `sites` — Platform site configuration
|
|
- `site_api_endpoints` — Per-site API endpoints
|
|
- `site_disabled_models` — Disabled models per site
|
|
- `accounts` — Upstream accounts
|
|
- `account_tokens` — Account-level tokens
|
|
- `checkin_logs` — Check-in history
|
|
- `model_availability` — Model availability per account
|
|
- `token_model_availability` — Model availability per token
|
|
- `token_routes` — Token routing rules
|
|
- `route_group_sources` — Route grouping
|
|
- `route_channels` — Route-to-account bindings
|
|
- `oauth_route_units` — OAuth route units
|
|
- `oauth_route_unit_members` — OAuth route unit member accounts
|
|
- `proxy_logs` — Proxy request logs
|
|
- `proxy_debug_traces` — Debug trace records
|
|
- `proxy_debug_attempts` — Per-attempt debug records
|
|
- `proxy_video_tasks` — Video generation tasks
|
|
- `proxy_files` — File storage
|
|
- `settings` — Key-value settings store
|
|
- `admin_snapshots` — Cached admin dashboard data
|
|
- `analytics_projection_checkpoints` — Usage aggregation state
|
|
- `site_day_usage` — Daily usage per site
|
|
- `site_hour_usage` — Hourly usage per site
|
|
- `model_day_usage` — Daily usage per model
|
|
- `downstream_api_keys` — Downstream client API keys
|
|
- `site_announcements` — Site announcements
|
|
- `users` — Admin users
|
|
- `events` — System events
|
|
- `organizations` — SaaS organizations
|
|
- `organization_members` — Organization membership
|
|
- `invitations` — Org invitations
|
|
- `data_plans` — Subscription plans
|
|
|
|
### Task 1.8: Dockerfile (`docker/Dockerfile`)
|
|
|
|
```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<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`)
|
|
|
|
```rust
|
|
use serde::{Serialize, Deserialize};
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
pub struct PaginationParams {
|
|
pub page: Option<i64>,
|
|
pub page_size: Option<i64>,
|
|
pub sort_by: Option<String>,
|
|
pub sort_order: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Serialize)]
|
|
pub struct PaginatedResponse<T: Serialize> {
|
|
pub data: Vec<T>,
|
|
pub total: i64,
|
|
pub page: i64,
|
|
pub page_size: i64,
|
|
pub total_pages: i64,
|
|
}
|
|
```
|
|
|
|
### Task 2.3: API Response Envelope (`src/shared/mod.rs`)
|
|
|
|
Standardized API response format matching the existing Node.js output.
|
|
|
|
---
|
|
|
|
## Phase 3: Authentication
|
|
|
|
**Scope:** JWT generation/validation, API key hashing/verification, password hashing, auth middleware.
|
|
|
|
### Task 3.1: JWT Service (`src/auth/jwt.rs`)
|
|
|
|
- `generate_jwt(secret, user_id, role) -> String`
|
|
- `validate_jwt(secret, token) -> Claims`
|
|
- Claims struct: `sub`, `role`, `exp`, `iat`
|
|
|
|
### Task 3.2: API Key Authentication (`src/auth/api_key.rs`)
|
|
|
|
- Blake3/Argon2 hashing for stored API keys
|
|
- `hash_api_key(key) -> String`
|
|
- `verify_api_key(key, hash) -> bool`
|
|
- Lookup key in `downstream_api_keys` table
|
|
|
|
### Task 3.3: Password Hashing (`src/auth/password.rs`)
|
|
|
|
- `hash_password(password) -> String` (argon2id)
|
|
- `verify_password(password, hash) -> bool`
|
|
|
|
### Task 3.4: Auth Middleware (Axum Layered Middleware)
|
|
|
|
Two auth layers:
|
|
1. `ProxyTokenAuth` — validates `Authorization: Bearer <proxy_token>` for `/v1/*` routes
|
|
2. `AuthTokenAuth` — validates `Authorization: Bearer <auth_token>` or JWT for `/api/*` routes
|
|
3. `DownstreamApiKeyAuth` — validates downstream API keys for `/v1/*` proxy routes
|
|
|
|
---
|
|
|
|
## Phase 4: Admin API Routes
|
|
|
|
**Scope:** All `/api/*` management endpoints.
|
|
|
|
Each route module follows this pattern:
|
|
|
|
```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<CanonicalMessage>,
|
|
pub stream: bool,
|
|
pub max_tokens: Option<i32>,
|
|
pub temperature: Option<f64>,
|
|
pub top_p: Option<f64>,
|
|
pub stop: Option<Vec<String>>,
|
|
pub tools: Option<Vec<Tool>>,
|
|
pub tool_choice: Option<ToolChoice>,
|
|
pub reasoning: Option<ReasoningConfig>,
|
|
// ... more fields
|
|
}
|
|
|
|
pub struct CanonicalResponse {
|
|
pub id: String,
|
|
pub model: String,
|
|
pub choices: Vec<CanonicalChoice>,
|
|
pub usage: Option<Usage>,
|
|
}
|
|
```
|
|
|
|
### Task 6.2: OpenAI Transformers (`transformers/openai/`)
|
|
|
|
- `inbound`: OpenAI JSON → CanonicalRequest
|
|
- `outbound`: CanonicalResponse → OpenAI JSON response
|
|
- `stream`: Parse OpenAI SSE `data: {...}` → CanonicalStreamEvent
|
|
- `request_bridge`: CanonicalRequest → OpenAI HTTP request
|
|
- `response_bridge`: OpenAI HTTP response → CanonicalResponse
|
|
|
|
### Task 6.3: Anthropic Transformers (`transformers/anthropic/`)
|
|
|
|
- `inbound`: CanonicalRequest → Anthropic MessagesRequest
|
|
- `outbound`: Anthropic MessagesResponse → CanonicalResponse
|
|
- `stream`: Parse Anthropic SSE (`event: content_block_delta`, etc.)
|
|
- `compatibility`: Anthropic-specific compatibility flags
|
|
|
|
### Task 6.4: Gemini Transformers (`transformers/gemini/`)
|
|
|
|
- `inbound`: CanonicalRequest → Gemini GenerateContentRequest
|
|
- `outbound`: Gemini GenerateContentResponse → CanonicalResponse
|
|
- `stream`: Parse Gemini SSE
|
|
- `url_resolver`: Resolve Gemini API URLs
|
|
- `cli_bridge`: Gemini CLI integration
|
|
|
|
---
|
|
|
|
## Phase 7: Provider Implementations
|
|
|
|
**Scope:** Actual HTTP calls to upstream AI providers.
|
|
|
|
| Provider | Crate module | Features |
|
|
|----------|-------------|----------|
|
|
| OpenAI / OpenAI-compatible | `proxy/providers/openai.rs` | Chat, streaming, models, images, embeddings, audio, files, responses |
|
|
| Anthropic | `proxy/providers/anthropic.rs` | Messages, streaming, models |
|
|
| Gemini | `proxy/providers/gemini.rs` | Generate content, streaming, models |
|
|
| Codex | `proxy/providers/codex.rs` | Chat, streaming, WebSocket sessions |
|
|
| Claude | `proxy/providers/claude.rs` | Chat, streaming |
|
|
| Gemini CLI | `proxy/providers/gemini_cli.rs` | CLI bridge |
|
|
| Antigravity | `proxy/providers/antigravity.rs` | Custom provider |
|
|
| NewAPI | `proxy/providers/new_api.rs` | OpenAI-compatible |
|
|
| OneAPI | `proxy/providers/one_api.rs` | OpenAI-compatible |
|
|
| OneHub | `proxy/providers/one_hub.rs` | OpenAI-compatible |
|
|
| DoneHub | `proxy/providers/done_hub.rs` | OpenAI-compatible |
|
|
| Sub2API | `proxy/providers/sub2api.rs` | Subscription-based |
|
|
| Veloera | `proxy/providers/veloera.rs` | Custom provider |
|
|
|
|
**Provider interface:**
|
|
```rust
|
|
#[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
|
|
|
|
```rust
|
|
// 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:
|
|
|
|
```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<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`:
|
|
|
|
```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<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:
|
|
|
|
```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.
|