When Configuration Becomes a Request Router

CC Switch changes character when app takeover is enabled. Instead of merely writing a provider into a client's native file, it places a local HTTP proxy between the client and the upstream service, so provider selection can happen at request time. This chapter explains that request path, what it makes possible, and why protocol translation remains a compatibility contract rather than a transparent promise; the practical goal is to decide when routing earns its extra complexity.

From a live file to a request path

In direct mode, the provider switch described earlier ends at the client's configuration file. CC Switch writes the selected endpoint and credentials in the format that the client expects. The client then connects to that endpoint, subject to its own reload behavior. Once takeover is enabled, the endpoint in the client's configuration is changed to a local address instead. The remote provider is no longer the client's immediate destination; CC Switch becomes the first hop.

That first hop is not just a forwarding socket. The proxy identifies the application whose request it is handling, looks up the provider target for that application, and then forwards the request to the selected upstream. A Claude request and a Codex request may arrive at the same local service but follow different handlers and different provider rules. The proxy state keeps current targets per application, so routing Claude through one provider does not require Codex or Gemini to share the same target.

This is the key difference between configuration-time choice and request-time choice. In direct mode, the client has learned the provider's remote address. To switch, CC Switch must update that address and wait for the client to reload it. In takeover mode, the client continues to talk to the stable local address. A provider switch updates the target held by the proxy, and the next routed request can use the new provider without teaching the running client a new endpoint. The user sees one action, but the system has moved the decision one layer downstream.

The proxy also creates a place to observe requests. When logging is enabled, it can record the application, provider, model, token fields, timing, status, and error information associated with forwarded traffic. This covers the traffic that actually traverses the proxy; it does not turn direct connections or client-written session files into proxy records. That distinction matters when interpreting an empty dashboard. No proxy record may mean that routing was not enabled, logging was off, the request bypassed the proxy, or the relevant evidence belongs in a session log instead.

The request lifetime matters for observation too. A non-streaming request has a clear endpoint, but a streaming response continues after the initial headers arrive. CC Switch's forwarding path ties active-connection accounting to the response lifetime, so the connection count is not reduced merely because the proxy has begun returning a stream. This is a small implementation choice with a visible consequence: an operational status panel is more useful when “active” covers the work that is still flowing rather than only the time spent choosing an upstream.

Takeover is also the prerequisite for the proxy's other request-plane functions. A local proxy can switch targets without a client restart, convert between API formats when the provider requires it, record usage as requests pass through, and apply failover policy when configured. None of those benefits follows merely from having several provider cards in the database. Without takeover, CC Switch can still manage and project configurations, but provider selection remains at the client boundary.

The proxy has its own lifecycle. It must bind a listen address and port, reject a second start while already running, and expose a health or status view. When it stops, CC Switch is designed to restore the application's pre-takeover configuration and close the service. That restoration depends on the saved ownership state and on the native file still being recognizable. An abnormal exit, an external edit, or a concurrent program can complicate the handoff, which is why takeover should be treated as a managed mode rather than as a harmless temporary flag.

The listen address makes the trust boundary concrete. Loopback, such as 127.0.0.1, restricts access to the local machine and is the documented recommendation. Binding to 0.0.0.0 or another non-loopback interface permits other hosts on the network to reach the listener. The project's security policy explicitly treats inbound requests to such a listener as untrusted input. A user choosing network access is therefore not merely solving a connectivity problem; they are expanding who can send data into a component that handles provider credentials, requests, responses, and configuration-sensitive behavior.

The relationship among these parts is easier to see as a request sequence:

sequenceDiagram participant C as AI client participant P as CC Switch local proxy participant R as Provider router participant T as Optional translator participant U as Upstream provider participant L as Request log C->>P: Send API request P->>R: Identify app and select target R->>T: Apply route and format rules T->>U: Forward upstream request U-->>T: Return response or stream T-->>P: Convert response when needed P->>L: Record outcome and usage P-->>C: Return response or stream

The diagram's “optional translator” is important. A native pass-through request may not need conversion, while a client and an upstream can speak different dialects. The proxy is the location where that difference can be handled, but handling it there does not make the dialects identical. Every conversion rule is another place where a client release, model capability, or upstream validator can expose an edge case.

Routing is also independent by application. The proxy configuration stores separate enabled states, queues, retry policies, and request statistics for the supported local-routing applications. A developer may route Claude for hot switching and logging while leaving Gemini direct, or route Codex only because a selected provider needs local protocol conversion. This selective design is more useful than an all-or-nothing proxy because the reasons for takeover are often app-specific. It also requires the user to know which applications are actually intercepted.

Translation is a contract, not magic

