Welcome to the third chapter of the cc-switch tutorial!
In the previous chapter, Intelligent Routing & Failover, we gave our gateway the ability to switch providers instantly if one fails.
However, we left a major problem unsolved. Even if we route a request from Claude Code to OpenAI, they might not understand each other. It's like calling a person who speaks French and handing the phone to someone who only speaks Japanese.
In this chapter, we will build the Provider Adaptation Layerβthe universal translator for your AI tools.
While most AI providers (Anthropic, OpenAI, Google) offer similar features, their "access points" are different.
/v1/messages. OpenAI uses /v1/chat/completions.x-api-key. OpenAI needs Authorization: Bearer ....max_tokens, the other max_completion_tokens.If cc-switch simply forwards the raw request from one to the other, the destination API will reject it with an error.
We use the Adapter Pattern. Think of this like a universal travel plug adapter.
To make this work in Rust, we use a Trait. A Trait is like a contract. It says: "If you want to be a Provider in this system, you must know how to do these specific things."
We defined this contract in adapter.rs. It breaks down the translation process into small steps.
ProviderAdapter)Every adapter (whether for OpenAI, Azure, or a custom gateway) implements this interface.
// src-tauri/src/proxy/providers/adapter.rs
pub trait ProviderAdapter: Send + Sync {
// 1. Identify yourself (for logging)
fn name(&self) -> &'static str;
// 2. Figure out the base address
fn extract_base_url(&self, provider: &Provider)
-> Result<String, ProxyError>;
// ... more functions below
}
Every provider handles passwords (API Keys) differently. The adapter's job is to inject the key into the request in the exact format the provider demands.
// src-tauri/src/proxy/providers/adapter.rs
// 3. Find the API key in the settings
fn extract_auth(&self, provider: &Provider) -> Option<AuthInfo>;
// 4. Inject the key into the HTTP headers
fn add_auth_headers(&self, req: RequestBuilder, auth: &AuthInfo)
-> RequestBuilder;
Sometimes, just changing the URL isn't enough. We might need to rewrite the actual message body (JSON) from Anthropic format to OpenAI format.
// src-tauri/src/proxy/providers/adapter.rs
// 5. Does this provider need JSON translation?
fn needs_transform(&self, _provider: &Provider) -> bool {
false // Default is "No", just pass it through
}
// 6. The logic to rewrite the JSON body
fn transform_request(&self, body: Value, _prov: &Provider)
-> Result<Value, ProxyError> {
Ok(body) // Default is to return the body unchanged
}
Let's trace what happens when your CLI tool sends a request that needs adaptation.
When the proxy server receives a request, it doesn't hard-code logic for OpenAI. Instead, it asks the ProviderAdapter to do the work.
Here is a simplified look at how the handler uses the adapter:
// Inside the request handler (simplified)
// 1. Get the correct adapter for the chosen provider
let adapter = provider_factory.get_adapter(&provider.type);
// 2. Build the new URL using the adapter's logic
let url = adapter.build_url(&provider.base_url, "/v1/messages");
// 3. Create the HTTP request
let mut request = client.post(url);
// 4. Ask the adapter to add authentication headers
if let Some(auth) = adapter.extract_auth(&provider) {
request = adapter.add_auth_headers(request, &auth);
}
Beginner Note: This pattern makes our code very clean. The handler doesn't care which provider is being used. It just follows the steps: Build URL -> Add Auth -> Send.
While the Rust backend handles the logic of adaptation, the configuration needs to come from the user.
In cc-switch, we support "Universal Providers" (like NewAPI or custom gateways) that are compatible with multiple tools. We define these presets in the frontend code so the UI knows how to display them.
universalProviderPresets.ts)This TypeScript file defines templates for providers that are known to work well.
// src/config/universalProviderPresets.ts
export const universalProviderPresets: UniversalProviderPreset[] = [
{
name: "NewAPI",
providerType: "newapi", // Tells Rust which adapter to use
defaultApps: {
claude: true, // Works with Claude
codex: true, // Works with Codex
gemini: true, // Works with Gemini
},
// ... icons and descriptions
},
];
When a user selects "NewAPI" in the dashboard:
providerType: "newapi".NewApiAdapter (which behaves like an OpenAI standard adapter) to handle traffic.Without the Provider Adaptation Layer:
With the Adapter:
ProviderAdapter trait.In this chapter, we learned:
ProviderAdapter) that acts as a contract for all providers.Now that our system can route traffic (Chapter 2) and translate languages (Chapter 3), we need a place to remember the user's settings, API keys, and usage history.
In the next chapter, we will dive into the database.
Next Chapter: SQLite Persistence & Schema
Generated by Code IQ