gephyr 1.16.18

Gephyr is a headless local AI relay/proxy API handling OpenAI, Claude, and Gemini-compatible APIs
Documentation
use axum::{
    http::{HeaderMap, StatusCode},
    Json,
};
use serde::Serialize;

use super::audit;
use crate::proxy::admin::ErrorResponse;
use crate::proxy::state::AdminState;

pub(crate) type AdminError = (StatusCode, Json<ErrorResponse>);

#[derive(Debug, Clone, Copy, Serialize)]
#[serde(rename_all = "snake_case")]
pub(crate) enum RuntimeApplyPolicy {
    AlwaysHotApplied,
    HotAppliedWhenSafe,
    RequiresRestart,
}

#[derive(Debug, Clone, Serialize)]
pub(crate) struct RuntimeApplyResult {
    pub policy: RuntimeApplyPolicy,
    pub applied: bool,
    pub requires_restart: bool,
}

#[derive(Debug, Clone)]
pub(crate) struct ProxyPatchResult {
    pub actor: super::audit_event::ActorIdentity,
    pub before: crate::proxy::config::ProxyConfig,
    pub after: crate::proxy::config::ProxyConfig,
    pub runtime_apply_policy: RuntimeApplyPolicy,
}

impl ProxyPatchResult {
    pub(crate) fn runtime_apply_result(&self, applied: bool) -> RuntimeApplyResult {
        RuntimeApplyResult {
            policy: self.runtime_apply_policy,
            applied,
            requires_restart: matches!(
                self.runtime_apply_policy,
                RuntimeApplyPolicy::RequiresRestart
            ),
        }
    }
}

pub(crate) fn supported_runtime_apply_policies() -> [RuntimeApplyPolicy; 3] {
    [
        RuntimeApplyPolicy::AlwaysHotApplied,
        RuntimeApplyPolicy::HotAppliedWhenSafe,
        RuntimeApplyPolicy::RequiresRestart,
    ]
}

pub(crate) async fn patch_proxy_config<F>(
    state: &AdminState,
    headers: &HeaderMap,
    runtime_apply_policy: RuntimeApplyPolicy,
    patch_fn: F,
) -> Result<ProxyPatchResult, AdminError>
where
    F: FnOnce(&mut crate::proxy::config::ProxyConfig) -> Result<(), String>,
{
    let actor = audit::resolve_admin_actor(state, headers).await;
    let mut app_config =
        crate::modules::system::config::load_app_config().map_err(internal_error)?;
    let before = app_config.proxy.clone();

    patch_fn(&mut app_config.proxy).map_err(bad_request)?;

    if let Err(errors) = crate::modules::system::validation::validate_app_config(&app_config) {
        let message = errors
            .iter()
            .map(|e| e.to_string())
            .collect::<Vec<_>>()
            .join("\n");
        return Err(bad_request(message));
    }

    crate::modules::system::config::save_app_config(&app_config).map_err(internal_error)?;
    let after = app_config.proxy.clone();

    Ok(ProxyPatchResult {
        actor,
        before,
        after,
        runtime_apply_policy,
    })
}

pub(crate) fn bad_request(error: impl Into<String>) -> AdminError {
    (
        StatusCode::BAD_REQUEST,
        Json(ErrorResponse {
            error: error.into(),
        }),
    )
}

pub(crate) fn internal_error(error: impl Into<String>) -> AdminError {
    (
        StatusCode::INTERNAL_SERVER_ERROR,
        Json(ErrorResponse {
            error: error.into(),
        }),
    )
}