Skip to content

Routing Table

A routing table is the per-channel routing map. It takes an incoming request, classified into a (OperationFamily, ProtocolKind) pair, and tells GPROXY one of four things:

  • Passthrough — forward the request to upstream unchanged (same protocol on both sides; GPROXY only rewrites the model, headers, and auth envelope).
  • TransformTo { destination } — run the protocol transform layer to convert the request into a different (operation, protocol) pair before forwarding. The inverse transform runs on the response.
  • Local — handle the request entirely inside GPROXY; never call upstream. Used for model_list / model_get under the *-only presets.
  • Unsupported — the route is not supported on this channel; return 501.

Each channel owns a fn routing_table(&self) -> RoutingTable method. The value it returns is the channel’s default routing map, and the engine uses it unless an operator has overridden it per-provider from the console.

Defined in sdk/gproxy-channel/src/routing.rs.

pub struct RouteKey {
pub operation: OperationFamily,
pub protocol: ProtocolKind,
}

OperationFamily (in gproxy-protocol) is the protocol-agnostic kind of call the route represents. The full list:

FamilyWhat it is
model_listList available models.
model_getFetch one model by id.
count_tokensToken-count endpoint.
compact”Compact” a conversation (Claude Code compact).
generate_contentNon-streaming chat / message / generate call.
stream_generate_contentStreaming chat / message / generate call.
create_image / stream_create_imageImage generation.
create_image_edit / stream_create_image_editImage editing.
openai_response_websocketOpenAI Responses WebSocket.
gemini_liveGemini Live bidi session.
embeddingsEmbedding endpoint.
file_upload / file_list / file_get / file_content / file_deleteFile endpoints.

ProtocolKind is the wire dialect:

KindMeaning
openaiOpenAI — umbrella used before a specific API is picked.
openai_chat_completionsOpenAI Chat Completions API.
openai_responseOpenAI Responses API.
claudeAnthropic Messages API.
geminiGoogle Gemini generateContent.
gemini_ndjsonGemini streaming over NDJSON (same semantics as gemini).

Example: the Anthropic channel’s default table

Section titled “Example: the Anthropic channel’s default table”

Simplified from channels/anthropic.rs:

let pass = |op, proto| (RouteKey::new(op, proto), RouteImplementation::Passthrough);
let xform = |op, proto, dst_op, dst_proto| (
RouteKey::new(op, proto),
RouteImplementation::TransformTo {
destination: RouteKey::new(dst_op, dst_proto),
},
);
let routes = vec![
// Native Claude routes — passthrough.
pass(OperationFamily::GenerateContent, ProtocolKind::Claude),
pass(OperationFamily::StreamGenerateContent, ProtocolKind::Claude),
// OpenAI Chat Completions clients are transformed into Claude on the way in.
xform(
OperationFamily::GenerateContent, ProtocolKind::OpenAiChatCompletion,
OperationFamily::GenerateContent, ProtocolKind::Claude,
),
// …
];

Read the table left-to-right as “when the client sends a (operation, protocol), do X”. The destination in a TransformTo entry is what the upstream ends up seeing.

The difference is a hot-path one:

  • Passthrough is the minimal-parsing fast path. The request body is forwarded with only the model, headers, and auth touched. This is why same-protocol routing is so cheap — see Providers & Channels.
  • TransformTo runs the protocol conversion in gproxy_protocol::transform, producing a new body that the upstream can accept. The inverse transform runs on the response (including per-chunk rewriting for streams).

The *-only presets (for example chat-completions-only, response-only, claude-only, gemini-only) set model_list and model_get to Local. Those requests are answered entirely from the local models table — GPROXY never calls upstream.

Concretely, when a channel’s routing table maps a route to Local, the handler calls Channel::handle_local(...) on the channel. That function builds the protocol-specific response body directly from the in-memory models snapshot.

For *-like / pass-through presets, model_list stays as Passthrough, and GPROXY merges the upstream response with the local models table after the call returns (see Models & Aliases for why and how).

Most users never touch the routing table — the defaults from each channel are fine. You’d override it when you want to:

  • Force local model listing on a channel whose upstream returns a huge list you don’t want clients to see.
  • Disable (Unsupported) a feature your upstream technically supports but you don’t want exposed (for example, blocking image generation on an OpenAI provider).
  • Cross-wire an odd shape — e.g. redirect OpenAI Responses traffic into a Chat Completions upstream by transforming the operation.

Provider-level overrides are edited from the console’s provider workspace and are stored as JSON (RoutingTableDocument). The schema enforces one entry per (operation, protocol) pair.