feat: Proxy extensions — /v1/completions + /v1/embeddings

- Added generic execute_proxy_request executor for any path suffix
- POST /v1/completions — forwards to upstream /completions
- POST /v1/embeddings — forwards to upstream /embeddings
- Both use existing auth + route resolution infra
- 18/18 tests pass
This commit is contained in:
boos
2026-05-17 16:59:43 +08:00
parent 4ac9c2385f
commit 84b4b16ebd
2 changed files with 85 additions and 0 deletions
+44
View File
@@ -45,6 +45,8 @@ pub enum DownstreamSource {
pub fn routes() -> Router<AppState> {
Router::new()
.route("/v1/chat/completions", post(chat_completions))
.route("/v1/completions", post(completions_handler))
.route("/v1/embeddings", post(embeddings_handler))
.route("/v1/images/generations", post(images_generations))
.route("/v1/models", get(list_models))
}
@@ -82,6 +84,48 @@ async fn chat_completions(
}
}
/// POST /v1/completions
async fn completions_handler(
State(state): State<AppState>,
headers: HeaderMap,
Json(body): Json<Value>,
) -> AppResult<Json<Value>> {
info!("POST /v1/completions");
check_auth(&state, &headers).await?;
let model = body
.get("model")
.and_then(|m| m.as_str())
.ok_or_else(|| AppError::BadRequest("model is required".into()))?
.to_string();
let route = resolve_route(&state, &model).await?;
let result = executors::execute_proxy_request(state, route, "/completions", body).await?;
Ok(Json(result))
}
/// POST /v1/embeddings
async fn embeddings_handler(
State(state): State<AppState>,
headers: HeaderMap,
Json(body): Json<Value>,
) -> AppResult<Json<Value>> {
info!("POST /v1/embeddings");
check_auth(&state, &headers).await?;
let model = body
.get("model")
.and_then(|m| m.as_str())
.ok_or_else(|| AppError::BadRequest("model is required".into()))?
.to_string();
let route = resolve_route(&state, &model).await?;
let result = executors::execute_proxy_request(state, route, "/embeddings", body).await?;
Ok(Json(result))
}
/// POST /v1/images/generations
async fn images_generations(
State(state): State<AppState>,