Skip to content
Merged
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
64 changes: 64 additions & 0 deletions crates/frontend/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6474,6 +6474,16 @@ pub struct AdminLlmGatewayProxyTrafficTotalsView {
pub total_bytes: u64,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default)]
#[serde(default)]
pub struct AdminProxyTrafficSnapshotView {
pub refreshed_at_ms: i64,
pub window_start_ms: i64,
pub window_end_ms: i64,
pub retention_days: u64,
pub totals: AdminLlmGatewayProxyTrafficTotalsView,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default)]
#[serde(default)]
pub struct AdminLlmGatewayProxyTrafficPointView {
Expand Down Expand Up @@ -7091,6 +7101,7 @@ pub struct AdminUpstreamProxyConfigView {
pub can_edit_slot_metadata: bool,
pub latest_codex_check: Option<AdminUpstreamProxyEndpointCheckView>,
pub latest_kiro_check: Option<AdminUpstreamProxyEndpointCheckView>,
pub traffic_snapshot: Option<AdminProxyTrafficSnapshotView>,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
Expand All @@ -7112,6 +7123,14 @@ pub struct AdminUpstreamProxyConfigsResponse {
pub generated_at: i64,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
#[serde(default)]
pub struct AdminProxyTrafficRefreshResponse {
pub proxy_config_id: String,
pub traffic_snapshot: AdminProxyTrafficSnapshotView,
pub generated_at: i64,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
#[serde(default)]
pub struct AdminUpstreamProxyConfigScopeView {
Expand Down Expand Up @@ -8073,6 +8092,7 @@ pub async fn create_admin_llm_gateway_proxy_config(
can_edit_slot_metadata: true,
latest_codex_check: None,
latest_kiro_check: None,
traffic_snapshot: None,
})
}

Expand Down Expand Up @@ -8121,6 +8141,7 @@ pub async fn patch_admin_llm_gateway_proxy_config(
can_edit_slot_metadata: true,
latest_codex_check: None,
latest_kiro_check: None,
traffic_snapshot: None,
})
}

Expand Down Expand Up @@ -8174,6 +8195,49 @@ pub async fn delete_admin_llm_gateway_proxy_config(proxy_id: &str) -> Result<(),
}
}

pub async fn refresh_admin_llm_gateway_proxy_traffic(
proxy_id: &str,
) -> Result<AdminProxyTrafficRefreshResponse, String> {
#[cfg(feature = "mock")]
{
Ok(AdminProxyTrafficRefreshResponse {
proxy_config_id: proxy_id.to_string(),
traffic_snapshot: AdminProxyTrafficSnapshotView {
retention_days: 7,
totals: AdminLlmGatewayProxyTrafficTotalsView {
event_count: 12,
request_bytes: 1024 * 1024,
response_bytes: 3 * 1024 * 1024,
total_bytes: 4 * 1024 * 1024,
},
..AdminProxyTrafficSnapshotView::default()
},
generated_at: 0,
})
}

#[cfg(not(feature = "mock"))]
{
let url = format!(
"{}/admin/llm-gateway/proxy-configs/{}/traffic-refresh",
llm_access_admin_base(),
urlencoding::encode(proxy_id)
);
let response = api_post(&url)
.send()
.await
.map_err(|e| format!("Network error: {:?}", e))?;
if !response.ok() {
let text = response.text().await.unwrap_or_default();
return Err(format!("Failed: {text}"));
}
response
.json()
.await
.map_err(|e| format!("Parse error: {:?}", e))
}
}

pub async fn reset_admin_llm_gateway_proxy_config_override(
proxy_id: &str,
) -> Result<AdminUpstreamProxyConfigView, String> {
Expand Down
179 changes: 129 additions & 50 deletions crates/frontend/src/pages/admin_llm_gateway.rs

Large diffs are not rendered by default.

11 changes: 10 additions & 1 deletion crates/llm-access-core/src/store/empty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use super::{
},
proxy::{
default_proxy_binding, default_proxy_bindings, AdminProxyBinding, AdminProxyConfig,
AdminProxyConfigPatch, NewAdminProxyConfig,
AdminProxyConfigPatch, AdminProxyTrafficSnapshot, NewAdminProxyConfig,
},
public::{
AdminAccountContributionRequest, AdminAccountContributionRequestsPage,
Expand Down Expand Up @@ -479,9 +479,18 @@ impl AdminProxyStore for EmptyAdminProxyStore {
can_edit_slot_metadata: true,
latest_codex_check: None,
latest_kiro_check: None,
traffic_snapshot: None,
})
}

