mirror of
https://github.com/whit3rabbit/anyllm-proxy.git
synced 2026-09-22 00:00:50 +00:00
style: apply cargo fmt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
9ab1166e81
commit
2ff669a10b
@@ -2,17 +2,17 @@ pub mod ai21;
|
||||
pub mod ai_ml_api;
|
||||
pub mod aleph_alpha;
|
||||
pub mod anthropic;
|
||||
pub mod assemblyai;
|
||||
pub mod baidu;
|
||||
pub mod blackboxai;
|
||||
pub mod brave;
|
||||
pub mod cartesia;
|
||||
pub mod anyscale;
|
||||
pub mod assemblyai;
|
||||
pub mod azure;
|
||||
pub mod azure_ai;
|
||||
pub mod baidu;
|
||||
pub mod baseten;
|
||||
pub mod bedrock;
|
||||
pub mod blackboxai;
|
||||
pub mod brave;
|
||||
pub mod bytez;
|
||||
pub mod cartesia;
|
||||
pub mod cerebras;
|
||||
pub mod chutes;
|
||||
pub mod clarifai;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use crate::model::{ModelCapabilities, ModelDef, ModelStatus};
|
||||
use crate::provider::{AuthKind, ProviderCapabilities, ProviderDef, ProviderProtocol, ProviderStatus};
|
||||
use crate::provider::{
|
||||
AuthKind, ProviderCapabilities, ProviderDef, ProviderProtocol, ProviderStatus,
|
||||
};
|
||||
|
||||
/// Zhipu AI (Z.AI) — GLM model series via OpenAI-compatible endpoint.
|
||||
pub const PROVIDER: ProviderDef = ProviderDef {
|
||||
|
||||
@@ -650,7 +650,10 @@ pub fn update_route(conn: &Connection, id: &str, patch: &RoutePatch) -> rusqlite
|
||||
}
|
||||
|
||||
if set_clauses.is_empty() {
|
||||
let count: i64 = conn.query_row("SELECT COUNT(*) FROM routes WHERE id = ?1", [id], |r| r.get(0))?;
|
||||
let count: i64 =
|
||||
conn.query_row("SELECT COUNT(*) FROM routes WHERE id = ?1", [id], |r| {
|
||||
r.get(0)
|
||||
})?;
|
||||
return Ok(count > 0);
|
||||
}
|
||||
|
||||
@@ -659,7 +662,8 @@ pub fn update_route(conn: &Connection, id: &str, patch: &RoutePatch) -> rusqlite
|
||||
param_values.push(Box::new(id.to_string()));
|
||||
|
||||
let sql = format!("UPDATE routes SET {} WHERE id = ?", set_clauses.join(", "));
|
||||
let params: Vec<&dyn rusqlite::types::ToSql> = param_values.iter().map(|p| p.as_ref()).collect();
|
||||
let params: Vec<&dyn rusqlite::types::ToSql> =
|
||||
param_values.iter().map(|p| p.as_ref()).collect();
|
||||
let updated = conn.execute(&sql, params.as_slice())?;
|
||||
Ok(updated > 0)
|
||||
}
|
||||
@@ -671,14 +675,18 @@ pub fn delete_route(conn: &Connection, id: &str) -> rusqlite::Result<bool> {
|
||||
|
||||
// ── Route Providers ──────────────────────────────────────────────────────────
|
||||
|
||||
pub fn list_route_providers(conn: &Connection, route_id: &str) -> rusqlite::Result<Vec<RouteProviderRow>> {
|
||||
pub fn list_route_providers(
|
||||
conn: &Connection,
|
||||
route_id: &str,
|
||||
) -> rusqlite::Result<Vec<RouteProviderRow>> {
|
||||
let mut stmt = conn.prepare(
|
||||
"SELECT id, route_id, backend_id, models, priority, enabled, created_at
|
||||
FROM route_providers WHERE route_id = ?1 ORDER BY priority ASC",
|
||||
)?;
|
||||
let rows = stmt.query_map([route_id], |row| {
|
||||
let models_json: String = row.get(3)?;
|
||||
let models: Vec<String> = serde_json::from_str(&models_json).unwrap_or_else(|_| vec!["*".into()]);
|
||||
let models: Vec<String> =
|
||||
serde_json::from_str(&models_json).unwrap_or_else(|_| vec!["*".into()]);
|
||||
Ok(RouteProviderRow {
|
||||
id: row.get(0)?,
|
||||
route_id: row.get(1)?,
|
||||
@@ -702,18 +710,27 @@ pub fn count_route_providers(conn: &Connection, route_id: &str) -> rusqlite::Res
|
||||
}
|
||||
|
||||
pub fn managed_backend_exists(conn: &Connection, id: &str) -> rusqlite::Result<bool> {
|
||||
let n: i64 = conn.query_row(
|
||||
"SELECT 1 FROM managed_backends WHERE id = ?1",
|
||||
[id],
|
||||
|row| row.get(0),
|
||||
).or_else(|e| match e {
|
||||
rusqlite::Error::QueryReturnedNoRows => Ok(0),
|
||||
other => Err(other),
|
||||
})?;
|
||||
let n: i64 = conn
|
||||
.query_row(
|
||||
"SELECT 1 FROM managed_backends WHERE id = ?1",
|
||||
[id],
|
||||
|row| row.get(0),
|
||||
)
|
||||
.or_else(|e| match e {
|
||||
rusqlite::Error::QueryReturnedNoRows => Ok(0),
|
||||
other => Err(other),
|
||||
})?;
|
||||
Ok(n == 1)
|
||||
}
|
||||
|
||||
pub fn add_route_provider(conn: &Connection, route_id: &str, backend_id: &str, models: &[String], priority: i32, enabled: bool) -> rusqlite::Result<()> {
|
||||
pub fn add_route_provider(
|
||||
conn: &Connection,
|
||||
route_id: &str,
|
||||
backend_id: &str,
|
||||
models: &[String],
|
||||
priority: i32,
|
||||
enabled: bool,
|
||||
) -> rusqlite::Result<()> {
|
||||
let models_json = serde_json::to_string(models).unwrap_or_else(|_| "[\"*\"]".into());
|
||||
conn.execute(
|
||||
"INSERT INTO route_providers (id, route_id, backend_id, models, priority, enabled)
|
||||
@@ -723,13 +740,21 @@ pub fn add_route_provider(conn: &Connection, route_id: &str, backend_id: &str, m
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_route_provider(conn: &Connection, id: &str, models: Option<&[String]>, priority: Option<i32>, enabled: Option<bool>) -> rusqlite::Result<bool> {
|
||||
pub fn update_route_provider(
|
||||
conn: &Connection,
|
||||
id: &str,
|
||||
models: Option<&[String]>,
|
||||
priority: Option<i32>,
|
||||
enabled: Option<bool>,
|
||||
) -> rusqlite::Result<bool> {
|
||||
let mut set_clauses: Vec<String> = Vec::new();
|
||||
let mut param_values: Vec<Box<dyn rusqlite::types::ToSql>> = Vec::new();
|
||||
|
||||
if let Some(m) = models {
|
||||
set_clauses.push("models = ?".into());
|
||||
param_values.push(Box::new(serde_json::to_string(m).unwrap_or_else(|_| "[\"*\"]".into())));
|
||||
param_values.push(Box::new(
|
||||
serde_json::to_string(m).unwrap_or_else(|_| "[\"*\"]".into()),
|
||||
));
|
||||
}
|
||||
if let Some(p) = priority {
|
||||
set_clauses.push("priority = ?".into());
|
||||
@@ -745,8 +770,12 @@ pub fn update_route_provider(conn: &Connection, id: &str, models: Option<&[Strin
|
||||
}
|
||||
|
||||
param_values.push(Box::new(id.to_string()));
|
||||
let sql = format!("UPDATE route_providers SET {} WHERE id = ?", set_clauses.join(", "));
|
||||
let params: Vec<&dyn rusqlite::types::ToSql> = param_values.iter().map(|p| p.as_ref()).collect();
|
||||
let sql = format!(
|
||||
"UPDATE route_providers SET {} WHERE id = ?",
|
||||
set_clauses.join(", ")
|
||||
);
|
||||
let params: Vec<&dyn rusqlite::types::ToSql> =
|
||||
param_values.iter().map(|p| p.as_ref()).collect();
|
||||
let updated = conn.execute(&sql, params.as_slice())?;
|
||||
Ok(updated > 0)
|
||||
}
|
||||
@@ -2884,15 +2913,7 @@ mod tests {
|
||||
let mut b = test_row(&format!("b{i}"));
|
||||
b.id = format!("backend-{i}");
|
||||
insert_managed_backend(conn, &b).unwrap();
|
||||
add_route_provider(
|
||||
conn,
|
||||
&route.id,
|
||||
&b.id,
|
||||
&["*".to_string()],
|
||||
i as i32,
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
add_route_provider(conn, &route.id, &b.id, &["*".to_string()], i as i32, true).unwrap();
|
||||
}
|
||||
|
||||
let ids: Vec<String> = list_route_providers(conn, &route.id)
|
||||
@@ -2937,7 +2958,10 @@ mod tests {
|
||||
// Priorities must be untouched.
|
||||
let rows = list_route_providers(&conn, &route_id).unwrap();
|
||||
for (i, row) in rows.iter().enumerate() {
|
||||
assert_eq!(row.priority, i as i32, "priorities must be unchanged after Mismatch");
|
||||
assert_eq!(
|
||||
row.priority, i as i32,
|
||||
"priorities must be unchanged after Mismatch"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use crate::admin::state::SharedState;
|
||||
use anyllm_providers::{
|
||||
all_providers, get_provider, list_models,
|
||||
model::ModelStatus,
|
||||
provider::{AuthKind, ProviderProtocol, ProviderStatus},
|
||||
};
|
||||
use axum::{extract::Path, extract::State, http::StatusCode, response::IntoResponse, Json};
|
||||
use crate::admin::state::SharedState;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
static REFRESH_CLIENT: LazyLock<reqwest::Client> = LazyLock::new(|| {
|
||||
@@ -152,13 +152,11 @@ pub(super) async fn list_provider_models(
|
||||
.collect();
|
||||
|
||||
// Enrich with cached model IDs from live refreshes (best-effort).
|
||||
let cached_models: Vec<String> =
|
||||
if let Ok(db_guard) = shared.db.lock() {
|
||||
crate::admin::db::list_cached_provider_models(&db_guard, &provider_id)
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
let cached_models: Vec<String> = if let Ok(db_guard) = shared.db.lock() {
|
||||
crate::admin::db::list_cached_provider_models(&db_guard, &provider_id).unwrap_or_default()
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
let has_models = !models.is_empty();
|
||||
Json(serde_json::json!({
|
||||
@@ -211,7 +209,10 @@ pub(super) async fn refresh_provider_models(
|
||||
// Resolve the API key from the first env var that is set.
|
||||
let api_key = provider.env_vars.iter().find_map(|v| std::env::var(v).ok());
|
||||
|
||||
let url = format!("{}/v1/models", provider.default_base_url.trim_end_matches('/'));
|
||||
let url = format!(
|
||||
"{}/v1/models",
|
||||
provider.default_base_url.trim_end_matches('/')
|
||||
);
|
||||
let mut req = REFRESH_CLIENT.get(&url);
|
||||
if let Some(ref key) = api_key {
|
||||
req = req.header("Authorization", format!("Bearer {key}"));
|
||||
|
||||
@@ -491,7 +491,8 @@ pub fn admin_router(shared: SharedState, token: Arc<zeroize::Zeroizing<String>>)
|
||||
)
|
||||
.route(
|
||||
"/admin/api/routes/{id}/providers",
|
||||
get(routes_api::list_route_providers_handler).post(routes_api::add_route_provider_handler),
|
||||
get(routes_api::list_route_providers_handler)
|
||||
.post(routes_api::add_route_provider_handler),
|
||||
)
|
||||
.route(
|
||||
"/admin/api/routes/{id}/providers/reorder",
|
||||
@@ -499,7 +500,8 @@ pub fn admin_router(shared: SharedState, token: Arc<zeroize::Zeroizing<String>>)
|
||||
)
|
||||
.route(
|
||||
"/admin/api/routes/{id}/providers/{provider_id}",
|
||||
put(routes_api::update_route_provider_handler).delete(routes_api::remove_route_provider_handler),
|
||||
put(routes_api::update_route_provider_handler)
|
||||
.delete(routes_api::remove_route_provider_handler),
|
||||
)
|
||||
.with_state(shared.clone())
|
||||
// Innermost: CSRF check runs after auth succeeds.
|
||||
@@ -721,7 +723,12 @@ mod tests {
|
||||
assert!(check_admin_rate_limit_with_rpm(ip, 3, &bucket, &TEST_PRUNE));
|
||||
assert!(check_admin_rate_limit_with_rpm(ip, 3, &bucket, &TEST_PRUNE));
|
||||
// 4th request in the same window should be rejected.
|
||||
assert!(!check_admin_rate_limit_with_rpm(ip, 3, &bucket, &TEST_PRUNE));
|
||||
assert!(!check_admin_rate_limit_with_rpm(
|
||||
ip,
|
||||
3,
|
||||
&bucket,
|
||||
&TEST_PRUNE
|
||||
));
|
||||
|
||||
bucket.remove(&ip);
|
||||
}
|
||||
|
||||
@@ -146,16 +146,26 @@ fn build_provider_responses(
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn audit(shared: &SharedState, source_ip: Option<String>, action: &str, target_type: &str, target_id: String, detail: Option<String>) {
|
||||
super::emit_audit(shared, crate::admin::db::AuditEntry {
|
||||
id: None,
|
||||
timestamp: None,
|
||||
action: action.into(),
|
||||
target_type: target_type.into(),
|
||||
target_id: Some(target_id),
|
||||
detail,
|
||||
source_ip,
|
||||
});
|
||||
fn audit(
|
||||
shared: &SharedState,
|
||||
source_ip: Option<String>,
|
||||
action: &str,
|
||||
target_type: &str,
|
||||
target_id: String,
|
||||
detail: Option<String>,
|
||||
) {
|
||||
super::emit_audit(
|
||||
shared,
|
||||
crate::admin::db::AuditEntry {
|
||||
id: None,
|
||||
timestamp: None,
|
||||
action: action.into(),
|
||||
target_type: target_type.into(),
|
||||
target_id: Some(target_id),
|
||||
detail,
|
||||
source_ip,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// ── Route CRUD ────────────────────────────────────────────────────────────────
|
||||
@@ -208,8 +218,19 @@ pub(super) async fn create_route(
|
||||
|
||||
match result {
|
||||
Some(Ok(())) => {
|
||||
audit(&shared, Some(addr.ip().to_string()), "route_created", "route", row.name.clone(), None);
|
||||
(StatusCode::CREATED, Json(serde_json::to_value(route_to_response(&row, 0)).unwrap())).into_response()
|
||||
audit(
|
||||
&shared,
|
||||
Some(addr.ip().to_string()),
|
||||
"route_created",
|
||||
"route",
|
||||
row.name.clone(),
|
||||
None,
|
||||
);
|
||||
(
|
||||
StatusCode::CREATED,
|
||||
Json(serde_json::to_value(route_to_response(&row, 0)).unwrap()),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
Some(Err(e)) => err_json(db_error_status(&e), e.to_string()),
|
||||
None => err_json(StatusCode::INTERNAL_SERVER_ERROR, "Failed to create route"),
|
||||
@@ -236,10 +257,20 @@ pub(super) async fn update_route(
|
||||
|
||||
match result {
|
||||
Some(Ok((true, Some(r), count))) => {
|
||||
audit(&shared, Some(addr.ip().to_string()), "route_updated", "route", id, None);
|
||||
audit(
|
||||
&shared,
|
||||
Some(addr.ip().to_string()),
|
||||
"route_updated",
|
||||
"route",
|
||||
id,
|
||||
None,
|
||||
);
|
||||
ok_json(route_to_response(&r, count))
|
||||
}
|
||||
Some(Ok((true, None, _))) => err_json(StatusCode::INTERNAL_SERVER_ERROR, "route not found after update"),
|
||||
Some(Ok((true, None, _))) => err_json(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"route not found after update",
|
||||
),
|
||||
Some(Ok((false, _, _))) => err_json(StatusCode::NOT_FOUND, "route not found"),
|
||||
Some(Err(e)) => err_json(StatusCode::INTERNAL_SERVER_ERROR, e.to_string()),
|
||||
None => err_json(StatusCode::INTERNAL_SERVER_ERROR, "Failed to update route"),
|
||||
@@ -259,7 +290,14 @@ pub(super) async fn delete_route(
|
||||
|
||||
match result {
|
||||
Some(Ok(true)) => {
|
||||
audit(&shared, Some(addr.ip().to_string()), "route_deleted", "route", id, None);
|
||||
audit(
|
||||
&shared,
|
||||
Some(addr.ip().to_string()),
|
||||
"route_deleted",
|
||||
"route",
|
||||
id,
|
||||
None,
|
||||
);
|
||||
ok_json(serde_json::json!({ "ok": true }))
|
||||
}
|
||||
Some(Ok(false)) => err_json(StatusCode::NOT_FOUND, "route not found"),
|
||||
@@ -275,7 +313,11 @@ pub(super) async fn list_route_providers_handler(
|
||||
Path(route_id): Path<String>,
|
||||
) -> axum::response::Response {
|
||||
let result = crate::admin::state::with_db(&shared.db, move |conn| {
|
||||
if crate::admin::db::get_route(conn, &route_id).ok().flatten().is_none() {
|
||||
if crate::admin::db::get_route(conn, &route_id)
|
||||
.ok()
|
||||
.flatten()
|
||||
.is_none()
|
||||
{
|
||||
return Ok::<_, rusqlite::Error>(None);
|
||||
}
|
||||
let providers = crate::admin::db::list_route_providers(conn, &route_id)?;
|
||||
@@ -287,7 +329,10 @@ pub(super) async fn list_route_providers_handler(
|
||||
match result {
|
||||
Some(Ok(Some(resp))) => ok_json(serde_json::json!({ "providers": resp })),
|
||||
Some(Ok(None)) => err_json(StatusCode::NOT_FOUND, "route not found"),
|
||||
_ => err_json(StatusCode::INTERNAL_SERVER_ERROR, "Failed to list route providers"),
|
||||
_ => err_json(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"Failed to list route providers",
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,25 +351,53 @@ pub(super) async fn add_route_provider_handler(
|
||||
let backend_id_for_db = backend_id.clone();
|
||||
let backend_id_check = backend_id.clone();
|
||||
let result = crate::admin::state::with_db(&shared.db, move |conn| {
|
||||
if crate::admin::db::get_route(conn, &route_id_clone).ok().flatten().is_none() {
|
||||
if crate::admin::db::get_route(conn, &route_id_clone)
|
||||
.ok()
|
||||
.flatten()
|
||||
.is_none()
|
||||
{
|
||||
return Ok::<_, rusqlite::Error>(Err::<(), String>("route not found".into()));
|
||||
}
|
||||
if !crate::admin::db::managed_backend_exists(conn, &backend_id_check)? {
|
||||
return Ok(Err::<(), String>("backend not found".into()));
|
||||
}
|
||||
crate::admin::db::add_route_provider(conn, &route_id_clone, &backend_id_for_db, &models, priority, enabled)?;
|
||||
crate::admin::db::add_route_provider(
|
||||
conn,
|
||||
&route_id_clone,
|
||||
&backend_id_for_db,
|
||||
&models,
|
||||
priority,
|
||||
enabled,
|
||||
)?;
|
||||
Ok(Ok::<(), String>(()))
|
||||
})
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Some(Ok(Ok(()))) => {
|
||||
audit(&shared, Some(addr.ip().to_string()), "route_provider_added", "route_provider", route_id, Some(format!("backend_id={}", backend_id)));
|
||||
audit(
|
||||
&shared,
|
||||
Some(addr.ip().to_string()),
|
||||
"route_provider_added",
|
||||
"route_provider",
|
||||
route_id,
|
||||
Some(format!("backend_id={}", backend_id)),
|
||||
);
|
||||
(StatusCode::CREATED, Json(serde_json::json!({ "ok": true }))).into_response()
|
||||
}
|
||||
Some(Ok(Err(msg))) => err_json(if msg == "route not found" { StatusCode::NOT_FOUND } else { StatusCode::BAD_REQUEST }, msg),
|
||||
Some(Ok(Err(msg))) => err_json(
|
||||
if msg == "route not found" {
|
||||
StatusCode::NOT_FOUND
|
||||
} else {
|
||||
StatusCode::BAD_REQUEST
|
||||
},
|
||||
msg,
|
||||
),
|
||||
Some(Err(e)) => err_json(db_error_status(&e), e.to_string()),
|
||||
None => err_json(StatusCode::INTERNAL_SERVER_ERROR, "Failed to add route provider"),
|
||||
None => err_json(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"Failed to add route provider",
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,12 +421,22 @@ pub(super) async fn update_route_provider_handler(
|
||||
|
||||
match result {
|
||||
Some(Ok(true)) => {
|
||||
audit(&shared, Some(addr.ip().to_string()), "route_provider_updated", "route_provider", provider_id, Some(format!("route_id={}", route_id)));
|
||||
audit(
|
||||
&shared,
|
||||
Some(addr.ip().to_string()),
|
||||
"route_provider_updated",
|
||||
"route_provider",
|
||||
provider_id,
|
||||
Some(format!("route_id={}", route_id)),
|
||||
);
|
||||
ok_json(serde_json::json!({ "ok": true }))
|
||||
}
|
||||
Some(Ok(false)) => err_json(StatusCode::NOT_FOUND, "route provider not found"),
|
||||
Some(Err(e)) => err_json(StatusCode::INTERNAL_SERVER_ERROR, e.to_string()),
|
||||
None => err_json(StatusCode::INTERNAL_SERVER_ERROR, "Failed to update route provider"),
|
||||
None => err_json(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"Failed to update route provider",
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -422,11 +505,21 @@ pub(super) async fn remove_route_provider_handler(
|
||||
|
||||
match result {
|
||||
Some(Ok(true)) => {
|
||||
audit(&shared, Some(addr.ip().to_string()), "route_provider_removed", "route_provider", provider_id, None);
|
||||
audit(
|
||||
&shared,
|
||||
Some(addr.ip().to_string()),
|
||||
"route_provider_removed",
|
||||
"route_provider",
|
||||
provider_id,
|
||||
None,
|
||||
);
|
||||
ok_json(serde_json::json!({ "ok": true }))
|
||||
}
|
||||
Some(Ok(false)) => err_json(StatusCode::NOT_FOUND, "route provider not found"),
|
||||
Some(Err(e)) => err_json(StatusCode::INTERNAL_SERVER_ERROR, e.to_string()),
|
||||
None => err_json(StatusCode::INTERNAL_SERVER_ERROR, "Failed to remove route provider"),
|
||||
None => err_json(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"Failed to remove route provider",
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,9 +164,9 @@ impl Config {
|
||||
};
|
||||
|
||||
let listen_port: u16 = match std::env::var("LISTEN_PORT") {
|
||||
Ok(val) => val.parse::<u16>().unwrap_or_else(|_| {
|
||||
panic!("LISTEN_PORT must be a number in 1-65535, got '{val}'")
|
||||
}),
|
||||
Ok(val) => val
|
||||
.parse::<u16>()
|
||||
.unwrap_or_else(|_| panic!("LISTEN_PORT must be a number in 1-65535, got '{val}'")),
|
||||
Err(_) => 3000,
|
||||
};
|
||||
if listen_port == 0 {
|
||||
|
||||
+75
-73
@@ -220,7 +220,10 @@ fn providers_subcommand(args: Vec<String>, _data_dir: &std::path::Path) -> i32 {
|
||||
.collect();
|
||||
println!("{}", serde_json::to_string_pretty(&out).unwrap());
|
||||
} else {
|
||||
println!("{:<20} {:<15} {:<12} {:>8}", "ID", "STATUS", "PROTOCOL", "MODELS");
|
||||
println!(
|
||||
"{:<20} {:<15} {:<12} {:>8}",
|
||||
"ID", "STATUS", "PROTOCOL", "MODELS"
|
||||
);
|
||||
println!("{}", "-".repeat(60));
|
||||
for p in &providers {
|
||||
println!(
|
||||
@@ -239,25 +242,25 @@ fn providers_subcommand(args: Vec<String>, _data_dir: &std::path::Path) -> i32 {
|
||||
let refresh_all = target == Some("--all");
|
||||
|
||||
// Collect providers to refresh before entering async context.
|
||||
let providers_to_refresh: Vec<anyllm_providers::provider::ProviderDef> =
|
||||
if refresh_all {
|
||||
anyllm_providers::all_providers()
|
||||
.filter(|p| p.capabilities.chat_completions)
|
||||
.filter(|p| p.env_vars.iter().any(|v| std::env::var(v).is_ok()))
|
||||
.cloned()
|
||||
.collect()
|
||||
} else if let Some(id) = target {
|
||||
match anyllm_providers::get_provider(id) {
|
||||
Some(p) => vec![p.clone()],
|
||||
None => {
|
||||
eprintln!("error: unknown provider '{id}'");
|
||||
return 1;
|
||||
}
|
||||
let providers_to_refresh: Vec<anyllm_providers::provider::ProviderDef> = if refresh_all
|
||||
{
|
||||
anyllm_providers::all_providers()
|
||||
.filter(|p| p.capabilities.chat_completions)
|
||||
.filter(|p| p.env_vars.iter().any(|v| std::env::var(v).is_ok()))
|
||||
.cloned()
|
||||
.collect()
|
||||
} else if let Some(id) = target {
|
||||
match anyllm_providers::get_provider(id) {
|
||||
Some(p) => vec![p.clone()],
|
||||
None => {
|
||||
eprintln!("error: unknown provider '{id}'");
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
eprintln!("usage: anyllm-proxy providers refresh <provider-id>|--all");
|
||||
return 1;
|
||||
};
|
||||
}
|
||||
} else {
|
||||
eprintln!("usage: anyllm-proxy providers refresh <provider-id>|--all");
|
||||
return 1;
|
||||
};
|
||||
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
@@ -269,7 +272,10 @@ fn providers_subcommand(args: Vec<String>, _data_dir: &std::path::Path) -> i32 {
|
||||
let mut exit = 0;
|
||||
for provider in &providers_to_refresh {
|
||||
let api_key = provider.env_vars.iter().find_map(|v| std::env::var(v).ok());
|
||||
let url = format!("{}/v1/models", provider.default_base_url.trim_end_matches('/'));
|
||||
let url = format!(
|
||||
"{}/v1/models",
|
||||
provider.default_base_url.trim_end_matches('/')
|
||||
);
|
||||
let result = rt.block_on(async {
|
||||
let mut req = client.get(&url);
|
||||
if let Some(ref key) = api_key {
|
||||
@@ -296,9 +302,7 @@ fn providers_subcommand(args: Vec<String>, _data_dir: &std::path::Path) -> i32 {
|
||||
.get("data")
|
||||
.and_then(|d| d.as_array())
|
||||
.map(|arr| {
|
||||
arr.iter()
|
||||
.filter_map(|m| m.get("id")?.as_str())
|
||||
.collect()
|
||||
arr.iter().filter_map(|m| m.get("id")?.as_str()).collect()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
println!("{}: {} models", provider.id, models.len());
|
||||
@@ -602,9 +606,9 @@ async fn async_main(args: Vec<String>, data_dir: PathBuf) {
|
||||
let mut admin_startup_info: Option<(String, String)> = None;
|
||||
let admin_parts = if enable_admin {
|
||||
let admin_port: u16 = match std::env::var("ADMIN_PORT") {
|
||||
Ok(val) => val.parse::<u16>().unwrap_or_else(|_| {
|
||||
panic!("ADMIN_PORT must be a number in 1-65535, got '{val}'")
|
||||
}),
|
||||
Ok(val) => val
|
||||
.parse::<u16>()
|
||||
.unwrap_or_else(|_| panic!("ADMIN_PORT must be a number in 1-65535, got '{val}'")),
|
||||
Err(_) => 3001,
|
||||
};
|
||||
if admin_port == 0 {
|
||||
@@ -878,8 +882,7 @@ async fn async_main(args: Vec<String>, data_dir: PathBuf) {
|
||||
if !provider.capabilities.chat_completions {
|
||||
continue;
|
||||
}
|
||||
let api_key =
|
||||
provider.env_vars.iter().find_map(|v| std::env::var(v).ok());
|
||||
let api_key = provider.env_vars.iter().find_map(|v| std::env::var(v).ok());
|
||||
if api_key.is_none() {
|
||||
continue; // skip unconfigured providers
|
||||
}
|
||||
@@ -903,52 +906,47 @@ async fn async_main(args: Vec<String>, data_dir: PathBuf) {
|
||||
status = %resp.status(),
|
||||
"provider auto-refresh upstream error"
|
||||
),
|
||||
Ok(resp) => {
|
||||
match resp.json::<serde_json::Value>().await {
|
||||
Err(e) => tracing::warn!(
|
||||
provider = %provider.id,
|
||||
error = %e,
|
||||
"provider auto-refresh: invalid JSON response"
|
||||
),
|
||||
Ok(json) => {
|
||||
let model_ids: Vec<String> = json
|
||||
.get("data")
|
||||
.and_then(|d| d.as_array())
|
||||
.map(|arr| {
|
||||
arr.iter()
|
||||
.filter_map(|m| {
|
||||
m.get("id")?.as_str().map(String::from)
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let count = model_ids.len();
|
||||
let db = shared_for_refresh.db.clone();
|
||||
let pid = provider.id.to_string();
|
||||
let _ = tokio::task::spawn_blocking(move || {
|
||||
let mut conn =
|
||||
db.lock().unwrap_or_else(|e| e.into_inner());
|
||||
if let Err(e) = admin::db::upsert_provider_models_cache(
|
||||
&mut conn,
|
||||
&pid,
|
||||
&model_ids,
|
||||
) {
|
||||
tracing::warn!(
|
||||
provider = %pid,
|
||||
error = %e,
|
||||
"failed to save auto-refresh results"
|
||||
);
|
||||
}
|
||||
Ok(resp) => match resp.json::<serde_json::Value>().await {
|
||||
Err(e) => tracing::warn!(
|
||||
provider = %provider.id,
|
||||
error = %e,
|
||||
"provider auto-refresh: invalid JSON response"
|
||||
),
|
||||
Ok(json) => {
|
||||
let model_ids: Vec<String> = json
|
||||
.get("data")
|
||||
.and_then(|d| d.as_array())
|
||||
.map(|arr| {
|
||||
arr.iter()
|
||||
.filter_map(|m| {
|
||||
m.get("id")?.as_str().map(String::from)
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.await;
|
||||
tracing::info!(
|
||||
provider = %provider.id,
|
||||
count = count,
|
||||
"auto-refreshed provider model cache"
|
||||
);
|
||||
}
|
||||
.unwrap_or_default();
|
||||
let count = model_ids.len();
|
||||
let db = shared_for_refresh.db.clone();
|
||||
let pid = provider.id.to_string();
|
||||
let _ = tokio::task::spawn_blocking(move || {
|
||||
let mut conn = db.lock().unwrap_or_else(|e| e.into_inner());
|
||||
if let Err(e) = admin::db::upsert_provider_models_cache(
|
||||
&mut conn, &pid, &model_ids,
|
||||
) {
|
||||
tracing::warn!(
|
||||
provider = %pid,
|
||||
error = %e,
|
||||
"failed to save auto-refresh results"
|
||||
);
|
||||
}
|
||||
})
|
||||
.await;
|
||||
tracing::info!(
|
||||
provider = %provider.id,
|
||||
count = count,
|
||||
"auto-refreshed provider model cache"
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
tokio::time::sleep(interval).await;
|
||||
@@ -1004,7 +1002,11 @@ async fn async_main(args: Vec<String>, data_dir: PathBuf) {
|
||||
let admin_token = Arc::new(zeroize::Zeroizing::new(admin_token));
|
||||
|
||||
// Capture for the startup banner printed after both servers are bound.
|
||||
let admin_display_host = if admin_bind == "0.0.0.0" { "localhost" } else { &admin_bind };
|
||||
let admin_display_host = if admin_bind == "0.0.0.0" {
|
||||
"localhost"
|
||||
} else {
|
||||
&admin_bind
|
||||
};
|
||||
let admin_ui_url = format!("http://{}:{}/admin/", admin_display_host, admin_port);
|
||||
admin_startup_info = Some((admin_ui_url, admin_token.as_str().to_owned()));
|
||||
|
||||
|
||||
@@ -197,11 +197,7 @@ pub(crate) async fn chat_completions(
|
||||
&effective.backend,
|
||||
&mut openai_req,
|
||||
);
|
||||
super::routes::inject_glm_thinking(
|
||||
&anthropic_req,
|
||||
&effective.backend,
|
||||
&mut openai_req,
|
||||
);
|
||||
super::routes::inject_glm_thinking(&anthropic_req, &effective.backend, &mut openai_req);
|
||||
// Gemini/Vertex rejects standard JSON Schema keywords; sanitize tool schemas.
|
||||
if matches!(
|
||||
effective.backend,
|
||||
|
||||
@@ -204,10 +204,7 @@ pub fn app_multi_with_shared(
|
||||
.and_then(|h| h.split(':').next())
|
||||
.unwrap_or("localhost")
|
||||
.to_owned();
|
||||
axum::response::Redirect::temporary(&format!(
|
||||
"http://{}:{}/admin/",
|
||||
host, port
|
||||
))
|
||||
axum::response::Redirect::temporary(&format!("http://{}:{}/admin/", host, port))
|
||||
}),
|
||||
)
|
||||
.merge(final_router)
|
||||
@@ -1077,7 +1074,10 @@ pub(crate) fn inject_glm_thinking(
|
||||
if !is_glm {
|
||||
return;
|
||||
}
|
||||
if matches!(&body.thinking, Some(anthropic::ThinkingConfig::Enabled { .. })) {
|
||||
if matches!(
|
||||
&body.thinking,
|
||||
Some(anthropic::ThinkingConfig::Enabled { .. })
|
||||
) {
|
||||
// reasoning_effort was injected by anthropic_to_openai_request; GLM doesn't use it.
|
||||
req.extra.remove("reasoning_effort");
|
||||
req.extra.insert(
|
||||
|
||||
Reference in New Issue
Block a user