Provedores upstream
Os 20 provedores que o Load Balance sabe falar, o que o proxy faz para escolher um deles e o que muda entre eles no comportamento que você enxerga: header enviado, formato do corpo, streaming e janela de uso.
Seu cliente sempre envia POST /v1/messages. Quando o destino não é Anthropic nativo, o LB converte o corpo para o formato do upstream e reconverte a resposta — você continua recebendo Anthropic, inclusive o SSE. A conversão é opcional por provider, e é isso que a seção formatos e conversão descreve.
Pré-requisitos#
Para escolher um provedor você precisa de três coisas:
sk-lb-…) para o seu cliente apontar para cá. O Quickstart do proxy mostra como emitir e configurar./app/keys, com o provider certo. Essa tela é ADMIN-only — veja URLs e grupos para como a chave entra no rodízio.Dois provedores merecem atenção na baseUrl: o z-ai vem com defaultBaseUrl vazio de propósito, porque existem duas superfícies distintas e você escolhe na baseUrl. E o openai-codex aponta para https://chatgpt.com/backend-api/codex — o endpoint da assinatura do ChatGPT, não a Responses API pública.
O authType é parte da identidade da chave, não um detalhe de cadastro: ele entra na resolução do provider e decide quais efeitos o adapter aplica. São oito valores — api_key, bearer, claude_cloud, oauth_max, oauth_console, codex_oauth, github e github-copilot.
A família de provedores#
Os 20 adapters se auto-registram por efeito colateral do import — cada arquivo chama registerProvider(new XProvider()) no fim, e o barrel backend/src/providers/index.ts importa os 20 na ordem. Não existe lista manual para manter, e o entity_list abaixo segue a ordem do próprio barrel.
Os que você mais vai usar
Os demais
Como o proxy sabe qual é o provedor#
A resolução acontece em resolveProvider(providerName?, authType?, baseUrl?), em backend/src/providers/registry.ts. A ordem é fixa e o primeiro critério que casar vence:
providerName e ele está registrado, acabou — nenhum outro critério é consultado.authType. github-copilot resolve para github-copilot; github para github-models; codex_oauth para openai-codex; e oauth_max, oauth_console ou claude_cloud para anthropic.baseUrl. Duas estratégias, e a diferença importa: substring crua na URL para a maioria — minimax.io, openrouter.ai, generativelanguage.googleapis.com, api.anthropic.com, chatgpt.com/backend-api/codex, opencode.ai, xiaomimimo.com, morphllm.com, api.mistral.ai, inference.poolside.ai, agentrouter.org, dashscope*.aliyuncs.com — e casamento por sufixo de host para z.ai, meta.ai e x.ai.openai. Se nada casou, o provider é openai. O único throw do arquivo acontece se nem o openai estiver registrado.baseUrl.includes('z.ai') casaria buzz.ai e jazz.ai, que contêm a substring — e rotearia essas chaves para a Z.AI. O mesmo vale para meta.ai (someta.ai, beta.aihost.com) e para x.ai. Por isso os três usam hostMatches(hostOf(baseUrl), domain): o domínio tem que ser o host exato ou um subdomínio dele. Uma URL sem protocolo é normalizada antes do parse, e uma URL que não parseia devolve host vazio — e host vazio não casa com nada.
O que muda entre eles#
A tabela lista só os quirks que você sente: a forma do auth e a diferença de comportamento que muda o que chega no upstream ou o que volta para o seu cliente. Vários providers OpenAI-compat — openai, morph, morph-fast-models, nvidia, poolside, xai — só fazem o básico: convertem x-api-key para Bearer e apagam anthropic-version e anthropic-beta. Por isso não têm linha própria.
| Provedor | Forma do auth | O que muda no comportamento |
|---|---|---|
anthropic | x-api-key; Authorization: Bearer em oauth_max e oauth_console | Com OAuth, o fingerprint do Claude Code entra no pedido: o anthropic-beta canônico é unido ao que o cliente já mandou e a URL ganha ?beta=true. Para listar modelos o header vira Bearer, porque o upstream devolveria 401 no slot x-api-key. |
openai-codex | codex_oauth por device code; sem header de chave | Apaga x-api-key, anthropic-version e anthropic-beta, porque o endpoint de assinatura não é a Responses API pública. Também força stream: true e rebufferiza a transcrição para quem pediu não-streaming. |
opencode | Authorization: Bearer no chat/responses; x-api-key no messages; x-goog-api-key no gemini | Apaga Authorization, authorization, x-api-key, anthropic-version e anthropic-beta de uma vez — cada formato reapresenta a mesma chave do seu jeito. O modelo decide o protocolo: claude-* vai messages, gpt-*/grok-*/muse-spark* vão responses, jev-* não é API de chat. |
meta | Authorization: Bearer — normaliza x-api-key encima | Mantém anthropic-version e apaga só anthropic-beta, porque fala o protocolo Anthropic. Os 8 providers OpenAI-compat apagam os dois. E sobrescreve cache_control.ttl para 5m, único valor que o Meta aceita. |
mimo | Header próprio api-key — não é Bearer nem x-api-key | Monta o objeto de headers do zero e descarta os headers customizados do operador que conflitem (authorization, api-key, x-api-key, content-type, anthropic-version, anthropic-beta). Também escreve thinking sempre explícito e renomeia max_tokens para max_completion_tokens. |
z-ai | x-api-key no mirror; sempre Bearer no compat | cache_control é implícito no upstream: o campo é removido de system[], tools[] e messages[].content[] em toda superfície que não seja o mirror /api/anthropic, para não sujar o log de auditoria dele. |
github-copilot | authorization: Bearer <copilotToken> + Copilot-Integration-Id | Renova sozinho: se o token não existe ou está a menos de 60 s de expirar, busca outro, persiste em metadata.copilotToken e sobrescreve ctx.keyDetails.headers inteiro. Sem o Copilot-Integration-Id o Copilot não responde. |
minimax | x-api-key + anthropic-version | É destreamForCache: um pedido com stream: true vai sem stream ao upstream e o proxy re-sintetiza o SSE, porque o MiniMax só honra o cache de prompt no modo sem stream — em streaming ele cobra a entrada inteira. |
qwen | x-api-key convertido para Bearer | Tem um stripper próprio que roda antes do conversor, porque o anthropicToOpenAI pode vazar restos Anthropic-only como top_k e thinking. |
openrouter | x-api-key convertido para Bearer | Claude sai por /v1/messages nativo, os demais modelos por /v1/chat/completions — o provider escolhe pelo id do modelo, não pela chave. |
O endpoint de assinatura do ChatGPT não aceita requisição sem streaming. O provider força stream: true sempre; se você pediu não-streaming, o transformResponse rebufferiza a transcrição inteira numa mensagem Anthropic antes de responder. O resultado é correto, mas o custo de memória é o do stream completo.
Formatos e conversão#
A conversão só acontece quando o destino não fala Anthropic nativo. Quem decide é o próprio adapter, devolvendo postProcessShape no transformRequest: false significa que o upstream responde no formato Anthropic, e o corpo vai intacto com a resposta crua; true significa que o LB reconverte a resposta. O handler do proxy só bifurca nesse campo.
Os conversores vivem em backend/src/converters/, e o barrel re-exporta só a fachada format.js:
| Conversor | Direção | Quem usa |
|---|---|---|
anthropicToOpenAI | Anthropic → OpenAI Chat | o mais usado: 9 providers |
anthropicToResponses | Anthropic → Responses | Codex, OpenCode no protocolo responses |
anthropicToGeminiNative | Anthropic → :generateContent | Gemini nativo, OpenCode no protocolo gemini |
geminiToAnthropic | Gemini → Anthropic (inclui o transform de stream) | Gemini nativo, OpenCode |
responsesToAnthropic + responsesStreamTransform | Responses → Anthropic (JSON e SSE) | Codex, OpenCode |
openAIToAnthropic | OpenAI Chat → Anthropic | Gemini na superfície compat, OpenCode |
mistralToAnthropic | resposta da Mistral → Anthropic | Mistral |
Estes providers não convertem nada, porque falam Anthropic nativo nas duas direções:
anthropicmetaminimax na superfície /anthropicagentrouterz-ai no mirror AnthropicO anthropicToOpenAI pode deixar passar restos Anthropic que o upstream OpenAI não entende — top_k, thinking. Por isso o Qwen tem um stripper próprio, que roda antes do conversor; a filterOpenAIChat é a whitelist genérica compartilhada pelos outros. O opencode faz o contrário: detecta a resposta pelo shape do JSON (output, candidates, choices), não pelo protocolo declarado.
Antes de qualquer conversão, sanitizeRequestBody normaliza o corpo inteiro: preenche metadata.user_id quando falta, corta códigos ANSI e sufixos [...] que o Claude Code injeta no nome do modelo, remove context_management e output_config por padrão, poda mídia antiga até caber no orçamento e valida cada bloco contra o allowlist de 17 tipos. A camada de conversão herda esse corpo já limpo.
Contas de plano#
Falar o mesmo protocolo não faz de um provedor uma chave de API comum. As contas de plano são assinaturas com janela de uso, e elas saem do rodízio antes do cooldown. São quatro famílias:
| Plano | Como a conta é reconhecida | De onde vem o dado da janela | Identidade da conta |
|---|---|---|---|
| Claude | oauth_max, oauth_console, claude_cloud | anthropic-ratelimit-unified-5h/7d-* na resposta | mesma organização Claude → mesma janela |
| Codex | codex_oauth | x-codex-primary/secondary-* na resposta | mesmo chatgpt-account-id → mesma janela |
| MiniMax | provider minimax | probe em token_plan/remains, a cada 5 min | cada chave é a própria conta |
| Muse | meta_device | probe em subs_usage, a cada 5 min | cada chave é a própria conta |
A chave do limite é a conta upstream, não a chave. Duas chaves da mesma conta Codex, com o mesmo chatgpt-account-id, dividem a janela; o mesmo vale para duas chaves da mesma organização Claude. Sem identificador de conta, cada chave é a sua própria conta — e é por isso que o MiniMax e a Muse aparecem por provider: eles não carregam esse identificador.
O Anthropic e o Codex publicam o estado em headers na resposta, de graça, a cada chamada. O MiniMax e a Muse não publicam: o LB faz uma consulta ativa a cada 5 minutos contra token_plan/remains e subs_usage, e outra logo depois de um 429. Quando a leitura passa do corte — 95% por padrão, ajustável por chave — a conta sai do rodízio até o reset; se todas as contas do grupo estouraram, o LB responde 429 imediato com o Retry-After real, sem tocar no upstream.
A página de limites de uso detalha o corte, o estado no Redis e como uma leitura abaixo do corte devolve a conta ao rodízio.
Próximos passos#
Agora que você sabe o que cada provedor faz, veja como o LB escolhe entre eles.
Depois de dominar o básico, explore além:
round_robin, rated, hedge e race, o cooldown e o failover.