feat(providers): add DeepSeek support - #421
Conversation
There was a problem hiding this comment.
Checked this against DeepSeek's official API docs. The core premise — that DeepSeek is a Chat-only service — doesn't hold: DeepSeek ships all three protocol shapes this catalog models.
1. The Codex exclusion is factually incorrect
capability.ts:
if (kind === 'codex' && account.service === 'deepseek') {
return { tier: 'unavailable', reason: 'protocol-unsupported' };
}DeepSeek implements the OpenAI Responses API natively — server-side, no translation proxy — and publishes a dedicated Codex integration page. The documented provider config is exactly the seam we already drive through codexEnv (OPENAI_BASE_URL + CODEX_API_KEY):
[model_providers.deepseek]
base_url = "https://api.deepseek.com/"
wire_api = "responses"Two real constraints, both already satisfied: Responses needs codex client >= 0.144.0 (we pin 0.144.1), and as of the 0731 docs only deepseek-v4-flash is supported, with deepseek-v4-pro slated for early August 2026.
Note that simply deleting the block yields native, since openai-chat is already in codex's native protocol list.
Separately — even if an exclusion were warranted, account.service === 'deepseek' is the wrong seam. This file's own doc comment says bindings are "derived from the adapters' real injection seams — the UI hardcodes nothing," and this is a service-name special case inside a protocol-driven table.
2. Missing the Anthropic Messages variant
DeepSeek exposes an Anthropic-compatible endpoint at https://api.deepseek.com/anthropic (/v1/messages), with an official Claude Code integration page. The catalog registers a single openai-chat variant, so bindingAvailability gives claude-code tier: 'translate' — routing through the local aigateway translator when a documented native path exists.
DeepSeek belongs in the dual-variant shape used by openrouter / vercel-gateway, not the single-variant shape used by openai-api.
Documented gaps on that endpoint, worth knowing before wiring it: no images/documents, no MCP, no cache_control, no top_k, and Claude model names are remapped (Opus -> deepseek-v4-pro, Sonnet/Haiku -> deepseek-v4-flash).
3. Consequent problems
- Tests encode the bug.
capability.test.tsassertsclaude-code -> { tier: 'translate' }andcodex -> unavailable. Both lock in the incorrect behavior rather than catching it. - i18n copy. "OpenAI Chat-compatible" / "OpenAI Chat 兼容" in both locales becomes inaccurate once the other two shapes are represented.
- Minor: the base URL omits
/v1whileopenai-apiandxaiinclude it. Harmless for chat (DeepSeek aliases both paths), but one bare URL can't serve both chat and the Anthropic path anyway — the variant split resolves this.
Shape of the fix
Three variants (Anthropic at /anthropic, Responses and Chat at the root), delete the codex special case entirely, update tests and i18n.
One snag: ServiceVariant['id'] is typed 'openai' | 'anthropic' | 'default', so a third protocol needs that union widened — DeepSeek is the first catalog service offering all three.
credentialType for the Anthropic variant should be 'api-key'. DeepSeek's header compatibility table lists x-api-key as Fully Supported (anthropic-version and anthropic-beta are both Ignored), which maps to the ANTHROPIC_API_KEY branch of claudeCodeEnv. Their Claude Code page instructs ANTHROPIC_AUTH_TOKEN — i.e. Authorization: Bearer — so that path is accepted too, but 'api-key' matches both the compatibility table and this catalog's existing convention: 'auth-token' is used for gateways (openrouter, vercel-gateway), while direct vendor entries like anthropic-api use 'api-key'.
Summary
Testing
pnpm test(2480 passed, 1 skipped)pnpm typecheckNotes