Skip to main content

codex_helper_core/proxy/
mod.rs

1use std::collections::HashMap;
2use std::sync::{Arc, Mutex};
3use std::time::Duration;
4
5use reqwest::Client;
6use tokio::sync::watch;
7
8mod admin;
9mod api_responses;
10mod attempt_execution;
11mod attempt_failures;
12mod attempt_health;
13mod attempt_request;
14mod attempt_response;
15mod attempt_selection;
16mod attempt_target;
17mod attempt_transport;
18mod auth_resolution;
19mod classify;
20mod client_identity;
21mod codex_relay_capabilities;
22mod codex_relay_evidence;
23mod codex_relay_live_smoke;
24mod codex_relay_probe;
25mod codex_relay_target;
26mod concurrency_limits;
27mod control_plane;
28mod control_plane_manifest;
29mod control_plane_routes;
30mod control_plane_service;
31mod entrypoint;
32mod failure_summary;
33mod headers;
34mod healthcheck_api;
35mod http_debug;
36mod models_compat;
37mod passive_health;
38mod persisted_registry_api;
39mod profile_defaults;
40mod provider_execution;
41mod provider_orchestration;
42mod providers_api;
43mod request_body;
44mod request_context;
45mod request_encoding;
46mod request_failures;
47mod request_preparation;
48mod request_routing;
49mod response_finalization;
50mod responses_websocket;
51mod retry;
52mod route_affinity;
53mod route_attempts;
54mod route_executor_runtime;
55mod route_executor_shadow;
56mod route_metadata;
57mod route_provenance;
58mod route_unavailability;
59mod router_setup;
60mod routing_plan;
61mod runtime_admin_api;
62mod runtime_config;
63mod selected_upstream_request;
64mod service_core;
65mod session_overrides;
66mod stations_api;
67mod stream;
68mod target_builder;
69#[cfg(test)]
70mod tests;
71
72use crate::filter::RequestFilter;
73use crate::lb::LbState;
74use crate::state::{ProviderBalanceSnapshot, ProxyState};
75use crate::usage_providers::UsageProviderRefreshSummary;
76
77pub use self::admin::{
78    admin_base_url_from_proxy_base_url, admin_loopback_addr_for_proxy_port,
79    admin_port_for_proxy_port, local_admin_base_url_for_proxy_port, local_proxy_base_url,
80};
81pub use self::api_responses::{ProfilesResponse, ReloadResult, RuntimeStatusResponse};
82pub use self::codex_relay_capabilities::{
83    CodexRelayCapabilitiesObserved, CodexRelayCapabilitiesRequest, CodexRelayCapabilitiesResponse,
84    CodexRelayCapabilityMismatch,
85};
86pub use self::codex_relay_evidence::{
87    CodexRelayEvidenceEntry, CodexRelayEvidenceFilters, CodexRelayEvidenceKind,
88    codex_relay_evidence_path, read_recent_codex_relay_evidence,
89};
90pub use self::codex_relay_live_smoke::{
91    CODEX_RELAY_LIVE_SMOKE_ACK, CodexRelayLiveSmokeCase, CodexRelayLiveSmokeConfidence,
92    CodexRelayLiveSmokeOutcome, CodexRelayLiveSmokeRequest, CodexRelayLiveSmokeResponse,
93    CodexRelayLiveSmokeResult, CodexRelayLiveSmokeSideEffect,
94};
95pub use self::codex_relay_probe::{
96    CodexRelayProbeClient, CodexRelayProbeConfidence, CodexRelayProbeKind, CodexRelayProbeResult,
97    CodexRelayProbeSideEffect, CodexRelayProbeSpec, CodexRelayProbeSupport,
98    classify_codex_relay_probe_response,
99};
100use self::concurrency_limits::ConcurrencyLimiter;
101pub use self::entrypoint::handle_proxy;
102pub use self::persisted_registry_api::PersistedRoutingUpsertRequest;
103pub use self::router_setup::{
104    admin_listener_router, proxy_only_router, proxy_only_router_with_admin_base_url, router,
105};
106use self::runtime_config::RuntimeConfig;
107
108pub const ADMIN_TOKEN_ENV_VAR: &str = "CODEX_HELPER_ADMIN_TOKEN";
109pub const ADMIN_TOKEN_HEADER: &str = "x-codex-helper-admin-token";
110pub const CLIENT_NAME_HEADER: &str = "x-codex-helper-client-name";
111pub const ADMIN_PORT_OFFSET: u16 = 1000;
112
113#[cfg(test)]
114const AUTH_FILE_CACHE_MIN_CHECK_INTERVAL: Duration = Duration::from_millis(20);
115#[cfg(not(test))]
116const AUTH_FILE_CACHE_MIN_CHECK_INTERVAL: Duration = Duration::from_millis(800);
117
118#[cfg(test)]
119fn codex_auth_json_value(key: &str) -> Option<String> {
120    auth_resolution::codex_auth_json_value(key)
121}
122
123#[cfg(test)]
124fn claude_settings_env_value(key: &str) -> Option<String> {
125    auth_resolution::claude_settings_env_value(key)
126}
127
128/// Generic proxy service; currently used by both Codex and Claude.
129#[derive(Clone)]
130pub struct ProxyService {
131    pub client: Client,
132    config: Arc<RuntimeConfig>,
133    pub service_name: &'static str,
134    lb_states: Arc<Mutex<HashMap<String, LbState>>>,
135    concurrency_limiter: Arc<ConcurrencyLimiter>,
136    filter: RequestFilter,
137    state: Arc<ProxyState>,
138    shutdown_tx: Option<watch::Sender<bool>>,
139}
140
141#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
142pub struct ProviderBalanceRefreshResponse {
143    pub service_name: String,
144    pub refresh: UsageProviderRefreshSummary,
145    pub provider_balances: HashMap<String, Vec<ProviderBalanceSnapshot>>,
146}
147
148#[derive(Debug, Clone)]
149pub struct ProxyControlError {
150    status: axum::http::StatusCode,
151    message: String,
152}
153
154impl ProxyControlError {
155    pub fn new(status: axum::http::StatusCode, message: impl Into<String>) -> Self {
156        Self {
157            status,
158            message: message.into(),
159        }
160    }
161
162    pub fn status(&self) -> axum::http::StatusCode {
163        self.status
164    }
165
166    pub fn message(&self) -> &str {
167        self.message.as_str()
168    }
169
170    pub fn into_http_error(self) -> (axum::http::StatusCode, String) {
171        (self.status, self.message)
172    }
173}
174
175impl std::fmt::Display for ProxyControlError {
176    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
177        write!(f, "status={}, {}", self.status, self.message)
178    }
179}
180
181impl std::error::Error for ProxyControlError {}
182
183impl From<(axum::http::StatusCode, String)> for ProxyControlError {
184    fn from((status, message): (axum::http::StatusCode, String)) -> Self {
185        Self::new(status, message)
186    }
187}