The simplest routed request is a pass-through: the client sends a protocol the provider accepts, the proxy adds the right upstream details, and the response comes back in the format the client already understands. The harder case is a useful one. A provider may offer an OpenAI-compatible Chat Completions endpoint while a client such as Codex expects the OpenAI Responses protocol, or Claude Code may send Anthropic Messages while a selected upstream accepts only an OpenAI format. CC Switch documents conversion in both directions for supported cases, including streaming responses.

This is why a provider's API format is an explicit setting rather than an incidental detail. Claude providers can be marked as Anthropic Messages, OpenAI Chat Completions, or OpenAI Responses. The first can pass through when the endpoint is compatible. The latter two require the proxy to translate the request and response, and they require takeover to be active. A direct connection with the wrong format cannot be repaired by a card label; the conversion exists in the local request path.

Codex makes the same point through model names. A client may send a model alias or role-oriented name, while the upstream exposes a different identifier. When local routing is required, CC Switch can maintain a model mapping and generate the catalog that Codex presents to the user. The mapping declares the real upstream model, an optional display name, and sometimes context information. This preserves a client-facing choice while giving the upstream the identifier it actually understands.

The two names should not be treated as cosmetic variants. The requested model is part of what the client asked for; the outbound model is what the upstream receives; and the pricing model used for accounting may need to follow the outbound or normalized identity. The current forwarding model keeps outbound-model attribution separate for this reason. If a proxy records only the client's alias, it can misattribute usage or apply the wrong price when the route maps that alias to another model.

A universal provider reduces repeated setup, not semantic differences. It stores a common endpoint and key, then generates child configurations for the applications selected by the user. Each child can still carry an app-specific model choice. A common provider may be convenient when one gateway serves Claude, Codex, and Gemini, but it does not mean that the gateway's protocol, model names, reasoning controls, or reload behavior are shared across those clients. The child projection remains the place where those differences are represented.

The same caution applies to model discovery. CC Switch can fetch available models from an OpenAI-compatible /v1/models endpoint for supported provider forms, but a provider that lacks that endpoint, returns an unexpected shape, or requires another path still needs manual configuration or an explicit models URL. Discovery is a convenience for filling a field; it is not a capability probe that proves the selected model supports every tool, context size, image mode, or reasoning parameter the client can request.

Endpoint construction introduces another boundary. By default, a base URL is treated as a prefix and the proxy or client appends a conventional path. Some vendors require a nonstandard multi-level path or provide a complete endpoint instead. CC Switch's full-URL mode tells the forwarding and testing paths to use the configured endpoint as the destination rather than applying the default suffix. That setting can solve a genuine URL-layout problem, but it also puts more responsibility on the user: a complete endpoint must be correct for the request type, and related operations may need a compatible sibling path.

The compatibility contract becomes visible in the project's release history. Recent releases include repairs for Codex configuration changes, Chat-to-Responses tool-call handling, reasoning fields, image-generation routes, strict tool-schema validation, prompt-prefix caching, and model catalogs. These fixes do not make the architecture incoherent. They show that a proxy that translates between moving systems is an actively maintained compatibility layer. A provider preset is a maintained starting point, not an eternal guarantee for every client or upstream version.

Saved configurations make this maintenance issue practical. Release notes repeatedly distinguish a new preset from an existing provider card: a changed endpoint, model, protocol, or capability declaration may apply when a provider is newly created, while a saved card retains its prior settings. A user who sees a newer preset in the application cannot assume that an older card has silently inherited it. Re-importing, editing, or testing the existing card may be necessary, especially after a client release changes the fields it accepts.

There is a corresponding limit to what translation can promise. The proxy may convert the outer request shape while preserving the intended content, but providers can differ in tool validation, reasoning controls, streaming conventions, context limits, image support, rate limits, and account policy. The manual notes that some reasoning detection is based on provider name, URL, and model keywords rather than a live capability probe. A relay that rewrites those names may escape detection, while a model with a familiar name may expose different behavior on another platform. A converted request that reaches the upstream is not proof that its semantics match the native path.

The decision is therefore conditional. Use direct mode when the client and provider already speak the same protocol, occasional switching is acceptable, and the extra network hop would add little value. Use takeover when immediate target changes, centralized request logs, local format conversion, or failover are important enough to justify a local service and its trust boundary. Route only the applications that need those functions, keep the listener on loopback unless network access is intentional, and test the actual model and tool behavior after configuration changes.

Once provider choice lives in the request path, failure can be handled as a policy rather than as a manual card change. But several provider cards do not automatically make a reliable service: the router still needs an ordered candidate chain, a retry budget, and a way to stop sending work to a provider that is repeatedly failing. That is the boundary the next discussion examines.

References