Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 13 additions & 125 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ keywords = ["chat", "axum", "websocket", "postgres"]
categories = ["web-programming"]

[dependencies]
axum = { version = "0.8" }
axum = { version = "0.8" }
tokio = { version = "1.48", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
Expand All @@ -36,7 +36,7 @@ reqwest = { version = "0.12", default-features = false, features = [
"json",
"multipart",
] }
tower-http = { version = "0.5", features = ["cors"] }
tower-http = { version = "0.6", features = ["cors"] }
omniference = { version = "0.3" }
aes-gcm = "0.10"
rand = "0.8"
Expand Down
30 changes: 30 additions & 0 deletions migrations/20260727000000_gateway_auth.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
CREATE TABLE gateway_projects (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
owner_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
team_id UUID REFERENCES teams(id) ON DELETE CASCADE,
name VARCHAR(100) NOT NULL,
is_enabled BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX gateway_projects_owner_idx ON gateway_projects(owner_id);
CREATE INDEX gateway_projects_team_idx ON gateway_projects(team_id) WHERE team_id IS NOT NULL;

CREATE TABLE gateway_api_keys (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
project_id UUID NOT NULL REFERENCES gateway_projects(id) ON DELETE CASCADE,
name VARCHAR(100) NOT NULL,
secret_hash TEXT NOT NULL,
key_prefix VARCHAR(48) NOT NULL,
last_four VARCHAR(4) NOT NULL,
scopes JSONB NOT NULL DEFAULT '["inference:read", "inference:write"]'::jsonb,
is_enabled BOOLEAN NOT NULL DEFAULT TRUE,
expires_at TIMESTAMPTZ,
revoked_at TIMESTAMPTZ,
last_used_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE UNIQUE INDEX gateway_api_keys_prefix_idx ON gateway_api_keys(key_prefix);
CREATE INDEX gateway_api_keys_project_idx ON gateway_api_keys(project_id);
14 changes: 10 additions & 4 deletions src/ai.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,25 @@

use omniference::{
OmniferenceEngine,
middleware::cost::QueuedCostSink,
types::{ProviderConfig, ProviderEndpoint},
};
use sqlx::PgPool;
use std::{collections::BTreeMap, sync::Arc};
use tokio::sync::RwLock;

use crate::types::JobState;
use crate::types::providers::Provider;
use crate::utils::encryption::decrypt_api_key;

/// Someone kill me for this name please
pub static OF_ENGINE: std::sync::OnceLock<Arc<RwLock<OmniferenceEngine>>> = std::sync::OnceLock::new();

/// Initialize the AI engine with providers from the database
pub async fn init(pool: &PgPool) {
let engine = Arc::new(RwLock::new(OmniferenceEngine::new()));
pub async fn init(state: &Arc<JobState>) {
let pool = &state.db;
let cost_sink = QueuedCostSink::spawn(Arc::new(crate::utils::omniference_cost::OxideCostSink::new(Arc::clone(state))));
let engine = Arc::new(RwLock::new(OmniferenceEngine::with_cost_sink(cost_sink)));

let providers = Provider::list_enabled_system(pool).await.unwrap_or_default();

Expand Down Expand Up @@ -69,13 +73,15 @@ pub async fn catalog() -> Option<Arc<omniference::catalog::Catalog>> {
pub async fn sync_pricing_overrides(_pool: &PgPool) {}

/// Reload providers from the database
pub async fn reload_providers(pool: &PgPool) {
pub async fn reload_providers(state: &Arc<JobState>) {
let pool = &state.db;
let providers = Provider::list_enabled_system(pool).await.unwrap_or_default();

let provider_count = providers.len();

// Create a fresh engine with new providers
let new_engine = OmniferenceEngine::new();
let cost_sink = QueuedCostSink::spawn(Arc::new(crate::utils::omniference_cost::OxideCostSink::new(Arc::clone(state))));
let new_engine = OmniferenceEngine::with_cost_sink(cost_sink);
let engine_arc = get();
let mut engine_write = engine_arc.write().await;
*engine_write = new_engine;
Expand Down
12 changes: 5 additions & 7 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,19 +69,17 @@ async fn main() {
i18n::I18n::init(&pool).await;
println!("[I18N] Translations loaded");

ai::init(&pool).await;

let app_state = Arc::new(JobState {
db: pool.clone(),
db: pool,
mcp_pool: crate::utils::tools::McpConnectionPool::new(),
client_tool_pending: crate::types::state::ClientToolPending::new(),
});
ai::init(&app_state).await;

tokio::spawn(jobs::start_job_scheduler(app_state.clone()));

let app = Router::new()
.merge(routes::build_router())
.with_state(app_state)
.merge(routes::build_router(Arc::clone(&app_state)))
.layer(DefaultBodyLimit::max(8 * 1024 * 1024));

let address = format!(
Expand All @@ -94,15 +92,15 @@ async fn main() {

println!("[SERVER] Listening on http://{}", address);

let pool_for_shutdown = pool.clone();
let state_for_shutdown = Arc::clone(&app_state);

let server = axum::serve(listener, app).with_graceful_shutdown(async move {
shutdown_signal().await;

println!("[SERVER] Shutdown signal received");
println!("[DATABASE] Closing pool...");

pool_for_shutdown.close().await;
state_for_shutdown.db.close().await;

println!("[DATABASE] Pool closed");
});
Expand Down
Loading