Files
cursor-byok/server/src/control/mod.rs
T
Usuario ExemploandCursor 0ce64e20ba feat: add Brazilian Portuguese locale and configurable token pricing
Enable Portuguese-speaking users with full pt-BR translations and let users
set per-million token rates so home cost estimates reflect their actual pricing.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-14 18:47:23 -03:00

282 lines
9.2 KiB
Rust

//! Exposes the local control API.
mod ads;
mod calls;
mod harness;
mod models;
mod overview;
mod plugins;
mod service;
mod settings;
use axum::{
body::{to_bytes, Body},
extract::State,
http::{header, header::CONTENT_TYPE, HeaderValue, Method, Request, Response, StatusCode},
routing::{any, get, post, put},
Router,
};
use tower_http::{
cors::{AllowOrigin, CorsLayer},
services::ServeDir,
};
use url::{Host, Url};
pub use service::{
CallDetail, CallSummary, ControlService, DiscoveredModels, LegacyModelImportPreview,
LegacyModelImportResult, ModelConnectivityResult, ModelDiscoveryInput, ObservabilitySettings,
};
pub fn web_router(service: ControlService, assets: impl AsRef<std::path::Path>) -> Router {
Router::new()
.nest_service(
"/__byok-api__",
ServeDir::new(assets).append_index_html_on_directories(true),
)
.merge(api_router(service))
}
pub fn proxy_web_router(service: ControlService, target: Url) -> Router {
frontend_proxy_router(target).merge(api_router(service))
}
fn frontend_proxy_router(target: Url) -> Router {
let state = FrontendProxy {
client: reqwest::Client::new(),
target: target.as_str().trim_end_matches('/').to_string(),
};
Router::new()
.route("/__byok-api__/", any(proxy_frontend))
.route("/__byok-api__/{*path}", any(proxy_frontend))
.with_state(state)
}
#[derive(Clone)]
struct FrontendProxy {
client: reqwest::Client,
target: String,
}
async fn proxy_frontend(
State(proxy): State<FrontendProxy>,
request: Request<Body>,
) -> Response<Body> {
let (parts, body) = request.into_parts();
let path = parts
.uri
.path_and_query()
.map(|value| value.as_str())
.unwrap_or("/__byok-api__/");
let mut upstream = proxy
.client
.request(parts.method, format!("{}{path}", proxy.target));
for (name, value) in &parts.headers {
if name != header::HOST && name != header::CONNECTION {
upstream = upstream.header(name, value);
}
}
let body = match to_bytes(body, 64 * 1024 * 1024).await {
Ok(body) => body,
Err(error) => return proxy_error(error),
};
let upstream = match upstream.body(body).send().await {
Ok(response) => response,
Err(error) => return proxy_error(error),
};
let status = upstream.status();
let headers = upstream.headers().clone();
let body = match upstream.bytes().await {
Ok(body) => body,
Err(error) => return proxy_error(error),
};
let mut response = Response::new(Body::from(body));
*response.status_mut() = status;
for (name, value) in &headers {
if name != header::CONNECTION
&& name != header::TRANSFER_ENCODING
&& name != header::CONTENT_LENGTH
{
response.headers_mut().insert(name, value.clone());
}
}
response
}
fn proxy_error(error: impl std::fmt::Display) -> Response<Body> {
tracing::warn!(%error, "frontend development proxy failed");
Response::builder()
.status(StatusCode::BAD_GATEWAY)
.body(Body::from("frontend development server is unavailable"))
.expect("static proxy error response")
}
pub fn api_router(service: ControlService) -> Router {
Router::new()
.route("/__byok-api__/api/promotions", get(ads::get))
.route(
"/__byok-api__/api/promotions/images/{file_name}",
get(ads::image),
)
.route(
"/__byok-api__/api/promotions/{ad_id}/dismissals",
post(ads::dismiss),
)
.route(
"/__byok-api__/api/models",
get(models::list).post(models::create),
)
.route("/__byok-api__/api/models/discover", post(models::discover))
.route(
"/__byok-api__/api/models/import-v0049",
get(models::preview_v0049).post(models::import_v0049),
)
.route("/__byok-api__/api/models/order", put(models::reorder))
.route("/__byok-api__/api/overview", get(overview::get))
.route(
"/__byok-api__/api/models/{model_hash}",
put(models::update).delete(models::remove),
)
.route(
"/__byok-api__/api/models/{model_hash}/test/{test_id}",
post(models::test).delete(models::cancel),
)
.route("/__byok-api__/api/llm-calls", get(calls::list))
.route("/__byok-api__/api/llm-calls/{call_id}", get(calls::detail))
.route("/__byok-api__/api/plugins", get(plugins::list))
.route(
"/__byok-api__/api/plugins/runtime",
get(plugins::runtime_status)
.post(plugins::initialize_runtime)
.delete(plugins::cancel_runtime_initialization),
)
.route(
"/__byok-api__/api/plugins/oauth/{session_id}/poll",
post(plugins::oauth_poll),
)
.route(
"/__byok-api__/api/plugins/{plugin_id}",
axum::routing::delete(plugins::remove),
)
.route(
"/__byok-api__/api/plugins/{plugin_id}/resources/{resource_type}/add/{method_id}/begin",
post(plugins::oauth_begin),
)
.route(
"/__byok-api__/api/plugins/{plugin_id}/resources/{resource_type}/import",
post(plugins::import),
)
.route(
"/__byok-api__/api/plugins/{plugin_id}/resources/{resource_type}/export",
get(plugins::export_resources),
)
.route(
"/__byok-api__/api/plugins/{plugin_id}/resources/{resource_type}/{resource_id}",
axum::routing::delete(plugins::delete_resource),
)
.route(
"/__byok-api__/api/plugins/{plugin_id}/resources/{resource_type}/{resource_id}/actions/{action_id}",
post(plugins::action),
)
.route(
"/__byok-api__/api/plugins/{plugin_id}/resources/{resource_type}/{resource_id}/refresh",
post(plugins::refresh_resource),
)
.route(
"/__byok-api__/api/plugins/{plugin_id}/providers/{provider_id}/models/sync",
post(plugins::sync_models),
)
.route(
"/__byok-api__/api/plugins/{plugin_id}/providers/{provider_id}/models/enabled",
put(plugins::set_model_enabled),
)
.route(
"/__byok-api__/api/settings/observability",
get(settings::get).put(settings::update),
)
.route(
"/__byok-api__/api/settings/ports",
get(settings::get_ports).put(settings::update_ports),
)
.route(
"/__byok-api__/api/settings/storage/statistics",
get(settings::get_storage).delete(settings::clear_storage),
)
.route(
"/__byok-api__/api/settings/proxy",
get(settings::get_proxy).put(settings::update_proxy),
)
.route(
"/__byok-api__/api/settings/tab",
get(settings::get_tab).put(settings::update_tab),
)
.route(
"/__byok-api__/api/settings/desktop",
get(settings::get_desktop).put(settings::update_desktop),
)
.route(
"/__byok-api__/api/settings/commit",
get(settings::get_commit).put(settings::update_commit),
)
.route(
"/__byok-api__/api/settings/pricing",
get(settings::get_pricing_settings).put(settings::update_pricing_settings),
)
.route(
"/__byok-api__/api/harness/cursor/status",
get(harness::status),
)
.route(
"/__byok-api__/api/harness/cursor/ca/initialize",
post(harness::initialize_ca),
)
.route(
"/__byok-api__/api/harness/cursor/enabled",
put(harness::set_enabled),
)
.with_state(service)
.layer(desktop_cors())
}
fn desktop_cors() -> CorsLayer {
CorsLayer::new()
.allow_origin(AllowOrigin::predicate(|origin, _| local_origin(origin)))
.allow_methods([Method::GET, Method::POST, Method::PUT, Method::DELETE])
.allow_headers([
CONTENT_TYPE,
header::ACCEPT_LANGUAGE,
header::HeaderName::from_static("disable-ad-ids"),
])
}
fn local_origin(origin: &HeaderValue) -> bool {
let Ok(origin) = origin.to_str() else {
return false;
};
if origin.eq_ignore_ascii_case("tauri://localhost") {
return true;
}
let Ok(origin) = Url::parse(origin) else {
return false;
};
if !matches!(origin.scheme(), "http" | "https")
|| !origin.username().is_empty()
|| origin.password().is_some()
|| origin.path() != "/"
|| origin.query().is_some()
|| origin.fragment().is_some()
{
return false;
}
match origin.host() {
Some(Host::Domain(host)) => {
host.eq_ignore_ascii_case("localhost") || host.eq_ignore_ascii_case("tauri.localhost")
}
Some(Host::Ipv4(address)) => {
address.is_loopback() || address.is_private() || address.is_link_local()
}
Some(Host::Ipv6(address)) => {
address.is_loopback() || address.is_unique_local() || address.is_unicast_link_local()
}
None => false,
}
}