crabllm-proxy today gates the route tree behind proxy::router(state, admin_routes), which assembles the full set of /v1/* routes and unconditionally installs the built-in auth middleware and the active-connections tracker. The handler functions themselves (handlers::chat_completions, handlers::embeddings, etc.) are pub(crate) and therefore unreachable from outside the crate.
This is fine for the standalone gateway use case but locks out anyone embedding crabllm-proxy inside another binary who needs a different middleware stack — e.g. wanting to install their own auth layer (DB-backed, OAuth-introspection, mTLS) instead of the built-in HashMap<token, key_name> lookup, or needing to add edge-level rate limiting / tracing / request-id propagation that has to wrap the handler.
Today the workaround is either to fork the crate or to wrap the assembled Router from the outside, neither of which lets you actually replace the auth layer (axum doesn't support removing layers, and the inner middleware still runs).
Proposal
Make handlers pub (currently pub(crate) mod handlers;). Double-check anthropic — its module is already pub, but verify the handler fns inside are reachable. Embedders can then assemble their own Router:
let app = Router::new()
.route("/v1/chat/completions", post(handlers::chat_completions::<S, P>))
.route("/v1/messages", post(anthropic::messages::<S, P>))
// ... other routes ...
.layer(my_auth_middleware)
.layer(my_observability_middleware)
.with_state(state);
proxy::router() stays as the convenient default for users who want the batteries-included shape; the change is purely additive — exposing what's already there.
Scope
One-line visibility change on mod handlers;. No API redesign, no behavior change. The default proxy::router() continues to work exactly as today.
Out of scope (deliberately)
Changes to Extension, KeyName, or Storage. This issue is only about giving embedders the same routing flexibility the crate has internally. Auth-layer pluggability (a resolver trait), typed identity in RequestContext, and additional storage backends are separate conversations that should each earn their merit on their own.
crabllm-proxytoday gates the route tree behindproxy::router(state, admin_routes), which assembles the full set of/v1/*routes and unconditionally installs the built-in auth middleware and the active-connections tracker. The handler functions themselves (handlers::chat_completions,handlers::embeddings, etc.) arepub(crate)and therefore unreachable from outside the crate.This is fine for the standalone gateway use case but locks out anyone embedding
crabllm-proxyinside another binary who needs a different middleware stack — e.g. wanting to install their own auth layer (DB-backed, OAuth-introspection, mTLS) instead of the built-inHashMap<token, key_name>lookup, or needing to add edge-level rate limiting / tracing / request-id propagation that has to wrap the handler.Today the workaround is either to fork the crate or to wrap the assembled
Routerfrom the outside, neither of which lets you actually replace the auth layer (axum doesn't support removing layers, and the inner middleware still runs).Proposal
Make
handlerspub(currentlypub(crate) mod handlers;). Double-checkanthropic— its module is alreadypub, but verify the handler fns inside are reachable. Embedders can then assemble their ownRouter:proxy::router()stays as the convenient default for users who want the batteries-included shape; the change is purely additive — exposing what's already there.Scope
One-line visibility change on
mod handlers;. No API redesign, no behavior change. The defaultproxy::router()continues to work exactly as today.Out of scope (deliberately)
Changes to
Extension,KeyName, orStorage. This issue is only about giving embedders the same routing flexibility the crate has internally. Auth-layer pluggability (a resolver trait), typed identity inRequestContext, and additional storage backends are separate conversations that should each earn their merit on their own.