diff --git a/.env.example b/.env.example index d4084b2b..5fd9c087 100644 --- a/.env.example +++ b/.env.example @@ -6,10 +6,9 @@ # Get an API key from DeepSeek, then keep it local in `.env`. # DEEPSEEK_API_KEY= -# Global endpoint: +# Official DeepSeek Platform host (see api-docs.deepseek.com); `deepseek-cn` uses the same host. # DEEPSEEK_BASE_URL=https://api.deepseek.com -# China endpoint: -# DEEPSEEK_BASE_URL=https://api.deepseeki.com +# DEEPSEEK_PROVIDER=deepseek-cn # V4 model selection. Compatibility aliases such as `deepseek-chat` normalize # to the current V4 flash model in the TUI. diff --git a/AGENTS.md b/AGENTS.md index c21570e4..3c681249 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -56,7 +56,7 @@ See README.md for project overview, docs/ARCHITECTURE.md for internals. - **Thinking Tokens**: DeepSeek models output thinking blocks (`ContentBlock::Thinking`) before final answers. The TUI streams and displays these with visual distinction. - **Reasoning Models**: `deepseek-v4-pro` and `deepseek-v4-flash` are the documented V4 model IDs. Legacy `deepseek-chat` and `deepseek-reasoner` are compatibility aliases for `deepseek-v4-flash`. - **Large Context Window**: DeepSeek V4 models have 1M-token context windows. Use search tools to navigate efficiently. -- **API**: OpenAI-compatible Chat Completions (`/chat/completions`) is the documented DeepSeek API path. Base URL configurable for global (`api.deepseek.com`) or China (`api.deepseeki.com`); `/v1` is accepted for OpenAI SDK compatibility, and `/beta` is only needed for beta features such as strict tool mode, chat prefix completion, and FIM completion. +- **API**: OpenAI-compatible Chat Completions (`/chat/completions`) is the documented DeepSeek API path. Base URL uses the official host `api.deepseek.com` for both global and `deepseek-cn` presets; legacy typo host `api.deepseeki.com` remains recognized for backward compatibility. `/v1` is accepted for OpenAI SDK compatibility, and `/beta` is only needed for beta features such as strict tool mode, chat prefix completion, and FIM completion. - **Thinking + Tool Calls**: In V4 thinking mode, assistant messages that contain tool calls must replay their `reasoning_content` in all subsequent requests or the API returns HTTP 400. ## GitHub Operations diff --git a/config.example.toml b/config.example.toml index 60aa101e..1c7958c0 100644 --- a/config.example.toml +++ b/config.example.toml @@ -19,8 +19,8 @@ provider = "deepseek" # deepseek | deepseek-cn | nvidia-nim | openai | openrouter | novita | fireworks | sglang | vllm | ollama api_key = "YOUR_DEEPSEEK_API_KEY" # must be non-empty base_url = "https://api.deepseek.com/beta" -# base_url = "https://api.deepseeki.com" # China users -# base_url = "https://api.deepseek.com" # opt out of DeepSeek beta features +# provider = "deepseek-cn" # mainland China preset (official https://api.deepseek.com) +# base_url = "https://api.deepseek.com" # opt out of DeepSeek beta features # Optional custom model request headers for OpenAI-compatible gateways. # Authorization and Content-Type are managed by the client and cannot be overridden here. # http_headers = { "X-Model-Provider-Id" = "your-model-provider" } diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 2f3d7f3b..f4d6c425 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -1526,13 +1526,13 @@ mod tests { ..ConfigToml::default() }; config.providers.deepseek.api_key = Some("provider-key".to_string()); - config.providers.deepseek.base_url = Some("https://api.deepseeki.com".to_string()); + config.providers.deepseek.base_url = Some("https://gateway.example/v1".to_string()); config.providers.deepseek.model = Some("deepseek-v4-flash".to_string()); let resolved = config.resolve_runtime_options(&CliRuntimeOverrides::default()); assert_eq!(resolved.api_key.as_deref(), Some("provider-key")); - assert_eq!(resolved.base_url, "https://api.deepseeki.com"); + assert_eq!(resolved.base_url, "https://gateway.example/v1"); assert_eq!(resolved.model, "deepseek-v4-flash"); } @@ -1547,7 +1547,7 @@ mod tests { ..ConfigToml::default() }; config.providers.deepseek.api_key = Some("provider-key".to_string()); - config.providers.deepseek.base_url = Some("https://api.deepseeki.com".to_string()); + config.providers.deepseek.base_url = Some("https://gateway.example/v1".to_string()); config.providers.deepseek.model = Some("deepseek-v4-flash".to_string()); config .http_headers @@ -1566,7 +1566,7 @@ mod tests { let resolved = config.resolve_runtime_options(&CliRuntimeOverrides::default()); assert_eq!(resolved.api_key.as_deref(), Some("provider-key")); - assert_eq!(resolved.base_url, "https://api.deepseeki.com"); + assert_eq!(resolved.base_url, "https://gateway.example/v1"); assert_eq!(resolved.model, "deepseek-v4-flash"); assert_eq!( resolved diff --git a/crates/tui/src/config.rs b/crates/tui/src/config.rs index beb4921b..7291edcf 100644 --- a/crates/tui/src/config.rs +++ b/crates/tui/src/config.rs @@ -42,7 +42,9 @@ pub const DEFAULT_VLLM_FLASH_MODEL: &str = "deepseek-ai/DeepSeek-V4-Flash"; pub const DEFAULT_VLLM_BASE_URL: &str = "http://localhost:8000/v1"; pub const DEFAULT_OLLAMA_MODEL: &str = "deepseek-coder:1.3b"; pub const DEFAULT_OLLAMA_BASE_URL: &str = "http://localhost:11434/v1"; -pub const DEFAULT_DEEPSEEKCN_BASE_URL: &str = "https://api.deepseeki.com"; +/// Official DeepSeek API host per https://api-docs.deepseek.com/ (`deepseek-cn` preset defaults here). +/// Legacy typo hostname `api.deepseeki.com` remains recognized in URL heuristics for backward compatibility. +pub const DEFAULT_DEEPSEEKCN_BASE_URL: &str = "https://api.deepseek.com"; const API_KEYRING_SENTINEL: &str = "__KEYRING__"; pub const COMMON_DEEPSEEK_MODELS: &[&str] = &[ "deepseek-v4-pro", diff --git a/crates/tui/src/main.rs b/crates/tui/src/main.rs index 616f85d9..10f20b4a 100644 --- a/crates/tui/src/main.rs +++ b/crates/tui/src/main.rs @@ -2438,16 +2438,8 @@ fn known_deepseek_base_url_kind(base_url: &str) -> Option { } } -fn recommended_strict_base_url(config: &Config, base_url: &str) -> &'static str { - if matches!( - config.api_provider(), - crate::config::ApiProvider::DeepseekCN - ) || base_url.to_ascii_lowercase().contains("api.deepseeki.com") - { - "https://api.deepseeki.com/beta" - } else { - crate::config::DEFAULT_DEEPSEEK_BASE_URL - } +fn recommended_strict_base_url(_config: &Config, _base_url: &str) -> &'static str { + crate::config::DEFAULT_DEEPSEEK_BASE_URL } fn doctor_timeout_recovery_lines(config: &Config) -> Vec { @@ -2463,7 +2455,7 @@ fn doctor_timeout_recovery_lines(config: &Config) -> Vec { && !target.base_url.contains("api.deepseeki.com") => { lines.push( - "If you are in mainland China, set `provider = \"deepseek-cn\"` or `base_url = \"https://api.deepseeki.com\"` in ~/.deepseek/config.toml, then rerun `deepseek doctor`." + "If you are in mainland China, set `provider = \"deepseek-cn\"` or `base_url = \"https://api.deepseek.com\"` in ~/.deepseek/config.toml, then rerun `deepseek doctor`." .to_string(), ); } @@ -4456,7 +4448,7 @@ mod doctor_endpoint_tests { assert!(!status.function_strict_sent); assert_eq!( status.recommended_base_url.as_deref(), - Some("https://api.deepseeki.com/beta") + Some(crate::config::DEFAULT_DEEPSEEK_BASE_URL) ); } @@ -4516,7 +4508,7 @@ mod doctor_endpoint_tests { let text = doctor_timeout_recovery_lines(&config).join("\n"); - assert!(text.contains("api.deepseeki.com")); + assert!(text.contains("api.deepseek.com")); assert!(text.contains("provider = \"deepseek-cn\"")); assert!(text.contains("deepseek doctor --json")); } diff --git a/crates/tui/src/tui/app.rs b/crates/tui/src/tui/app.rs index 86f82c3e..4ba08a8d 100644 --- a/crates/tui/src/tui/app.rs +++ b/crates/tui/src/tui/app.rs @@ -1119,7 +1119,7 @@ impl App { } = options; // If no provider is explicitly configured AND the system locale - // indicates Chinese (zh-*), suggest DeepseekCN (api.deepseeki.com) + // indicates Chinese (zh-*), suggest DeepseekCN (official api.deepseek.com preset) // as the appropriate default. let provider = if config.provider.is_none() && is_chinese_system_locale() { let cn_base_url = crate::config::DEFAULT_DEEPSEEKCN_BASE_URL.to_string(); diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index 10660fa9..cc8d9c04 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -356,9 +356,9 @@ If you are upgrading from older releases: ### Core keys (used by the TUI/engine) -- `provider` (string, optional): `deepseek` (default), `deepseek-cn`, `nvidia-nim`, `openai`, `openrouter`, `novita`, `fireworks`, `sglang`, `vllm`, or `ollama`. `deepseek-cn` uses DeepSeek's mainland China endpoint (`https://api.deepseeki.com`); `nvidia-nim` targets NVIDIA's NIM-hosted DeepSeek endpoints through `https://integrate.api.nvidia.com/v1`; `openai` targets a generic OpenAI-compatible endpoint, defaulting to `https://api.openai.com/v1`; `fireworks` targets `https://api.fireworks.ai/inference/v1`; `sglang` targets a self-hosted OpenAI-compatible endpoint, defaulting to `http://localhost:30000/v1`; `vllm` targets a self-hosted vLLM OpenAI-compatible endpoint, defaulting to `http://localhost:8000/v1`; `ollama` targets Ollama's OpenAI-compatible endpoint, defaulting to `http://localhost:11434/v1`. +- `provider` (string, optional): `deepseek` (default), `deepseek-cn`, `nvidia-nim`, `openai`, `openrouter`, `novita`, `fireworks`, `sglang`, `vllm`, or `ollama`. `deepseek-cn` presets DeepSeek Platform for mainland China with the documented host [`https://api.deepseek.com`](https://api-docs.deepseek.com/) (distinct from typo `api.deepseeki.com`, which older configs may still carry and the client accepts as a DeepSeek-compatible host); `nvidia-nim` targets NVIDIA's NIM-hosted DeepSeek endpoints through `https://integrate.api.nvidia.com/v1`; `openai` targets a generic OpenAI-compatible endpoint, defaulting to `https://api.openai.com/v1`; `fireworks` targets `https://api.fireworks.ai/inference/v1`; `sglang` targets a self-hosted OpenAI-compatible endpoint, defaulting to `http://localhost:30000/v1`; `vllm` targets a self-hosted vLLM OpenAI-compatible endpoint, defaulting to `http://localhost:8000/v1`; `ollama` targets Ollama's OpenAI-compatible endpoint, defaulting to `http://localhost:11434/v1`. - `api_key` (string, required for hosted providers): must be non-empty for DeepSeek/hosted providers (or set the provider API key env var). Self-hosted SGLang, vLLM, and Ollama can omit it. -- `base_url` (string, optional): defaults to `https://api.deepseek.com/beta` for DeepSeek's OpenAI-compatible Chat Completions API in v0.8.16, `https://api.deepseeki.com` for `provider = "deepseek-cn"`, `https://api.openai.com/v1` for `provider = "openai"`, or the provider-specific endpoint for hosted/self-hosted providers. Set `https://api.deepseek.com` or `https://api.deepseek.com/v1` explicitly to opt out of DeepSeek beta features. +- `base_url` (string, optional): defaults to `https://api.deepseek.com/beta` for DeepSeek's OpenAI-compatible Chat Completions API in v0.8.16, `https://api.deepseek.com` for `provider = "deepseek-cn"`, `https://api.openai.com/v1` for `provider = "openai"`, or the provider-specific endpoint for hosted/self-hosted providers. Set `https://api.deepseek.com` or `https://api.deepseek.com/v1` explicitly to opt out of DeepSeek beta features. - `default_text_model` (string, optional): defaults to `deepseek-v4-pro` for DeepSeek, `deepseek-ai/deepseek-v4-pro` for NVIDIA NIM, `gpt-4.1` for generic OpenAI-compatible endpoints, `accounts/fireworks/models/deepseek-v4-pro` for Fireworks, `deepseek-ai/DeepSeek-V4-Pro` for SGLang/vLLM, and `deepseek-coder:1.3b` for Ollama. Current public DeepSeek IDs are `deepseek-v4-pro` and `deepseek-v4-flash`, both with 1M context windows, 384K max output, and thinking mode enabled by default. Legacy `deepseek-chat` and `deepseek-reasoner` remain compatibility aliases for `deepseek-v4-flash` until July 24, 2026. Provider-specific mappings translate `deepseek-v4-pro` / `deepseek-v4-flash` to each provider's model ID where supported. Generic `openai` and Ollama model IDs are passed through unchanged. Use `/models` or `deepseek models` to discover live IDs from your configured endpoint. `DEEPSEEK_MODEL` overrides this for a single process. - `reasoning_effort` (string, optional): `off`, `low`, `medium`, `high`, or `max`; defaults to the configured UI tier. DeepSeek Platform receives top-level `thinking` / `reasoning_effort` fields. NVIDIA NIM receives equivalent settings through `chat_template_kwargs`. - `allow_shell` (bool, optional): defaults to `true` (sandboxed).