feat: Proxy extensions — audio, video, completions, embeddings
- Generic execute_proxy_request executor for arbitrary paths - POST /v1/completions, /v1/embeddings - POST /v1/audio/speech, /v1/audio/transcriptions, /v1/audio/translations - POST /v1/videos - All reuse existing auth + route resolution infra - 18/18 tests pass
This commit is contained in:
@@ -47,7 +47,11 @@ pub fn routes() -> Router<AppState> {
|
|||||||
.route("/v1/chat/completions", post(chat_completions))
|
.route("/v1/chat/completions", post(chat_completions))
|
||||||
.route("/v1/completions", post(completions_handler))
|
.route("/v1/completions", post(completions_handler))
|
||||||
.route("/v1/embeddings", post(embeddings_handler))
|
.route("/v1/embeddings", post(embeddings_handler))
|
||||||
|
.route("/v1/audio/transcriptions", post(audio_transcriptions))
|
||||||
|
.route("/v1/audio/translations", post(audio_translations))
|
||||||
|
.route("/v1/audio/speech", post(audio_speech))
|
||||||
.route("/v1/images/generations", post(images_generations))
|
.route("/v1/images/generations", post(images_generations))
|
||||||
|
.route("/v1/videos", post(videos_handler))
|
||||||
.route("/v1/models", get(list_models))
|
.route("/v1/models", get(list_models))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,6 +130,89 @@ async fn embeddings_handler(
|
|||||||
Ok(Json(result))
|
Ok(Json(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// POST /v1/audio/speech
|
||||||
|
async fn audio_speech(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
headers: HeaderMap,
|
||||||
|
Json(body): Json<Value>,
|
||||||
|
) -> AppResult<Json<Value>> {
|
||||||
|
info!("POST /v1/audio/speech");
|
||||||
|
|
||||||
|
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, "/audio/speech", body).await?;
|
||||||
|
Ok(Json(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// POST /v1/audio/transcriptions — proxy to upstream
|
||||||
|
async fn audio_transcriptions(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
headers: HeaderMap,
|
||||||
|
Json(body): Json<Value>,
|
||||||
|
) -> AppResult<Json<Value>> {
|
||||||
|
info!("POST /v1/audio/transcriptions");
|
||||||
|
|
||||||
|
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, "/audio/transcriptions", body).await?;
|
||||||
|
Ok(Json(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// POST /v1/audio/translations — proxy to upstream
|
||||||
|
async fn audio_translations(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
headers: HeaderMap,
|
||||||
|
Json(body): Json<Value>,
|
||||||
|
) -> AppResult<Json<Value>> {
|
||||||
|
info!("POST /v1/audio/translations");
|
||||||
|
|
||||||
|
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, "/audio/translations", body).await?;
|
||||||
|
Ok(Json(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// POST /v1/videos — list/create video generation tasks
|
||||||
|
async fn videos_handler(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
headers: HeaderMap,
|
||||||
|
Json(body): Json<Value>,
|
||||||
|
) -> AppResult<Json<Value>> {
|
||||||
|
info!("POST /v1/videos");
|
||||||
|
|
||||||
|
check_auth(&state, &headers).await?;
|
||||||
|
|
||||||
|
let model = body
|
||||||
|
.get("model")
|
||||||
|
.and_then(|m| m.as_str())
|
||||||
|
.unwrap_or("default");
|
||||||
|
|
||||||
|
let route = resolve_route(&state, model).await?;
|
||||||
|
let result = executors::execute_proxy_request(state, route, "/videos", body).await?;
|
||||||
|
Ok(Json(result))
|
||||||
|
}
|
||||||
|
|
||||||
/// POST /v1/images/generations
|
/// POST /v1/images/generations
|
||||||
async fn images_generations(
|
async fn images_generations(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
|
|||||||
Reference in New Issue
Block a user