async fn record_admin_proxy_traffic_snapshot(
&self,
_proxy_id: &str,
_snapshot: AdminProxyTrafficSnapshot,
) -> anyhow::Result<Option<AdminProxyConfig>> {
Ok(None)
}

async fn patch_admin_proxy_config(
&self,
_proxy_id: &str,
Expand Down
3 changes: 2 additions & 1 deletion crates/llm-access-core/src/store/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ pub use kiro_account::{
};
pub use proxy::{
default_proxy_bindings, AdminProxyBinding, AdminProxyConfig, AdminProxyConfigPatch,
AdminProxyEndpointCheck, AdminProxyEndpointCheckUpdate, NewAdminProxyConfig,
AdminProxyEndpointCheck, AdminProxyEndpointCheckUpdate, AdminProxyTrafficSnapshot,
NewAdminProxyConfig,
};
pub use public::{
AdminAccountContributionRequest, AdminAccountContributionRequestsPage, AdminReviewQueueAction,
Expand Down
63 changes: 62 additions & 1 deletion crates/llm-access-core/src/store/proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

use serde::{Deserialize, Serialize};

use super::{PROVIDER_CODEX, PROVIDER_KIRO};
use super::{usage::ProxyTrafficTotals, PROVIDER_CODEX, PROVIDER_KIRO};

/// Admin-facing projection of one reusable upstream proxy config.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
Expand Down Expand Up @@ -42,6 +42,24 @@ pub struct AdminProxyConfig {
/// Latest Kiro endpoint check observed from this node scope.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub latest_kiro_check: Option<AdminProxyEndpointCheck>,
/// Last manually refreshed traffic snapshot for this proxy.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub traffic_snapshot: Option<AdminProxyTrafficSnapshot>,
}

/// Last manually persisted traffic aggregate for one proxy config.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AdminProxyTrafficSnapshot {
/// Refresh timestamp in Unix milliseconds.
pub refreshed_at_ms: i64,
/// Inclusive window lower bound in Unix milliseconds.
pub window_start_ms: i64,
/// Exclusive window upper bound in Unix milliseconds.
pub window_end_ms: i64,
/// Retention window represented by this snapshot.
pub retention_days: u64,
/// Traffic totals for the represented window.
pub totals: ProxyTrafficTotals,
}

/// Latest connectivity probe result for one proxy/provider endpoint.
Expand Down Expand Up @@ -160,3 +178,46 @@ pub fn default_proxy_binding(provider_type: &str) -> AdminProxyBinding {
error_message: None,
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::store::ProxyTrafficTotals;

#[test]
fn admin_proxy_config_serializes_persisted_traffic_snapshot_when_present() {
let proxy = AdminProxyConfig {
id: "proxy-1".to_string(),
name: "Proxy 1".to_string(),
proxy_url: "http://proxy.local:8080".to_string(),
proxy_username: None,
proxy_password: None,
status: "active".to_string(),
created_at: 100,
updated_at: 200,
scope_node_id: None,
effective_source: "core".to_string(),
has_node_override: false,
can_edit_slot_metadata: true,
latest_codex_check: None,
latest_kiro_check: None,
traffic_snapshot: Some(AdminProxyTrafficSnapshot {
refreshed_at_ms: 1_700_000_000_000,
window_start_ms: 1_699_395_200_000,
window_end_ms: 1_700_000_000_000,
retention_days: 7,
totals: ProxyTrafficTotals {
event_count: 2,
request_bytes: 10,
response_bytes: 30,
total_bytes: 40,
},
}),
};

let value = serde_json::to_value(proxy).expect("serialize proxy config");

assert_eq!(value["traffic_snapshot"]["retention_days"], 7);
assert_eq!(value["traffic_snapshot"]["totals"]["total_bytes"], 40);
}
}
12 changes: 11 additions & 1 deletion crates/llm-access-core/src/store/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ use super::{
},
proxy::{
AdminProxyBinding, AdminProxyConfig, AdminProxyConfigPatch, AdminProxyEndpointCheckUpdate,
NewAdminProxyConfig,
AdminProxyTrafficSnapshot, NewAdminProxyConfig,
},
public::{
AdminAccountContributionRequest, AdminAccountContributionRequestsPage,
Expand Down Expand Up @@ -444,6 +444,16 @@ pub trait AdminProxyStore: Send + Sync {
anyhow::bail!("proxy endpoint checks are not supported by this store")
}

/// Persist the latest manually refreshed traffic snapshot for one proxy
/// config.
async fn record_admin_proxy_traffic_snapshot(
&self,
_proxy_id: &str,
_snapshot: AdminProxyTrafficSnapshot,
) -> anyhow::Result<Option<AdminProxyConfig>> {
anyhow::bail!("proxy traffic snapshots are not supported by this store")
}

/// Delete one proxy config by id and return the removed row.
async fn delete_admin_proxy_config(
&self,
Expand Down
12 changes: 12 additions & 0 deletions crates/llm-access-migrations/migrations/postgres/0001_init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,18 @@ CREATE TABLE IF NOT EXISTS llm_proxy_configs (
CREATE INDEX IF NOT EXISTS idx_llm_proxy_configs_status
ON llm_proxy_configs(status);

CREATE TABLE IF NOT EXISTS llm_proxy_config_traffic_snapshots (
proxy_config_id TEXT PRIMARY KEY REFERENCES llm_proxy_configs(proxy_config_id) ON DELETE CASCADE,
refreshed_at_ms BIGINT NOT NULL CHECK (refreshed_at_ms >= 0),
window_start_ms BIGINT NOT NULL CHECK (window_start_ms >= 0),
window_end_ms BIGINT NOT NULL CHECK (window_end_ms > window_start_ms),
retention_days BIGINT NOT NULL CHECK (retention_days > 0),
event_count BIGINT NOT NULL CHECK (event_count >= 0),
request_bytes BIGINT NOT NULL CHECK (request_bytes >= 0),
response_bytes BIGINT NOT NULL CHECK (response_bytes >= 0),
total_bytes BIGINT NOT NULL CHECK (total_bytes >= 0)
);

CREATE TABLE IF NOT EXISTS llm_proxy_bindings (
provider_type TEXT PRIMARY KEY CHECK (provider_type IN ('codex', 'kiro')),
proxy_config_id TEXT NOT NULL REFERENCES llm_proxy_configs(proxy_config_id),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
CREATE TABLE IF NOT EXISTS llm_proxy_config_traffic_snapshots (
proxy_config_id TEXT PRIMARY KEY REFERENCES llm_proxy_configs(proxy_config_id) ON DELETE CASCADE,
refreshed_at_ms BIGINT NOT NULL CHECK (refreshed_at_ms >= 0),
window_start_ms BIGINT NOT NULL CHECK (window_start_ms >= 0),
window_end_ms BIGINT NOT NULL CHECK (window_end_ms > window_start_ms),
retention_days BIGINT NOT NULL CHECK (retention_days > 0),
event_count BIGINT NOT NULL CHECK (event_count >= 0),
request_bytes BIGINT NOT NULL CHECK (request_bytes >= 0),
response_bytes BIGINT NOT NULL CHECK (response_bytes >= 0),
total_bytes BIGINT NOT NULL CHECK (total_bytes >= 0)
);
23 changes: 23 additions & 0 deletions crates/llm-access-migrations/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,11 @@ const POSTGRES_MIGRATIONS: &[SqlMigration] = &[
name: "kiro_pool_strategy",
sql: include_str!("../migrations/postgres/0026_kiro_pool_strategy.sql"),
},
SqlMigration {
version: 27,
name: "proxy_config_traffic_snapshots",
sql: include_str!("../migrations/postgres/0027_proxy_config_traffic_snapshots.sql"),
},
];

/// Return target DuckDB migrations in execution order.
Expand Down Expand Up @@ -386,4 +391,22 @@ mod tests {
.sql
.contains("codex_fallback_affinity_prefix_bytes"));
}

#[test]
fn postgres_migrations_include_proxy_config_traffic_snapshots() {
let migrations = super::postgres_migrations();
let migration = migrations
.iter()
.find(|migration| migration.name == "proxy_config_traffic_snapshots")
.expect("proxy traffic snapshot migration exists");

assert_eq!(migration.version, 27);
assert!(migration
.sql
.contains("CREATE TABLE IF NOT EXISTS llm_proxy_config_traffic_snapshots"));
assert!(migration
.sql
.contains("REFERENCES llm_proxy_configs(proxy_config_id) ON DELETE CASCADE"));
assert!(migration.sql.contains("total_bytes BIGINT NOT NULL"));
}
}
Loading
Loading