fix: DeepSeek-first v0.8.45 — CODEWHALE_* env aliases, remove public Kimi/Moonshot promotion
Closes #2164 (superseded).
This commit is contained in:
+15
-4
@@ -7,11 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
### Added
|
||||
|
||||
- **Kimi Code API-key setup.** `codewhale config set providers.moonshot.*`
|
||||
now writes the Moonshot/Kimi provider table, and Kimi Code API-key
|
||||
endpoints default to `kimi-for-coding` without using the Kimi CLI OAuth path.
|
||||
- **`CODEWHALE_*` env aliases.** `CODEWHALE_PROVIDER`, `CODEWHALE_MODEL`,
|
||||
and `CODEWHALE_BASE_URL` are public product-scoped aliases that take
|
||||
precedence over the legacy `DEEPSEEK_*` forms. The `DEEPSEEK_*` names
|
||||
remain accepted for back-compat. Recommended setup paths are
|
||||
`codewhale --provider <name>`, `provider = "<name>"` in
|
||||
`~/.codewhale/config.toml`, or `CODEWHALE_PROVIDER=<name>`.
|
||||
|
||||
### Changed
|
||||
|
||||
- **DeepSeek-first focus.** v0.8.45.x refocuses on delivering the
|
||||
highest-quality experience on DeepSeek first. The project's broader
|
||||
goal remains to become a strong harness for open-source and open-weight
|
||||
coding models, but additional first-class provider paths are planned
|
||||
for v0.9.0 after the core DeepSeek workflow is solid.
|
||||
|
||||
## [0.8.45] - 2026-05-25
|
||||
|
||||
|
||||
@@ -275,11 +275,12 @@ Both binaries are required. Cross-compilation and platform-specific notes: [docs
|
||||
|
||||
### Providers
|
||||
|
||||
Official DeepSeek remains the default and first-class path. v0.8.45 supports
|
||||
all 12 provider IDs in this order: `deepseek`, `nvidia-nim`, `openai`,
|
||||
`atlascloud`, `wanjie-ark`, `openrouter`, `novita`, `fireworks`, `moonshot`,
|
||||
`sglang`, `vllm`, and `ollama`. Other providers are additive, with OpenRouter
|
||||
starting from DeepSeek Pro/Flash before broader open-model catalogs are enabled.
|
||||
CodeWhale v0.8.45 focuses on delivering the highest-quality experience on
|
||||
DeepSeek first. The project's broader goal remains to become a strong harness
|
||||
for open-source and open-weight coding models — additional first-class
|
||||
provider paths are planned for v0.9.0. Backend provider infrastructure for
|
||||
other OpenAI-compatible endpoints and self-hosted runtimes is available under
|
||||
the same `--provider` flag for advanced users who need it today.
|
||||
|
||||
```bash
|
||||
# DeepSeek (default)
|
||||
@@ -314,24 +315,6 @@ codewhale --provider novita --model deepseek/deepseek-v4-pro
|
||||
codewhale auth set --provider fireworks --api-key "YOUR_FIREWORKS_API_KEY"
|
||||
codewhale --provider fireworks --model deepseek-v4-pro
|
||||
|
||||
# Kimi Code plan API key
|
||||
codewhale auth set --provider moonshot --api-key "YOUR_KIMI_CODE_API_KEY"
|
||||
codewhale config set providers.moonshot.auth_mode api_key
|
||||
codewhale config set providers.moonshot.base_url https://api.kimi.com/coding/v1
|
||||
codewhale config set providers.moonshot.model kimi-for-coding
|
||||
codewhale --provider moonshot
|
||||
|
||||
# Kimi/Moonshot Platform API key
|
||||
codewhale auth set --provider moonshot --api-key "YOUR_MOONSHOT_OR_KIMI_API_KEY"
|
||||
codewhale config set providers.moonshot.auth_mode api_key
|
||||
codewhale config set providers.moonshot.base_url https://api.moonshot.ai/v1
|
||||
codewhale config set providers.moonshot.model kimi-k2.6
|
||||
codewhale --provider moonshot
|
||||
|
||||
# Kimi through OpenRouter's catalog
|
||||
codewhale auth set --provider openrouter --api-key "YOUR_OPENROUTER_API_KEY"
|
||||
codewhale --provider openrouter --model moonshotai/kimi-k2.6
|
||||
|
||||
# Self-hosted SGLang
|
||||
SGLANG_BASE_URL="http://localhost:30000/v1" codewhale --provider sglang --model deepseek-v4-flash
|
||||
|
||||
@@ -506,21 +489,23 @@ Key environment variables:
|
||||
|
||||
| Variable | Purpose |
|
||||
|---|---|
|
||||
| `CODEWHALE_PROVIDER` | Active provider. Public alias for `DEEPSEEK_PROVIDER`; wins when both are set. |
|
||||
| `CODEWHALE_MODEL` | Default model for the active provider. Public alias for `DEEPSEEK_MODEL`. |
|
||||
| `CODEWHALE_BASE_URL` | Base URL for the active provider. Public alias for `DEEPSEEK_BASE_URL`. |
|
||||
| `DEEPSEEK_API_KEY` | API key |
|
||||
| `DEEPSEEK_BASE_URL` | API base URL |
|
||||
| `DEEPSEEK_BASE_URL` | API base URL (legacy alias of `CODEWHALE_BASE_URL`) |
|
||||
| `DEEPSEEK_HTTP_HEADERS` | Optional custom model request headers, e.g. `X-Model-Provider-Id=your-model-provider` |
|
||||
| `DEEPSEEK_MODEL` | Default model |
|
||||
| `DEEPSEEK_MODEL` | Default model (legacy alias of `CODEWHALE_MODEL`) |
|
||||
| `DEEPSEEK_STREAM_IDLE_TIMEOUT_SECS` | Stream idle timeout in seconds, default `300`, clamped to `1..=3600` |
|
||||
| `DEEPSEEK_PROVIDER` | `deepseek` (default), `nvidia-nim`, `openai`, `atlascloud`, `wanjie-ark`, `openrouter`, `novita`, `fireworks`, `moonshot`, `sglang`, `vllm`, `ollama` |
|
||||
| `DEEPSEEK_PROVIDER` | Legacy alias of `CODEWHALE_PROVIDER`. Accepts `deepseek` (default), `nvidia-nim`, `openai`, `atlascloud`, `wanjie-ark`, `openrouter`, `novita`, `fireworks`, `sglang`, `vllm`, `ollama`. |
|
||||
| `DEEPSEEK_PROFILE` | Config profile name |
|
||||
| `DEEPSEEK_MEMORY` | Set to `on` to enable user memory |
|
||||
| `DEEPSEEK_ALLOW_INSECURE_HTTP=1` | Allow non-local `http://` API base URLs on trusted networks |
|
||||
| `NVIDIA_API_KEY` / `NVIDIA_NIM_API_KEY` / `OPENAI_API_KEY` / `ATLASCLOUD_API_KEY` / `WANJIE_ARK_API_KEY` / `WANJIE_API_KEY` / `WANJIE_MAAS_API_KEY` / `OPENROUTER_API_KEY` / `NOVITA_API_KEY` / `FIREWORKS_API_KEY` / `MOONSHOT_API_KEY` / `KIMI_API_KEY` / `SGLANG_API_KEY` / `VLLM_API_KEY` / `OLLAMA_API_KEY` | Provider auth |
|
||||
| `NVIDIA_API_KEY` / `NVIDIA_NIM_API_KEY` / `OPENAI_API_KEY` / `ATLASCLOUD_API_KEY` / `WANJIE_ARK_API_KEY` / `WANJIE_API_KEY` / `WANJIE_MAAS_API_KEY` / `OPENROUTER_API_KEY` / `NOVITA_API_KEY` / `FIREWORKS_API_KEY` / `SGLANG_API_KEY` / `VLLM_API_KEY` / `OLLAMA_API_KEY` | Provider auth |
|
||||
| `NVIDIA_NIM_BASE_URL` / `NIM_BASE_URL` / `NVIDIA_BASE_URL` | NVIDIA NIM endpoint override |
|
||||
| `OPENAI_BASE_URL` / `OPENAI_MODEL` | Generic OpenAI-compatible endpoint and model ID |
|
||||
| `ATLASCLOUD_BASE_URL` / `ATLASCLOUD_MODEL` | AtlasCloud endpoint and model override |
|
||||
| `WANJIE_ARK_BASE_URL` / `WANJIE_BASE_URL` / `WANJIE_MAAS_BASE_URL` / `WANJIE_ARK_MODEL` / `WANJIE_MODEL` / `WANJIE_MAAS_MODEL` | Wanjie Ark endpoint and model override |
|
||||
| `MOONSHOT_BASE_URL` / `KIMI_BASE_URL` / `MOONSHOT_MODEL` / `KIMI_MODEL_NAME` / `KIMI_MODEL` | Moonshot/Kimi endpoint and model override. For a Kimi Code plan API key, use `KIMI_BASE_URL=https://api.kimi.com/coding/v1` and `KIMI_MODEL=kimi-for-coding`. |
|
||||
| `OPENROUTER_BASE_URL` | OpenRouter endpoint override |
|
||||
| `NOVITA_BASE_URL` | Novita endpoint override |
|
||||
| `FIREWORKS_BASE_URL` | Fireworks endpoint override |
|
||||
|
||||
+180
-3
@@ -1787,10 +1787,15 @@ struct EnvRuntimeOverrides {
|
||||
impl EnvRuntimeOverrides {
|
||||
fn load() -> Self {
|
||||
Self {
|
||||
provider: std::env::var("DEEPSEEK_PROVIDER")
|
||||
provider: std::env::var("CODEWHALE_PROVIDER")
|
||||
.or_else(|_| std::env::var("DEEPSEEK_PROVIDER"))
|
||||
.ok()
|
||||
.and_then(|v| ProviderKind::parse(&v)),
|
||||
model: std::env::var("DEEPSEEK_MODEL").ok(),
|
||||
model: std::env::var("CODEWHALE_MODEL")
|
||||
.or_else(|_| std::env::var("DEEPSEEK_MODEL"))
|
||||
.or_else(|_| std::env::var("DEEPSEEK_DEFAULT_TEXT_MODEL"))
|
||||
.ok()
|
||||
.filter(|v| !v.trim().is_empty()),
|
||||
wanjie_ark_model: std::env::var("WANJIE_ARK_MODEL")
|
||||
.or_else(|_| std::env::var("WANJIE_MODEL"))
|
||||
.or_else(|_| std::env::var("WANJIE_MAAS_MODEL"))
|
||||
@@ -1816,7 +1821,8 @@ impl EnvRuntimeOverrides {
|
||||
.ok()
|
||||
.and_then(|value| parse_http_headers(&value).ok())
|
||||
.filter(|headers| !headers.is_empty()),
|
||||
deepseek_base_url: std::env::var("DEEPSEEK_BASE_URL")
|
||||
deepseek_base_url: std::env::var("CODEWHALE_BASE_URL")
|
||||
.or_else(|_| std::env::var("DEEPSEEK_BASE_URL"))
|
||||
.ok()
|
||||
.filter(|v| !v.trim().is_empty()),
|
||||
nvidia_base_url: std::env::var("NVIDIA_NIM_BASE_URL")
|
||||
@@ -1921,6 +1927,7 @@ mod tests {
|
||||
deepseek_base_url: Option<OsString>,
|
||||
deepseek_http_headers: Option<OsString>,
|
||||
deepseek_model: Option<OsString>,
|
||||
deepseek_default_text_model: Option<OsString>,
|
||||
deepseek_provider: Option<OsString>,
|
||||
deepseek_auth_mode: Option<OsString>,
|
||||
nvidia_api_key: Option<OsString>,
|
||||
@@ -1954,6 +1961,9 @@ mod tests {
|
||||
vllm_base_url: Option<OsString>,
|
||||
ollama_api_key: Option<OsString>,
|
||||
ollama_base_url: Option<OsString>,
|
||||
codewhale_provider: Option<OsString>,
|
||||
codewhale_model: Option<OsString>,
|
||||
codewhale_base_url: Option<OsString>,
|
||||
}
|
||||
|
||||
impl EnvGuard {
|
||||
@@ -1963,8 +1973,12 @@ mod tests {
|
||||
deepseek_base_url: env::var_os("DEEPSEEK_BASE_URL"),
|
||||
deepseek_http_headers: env::var_os("DEEPSEEK_HTTP_HEADERS"),
|
||||
deepseek_model: env::var_os("DEEPSEEK_MODEL"),
|
||||
deepseek_default_text_model: env::var_os("DEEPSEEK_DEFAULT_TEXT_MODEL"),
|
||||
deepseek_provider: env::var_os("DEEPSEEK_PROVIDER"),
|
||||
deepseek_auth_mode: env::var_os("DEEPSEEK_AUTH_MODE"),
|
||||
codewhale_provider: env::var_os("CODEWHALE_PROVIDER"),
|
||||
codewhale_model: env::var_os("CODEWHALE_MODEL"),
|
||||
codewhale_base_url: env::var_os("CODEWHALE_BASE_URL"),
|
||||
nvidia_api_key: env::var_os("NVIDIA_API_KEY"),
|
||||
nvidia_nim_api_key: env::var_os("NVIDIA_NIM_API_KEY"),
|
||||
nim_base_url: env::var_os("NIM_BASE_URL"),
|
||||
@@ -2003,8 +2017,12 @@ mod tests {
|
||||
env::remove_var("DEEPSEEK_BASE_URL");
|
||||
env::remove_var("DEEPSEEK_HTTP_HEADERS");
|
||||
env::remove_var("DEEPSEEK_MODEL");
|
||||
env::remove_var("DEEPSEEK_DEFAULT_TEXT_MODEL");
|
||||
env::remove_var("DEEPSEEK_PROVIDER");
|
||||
env::remove_var("DEEPSEEK_AUTH_MODE");
|
||||
env::remove_var("CODEWHALE_PROVIDER");
|
||||
env::remove_var("CODEWHALE_MODEL");
|
||||
env::remove_var("CODEWHALE_BASE_URL");
|
||||
env::remove_var("NVIDIA_API_KEY");
|
||||
env::remove_var("NVIDIA_NIM_API_KEY");
|
||||
env::remove_var("NIM_BASE_URL");
|
||||
@@ -2057,8 +2075,15 @@ mod tests {
|
||||
Self::restore_var("DEEPSEEK_BASE_URL", self.deepseek_base_url.take());
|
||||
Self::restore_var("DEEPSEEK_HTTP_HEADERS", self.deepseek_http_headers.take());
|
||||
Self::restore_var("DEEPSEEK_MODEL", self.deepseek_model.take());
|
||||
Self::restore_var(
|
||||
"DEEPSEEK_DEFAULT_TEXT_MODEL",
|
||||
self.deepseek_default_text_model.take(),
|
||||
);
|
||||
Self::restore_var("DEEPSEEK_PROVIDER", self.deepseek_provider.take());
|
||||
Self::restore_var("DEEPSEEK_AUTH_MODE", self.deepseek_auth_mode.take());
|
||||
Self::restore_var("CODEWHALE_PROVIDER", self.codewhale_provider.take());
|
||||
Self::restore_var("CODEWHALE_MODEL", self.codewhale_model.take());
|
||||
Self::restore_var("CODEWHALE_BASE_URL", self.codewhale_base_url.take());
|
||||
Self::restore_var("NVIDIA_API_KEY", self.nvidia_api_key.take());
|
||||
Self::restore_var("NVIDIA_NIM_API_KEY", self.nvidia_nim_api_key.take());
|
||||
Self::restore_var("NIM_BASE_URL", self.nim_base_url.take());
|
||||
@@ -2408,6 +2433,55 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// End-to-end smoke for the preferred Kimi Code setup path:
|
||||
/// 1. Start from a fresh root config that uses DeepSeek defaults.
|
||||
/// 2. Mutate it through the same key-value setters the
|
||||
/// `codewhale config set providers.moonshot.*` CLI invokes.
|
||||
/// 3. Switch the active provider through `CODEWHALE_PROVIDER` —
|
||||
/// the public env alias — without ever touching the legacy
|
||||
/// `DEEPSEEK_PROVIDER` name.
|
||||
/// 4. Resolve the runtime and confirm the doctor/runtime values.
|
||||
///
|
||||
/// No real API key is required; the `api_key` here is just a
|
||||
/// non-empty placeholder.
|
||||
#[test]
|
||||
fn moonshot_kimi_code_smoke_config_set_then_resolve() -> Result<()> {
|
||||
let _lock = env_lock();
|
||||
let _env = EnvGuard::without_deepseek_runtime_overrides();
|
||||
|
||||
let mut config = ConfigToml {
|
||||
provider: ProviderKind::Deepseek,
|
||||
default_text_model: Some("deepseek-v4-pro".to_string()),
|
||||
..ConfigToml::default()
|
||||
};
|
||||
|
||||
// Same key paths a user would run via `codewhale config set`.
|
||||
config.set_value("providers.moonshot.api_key", "kimi-code-key-placeholder")?;
|
||||
config.set_value("providers.moonshot.auth_mode", "api_key")?;
|
||||
config.set_value("providers.moonshot.base_url", DEFAULT_KIMI_CODE_BASE_URL)?;
|
||||
config.set_value("providers.moonshot.model", DEFAULT_KIMI_CODE_MODEL)?;
|
||||
|
||||
// Public env alias for the active-provider switch.
|
||||
// Safety: test-only env mutation guarded by env_lock().
|
||||
unsafe { env::set_var("CODEWHALE_PROVIDER", "moonshot") };
|
||||
|
||||
let resolved = config.resolve_runtime_options(&CliRuntimeOverrides::default());
|
||||
|
||||
assert_eq!(resolved.provider, ProviderKind::Moonshot);
|
||||
assert_eq!(resolved.base_url, DEFAULT_KIMI_CODE_BASE_URL);
|
||||
assert_eq!(resolved.model, DEFAULT_KIMI_CODE_MODEL);
|
||||
assert_eq!(resolved.auth_mode.as_deref(), Some("api_key"));
|
||||
assert_eq!(
|
||||
resolved.api_key.as_deref(),
|
||||
Some("kimi-code-key-placeholder")
|
||||
);
|
||||
assert_eq!(
|
||||
resolved.api_key_source,
|
||||
Some(RuntimeApiKeySource::ConfigFile)
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn moonshot_provider_config_values_round_trip() -> Result<()> {
|
||||
let mut config = ConfigToml::default();
|
||||
@@ -2757,6 +2831,109 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// `CODEWHALE_PROVIDER` is the user-facing env alias for switching the
|
||||
/// active provider. It must be honored by the runtime resolver and win
|
||||
/// over a root `provider = "deepseek"` config entry.
|
||||
#[test]
|
||||
fn codewhale_provider_env_switches_active_provider() {
|
||||
let _lock = env_lock();
|
||||
let _env = EnvGuard::without_deepseek_runtime_overrides();
|
||||
// Safety: test-only env mutation guarded by env_lock().
|
||||
unsafe {
|
||||
env::set_var("CODEWHALE_PROVIDER", "moonshot");
|
||||
}
|
||||
let mut config = ConfigToml {
|
||||
provider: ProviderKind::Deepseek,
|
||||
..ConfigToml::default()
|
||||
};
|
||||
config.providers.moonshot.api_key = Some("kimi-code-key".to_string());
|
||||
config.providers.moonshot.base_url = Some(DEFAULT_KIMI_CODE_BASE_URL.to_string());
|
||||
|
||||
let resolved = config.resolve_runtime_options(&CliRuntimeOverrides::default());
|
||||
|
||||
assert_eq!(resolved.provider, ProviderKind::Moonshot);
|
||||
assert_eq!(resolved.base_url, DEFAULT_KIMI_CODE_BASE_URL);
|
||||
assert_eq!(resolved.model, DEFAULT_KIMI_CODE_MODEL);
|
||||
assert_eq!(resolved.api_key.as_deref(), Some("kimi-code-key"));
|
||||
}
|
||||
|
||||
/// When both `CODEWHALE_PROVIDER` and the legacy `DEEPSEEK_PROVIDER`
|
||||
/// are set, the public alias wins — a user adopting `CODEWHALE_*` in a
|
||||
/// fresh shell config is not tripped up by a stale legacy export still
|
||||
/// living in their dotfiles.
|
||||
#[test]
|
||||
fn codewhale_provider_env_wins_over_deepseek_provider_env() {
|
||||
let _lock = env_lock();
|
||||
let _env = EnvGuard::without_deepseek_runtime_overrides();
|
||||
// Safety: test-only env mutation guarded by env_lock().
|
||||
unsafe {
|
||||
env::set_var("CODEWHALE_PROVIDER", "moonshot");
|
||||
env::set_var("DEEPSEEK_PROVIDER", "openrouter");
|
||||
}
|
||||
let config = ConfigToml {
|
||||
provider: ProviderKind::Deepseek,
|
||||
..ConfigToml::default()
|
||||
};
|
||||
|
||||
let resolved = config.resolve_runtime_options(&CliRuntimeOverrides::default());
|
||||
|
||||
assert_eq!(resolved.provider, ProviderKind::Moonshot);
|
||||
}
|
||||
|
||||
/// `CODEWHALE_MODEL` is the user-facing env alias for picking a model
|
||||
/// against the active provider. It must be honored by the runtime
|
||||
/// resolver in place of `DEEPSEEK_MODEL`.
|
||||
#[test]
|
||||
fn codewhale_model_env_alias_overrides_default_for_active_provider() {
|
||||
let _lock = env_lock();
|
||||
let _env = EnvGuard::without_deepseek_runtime_overrides();
|
||||
// Safety: test-only env mutation guarded by env_lock().
|
||||
unsafe {
|
||||
env::set_var("CODEWHALE_PROVIDER", "moonshot");
|
||||
env::set_var("CODEWHALE_MODEL", "custom-kimi-test-model");
|
||||
}
|
||||
let config = ConfigToml::default();
|
||||
|
||||
let resolved = config.resolve_runtime_options(&CliRuntimeOverrides::default());
|
||||
|
||||
assert_eq!(resolved.provider, ProviderKind::Moonshot);
|
||||
assert_eq!(resolved.model, "custom-kimi-test-model");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blank_codewhale_model_env_alias_does_not_override_default_for_active_provider() {
|
||||
let _lock = env_lock();
|
||||
let _env = EnvGuard::without_deepseek_runtime_overrides();
|
||||
// Safety: test-only env mutation guarded by env_lock().
|
||||
unsafe {
|
||||
env::set_var("CODEWHALE_PROVIDER", "moonshot");
|
||||
env::set_var("CODEWHALE_MODEL", " ");
|
||||
}
|
||||
let config = ConfigToml::default();
|
||||
|
||||
let resolved = config.resolve_runtime_options(&CliRuntimeOverrides::default());
|
||||
|
||||
assert_eq!(resolved.provider, ProviderKind::Moonshot);
|
||||
assert_eq!(resolved.model, DEFAULT_MOONSHOT_MODEL);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deepseek_default_text_model_legacy_alias_still_overrides_active_provider_model() {
|
||||
let _lock = env_lock();
|
||||
let _env = EnvGuard::without_deepseek_runtime_overrides();
|
||||
// Safety: test-only env mutation guarded by env_lock().
|
||||
unsafe {
|
||||
env::set_var("CODEWHALE_PROVIDER", "moonshot");
|
||||
env::set_var("DEEPSEEK_DEFAULT_TEXT_MODEL", "legacy-env-model");
|
||||
}
|
||||
let config = ConfigToml::default();
|
||||
|
||||
let resolved = config.resolve_runtime_options(&CliRuntimeOverrides::default());
|
||||
|
||||
assert_eq!(resolved.provider, ProviderKind::Moonshot);
|
||||
assert_eq!(resolved.model, "legacy-env-model");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wanjie_ark_provider_defaults_to_openai_compatible_endpoint_and_model() {
|
||||
let _lock = env_lock();
|
||||
|
||||
+15
-4
@@ -7,11 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
### Added
|
||||
|
||||
- **Kimi Code API-key setup.** `codewhale config set providers.moonshot.*`
|
||||
now writes the Moonshot/Kimi provider table, and Kimi Code API-key
|
||||
endpoints default to `kimi-for-coding` without using the Kimi CLI OAuth path.
|
||||
- **`CODEWHALE_*` env aliases.** `CODEWHALE_PROVIDER`, `CODEWHALE_MODEL`,
|
||||
and `CODEWHALE_BASE_URL` are public product-scoped aliases that take
|
||||
precedence over the legacy `DEEPSEEK_*` forms. The `DEEPSEEK_*` names
|
||||
remain accepted for back-compat. Recommended setup paths are
|
||||
`codewhale --provider <name>`, `provider = "<name>"` in
|
||||
`~/.codewhale/config.toml`, or `CODEWHALE_PROVIDER=<name>`.
|
||||
|
||||
### Changed
|
||||
|
||||
- **DeepSeek-first focus.** v0.8.45.x refocuses on delivering the
|
||||
highest-quality experience on DeepSeek first. The project's broader
|
||||
goal remains to become a strong harness for open-source and open-weight
|
||||
coding models, but additional first-class provider paths are planned
|
||||
for v0.9.0 after the core DeepSeek workflow is solid.
|
||||
|
||||
## [0.8.45] - 2026-05-25
|
||||
|
||||
|
||||
+252
-17
@@ -1554,6 +1554,19 @@ impl Config {
|
||||
}
|
||||
}
|
||||
}
|
||||
let moonshot_config = (provider == ApiProvider::Moonshot)
|
||||
.then(|| self.provider_config())
|
||||
.flatten();
|
||||
let moonshot_uses_kimi_code = moonshot_config.is_some_and(|config| {
|
||||
provider_config_uses_kimi_oauth(config)
|
||||
|| config
|
||||
.base_url
|
||||
.as_deref()
|
||||
.is_some_and(moonshot_base_url_uses_kimi_code)
|
||||
});
|
||||
if moonshot_uses_kimi_code {
|
||||
return DEFAULT_KIMI_CODE_MODEL.to_string();
|
||||
}
|
||||
if let Some(model) = self.default_text_model.as_deref()
|
||||
&& (provider_passes_model_through(provider)
|
||||
|| self.active_provider_preserves_custom_base_url_model())
|
||||
@@ -1570,19 +1583,6 @@ impl Config {
|
||||
{
|
||||
return model_for_provider(provider, normalized);
|
||||
}
|
||||
let moonshot_config = (provider == ApiProvider::Moonshot)
|
||||
.then(|| self.provider_config())
|
||||
.flatten();
|
||||
let moonshot_uses_kimi_code = moonshot_config.is_some_and(|config| {
|
||||
provider_config_uses_kimi_oauth(config)
|
||||
|| config
|
||||
.base_url
|
||||
.as_deref()
|
||||
.is_some_and(moonshot_base_url_uses_kimi_code)
|
||||
});
|
||||
if moonshot_uses_kimi_code {
|
||||
return DEFAULT_KIMI_CODE_MODEL.to_string();
|
||||
}
|
||||
|
||||
match provider {
|
||||
ApiProvider::Deepseek | ApiProvider::DeepseekCN => DEFAULT_TEXT_MODEL,
|
||||
@@ -2271,11 +2271,29 @@ fn default_memory_path() -> Option<PathBuf> {
|
||||
|
||||
// === Environment Overrides ===
|
||||
|
||||
/// Read a CodeWhale env var, preferring the `CODEWHALE_*` form over the
|
||||
/// legacy `DEEPSEEK_*` form. Empty values are ignored so a blank shell export
|
||||
/// does not erase configured provider settings.
|
||||
fn codewhale_env_var(
|
||||
codewhale_name: &str,
|
||||
legacy_name: &str,
|
||||
) -> Result<String, std::env::VarError> {
|
||||
std::env::var(codewhale_name)
|
||||
.ok()
|
||||
.filter(|value| !value.trim().is_empty())
|
||||
.or_else(|| {
|
||||
std::env::var(legacy_name)
|
||||
.ok()
|
||||
.filter(|value| !value.trim().is_empty())
|
||||
})
|
||||
.ok_or(std::env::VarError::NotPresent)
|
||||
}
|
||||
|
||||
fn apply_env_overrides(config: &mut Config) {
|
||||
if let Ok(value) = std::env::var("DEEPSEEK_PROVIDER") {
|
||||
if let Ok(value) = codewhale_env_var("CODEWHALE_PROVIDER", "DEEPSEEK_PROVIDER") {
|
||||
config.provider = Some(value);
|
||||
}
|
||||
if let Ok(value) = std::env::var("DEEPSEEK_BASE_URL") {
|
||||
if let Ok(value) = codewhale_env_var("CODEWHALE_BASE_URL", "DEEPSEEK_BASE_URL") {
|
||||
match config.api_provider() {
|
||||
ApiProvider::Deepseek | ApiProvider::DeepseekCN => {
|
||||
config.base_url = Some(value);
|
||||
@@ -2558,8 +2576,13 @@ fn apply_env_overrides(config: &mut Config) {
|
||||
.moonshot
|
||||
.model = Some(value);
|
||||
}
|
||||
if let Ok(value) =
|
||||
std::env::var("DEEPSEEK_MODEL").or_else(|_| std::env::var("DEEPSEEK_DEFAULT_TEXT_MODEL"))
|
||||
if let Some(value) = codewhale_env_var("CODEWHALE_MODEL", "DEEPSEEK_MODEL")
|
||||
.ok()
|
||||
.or_else(|| {
|
||||
std::env::var("DEEPSEEK_DEFAULT_TEXT_MODEL")
|
||||
.ok()
|
||||
.filter(|value| !value.trim().is_empty())
|
||||
})
|
||||
{
|
||||
// The CLI `--model` handoff always sets DEEPSEEK_MODEL, never the
|
||||
// provider-specific *_MODEL var. The legacy root `default_text_model`
|
||||
@@ -4075,6 +4098,9 @@ mod tests {
|
||||
deepseek_http_headers: Option<OsString>,
|
||||
deepseek_model: Option<OsString>,
|
||||
deepseek_default_text_model: Option<OsString>,
|
||||
codewhale_provider: Option<OsString>,
|
||||
codewhale_model: Option<OsString>,
|
||||
codewhale_base_url: Option<OsString>,
|
||||
nvidia_api_key: Option<OsString>,
|
||||
nvidia_nim_api_key: Option<OsString>,
|
||||
nim_base_url: Option<OsString>,
|
||||
@@ -4137,6 +4163,9 @@ mod tests {
|
||||
let http_headers_prev = env::var_os("DEEPSEEK_HTTP_HEADERS");
|
||||
let model_prev = env::var_os("DEEPSEEK_MODEL");
|
||||
let default_text_model_prev = env::var_os("DEEPSEEK_DEFAULT_TEXT_MODEL");
|
||||
let codewhale_provider_prev = env::var_os("CODEWHALE_PROVIDER");
|
||||
let codewhale_model_prev = env::var_os("CODEWHALE_MODEL");
|
||||
let codewhale_base_url_prev = env::var_os("CODEWHALE_BASE_URL");
|
||||
let nvidia_api_key_prev = env::var_os("NVIDIA_API_KEY");
|
||||
let nvidia_nim_api_key_prev = env::var_os("NVIDIA_NIM_API_KEY");
|
||||
let nim_base_url_prev = env::var_os("NIM_BASE_URL");
|
||||
@@ -4194,6 +4223,9 @@ mod tests {
|
||||
env::remove_var("DEEPSEEK_HTTP_HEADERS");
|
||||
env::remove_var("DEEPSEEK_MODEL");
|
||||
env::remove_var("DEEPSEEK_DEFAULT_TEXT_MODEL");
|
||||
env::remove_var("CODEWHALE_PROVIDER");
|
||||
env::remove_var("CODEWHALE_MODEL");
|
||||
env::remove_var("CODEWHALE_BASE_URL");
|
||||
env::remove_var("NVIDIA_API_KEY");
|
||||
env::remove_var("NVIDIA_NIM_API_KEY");
|
||||
env::remove_var("NIM_BASE_URL");
|
||||
@@ -4251,6 +4283,9 @@ mod tests {
|
||||
deepseek_http_headers: http_headers_prev,
|
||||
deepseek_model: model_prev,
|
||||
deepseek_default_text_model: default_text_model_prev,
|
||||
codewhale_provider: codewhale_provider_prev,
|
||||
codewhale_model: codewhale_model_prev,
|
||||
codewhale_base_url: codewhale_base_url_prev,
|
||||
nvidia_api_key: nvidia_api_key_prev,
|
||||
nvidia_nim_api_key: nvidia_nim_api_key_prev,
|
||||
nim_base_url: nim_base_url_prev,
|
||||
@@ -4317,6 +4352,9 @@ mod tests {
|
||||
"DEEPSEEK_DEFAULT_TEXT_MODEL",
|
||||
self.deepseek_default_text_model.take(),
|
||||
);
|
||||
Self::restore_var("CODEWHALE_PROVIDER", self.codewhale_provider.take());
|
||||
Self::restore_var("CODEWHALE_MODEL", self.codewhale_model.take());
|
||||
Self::restore_var("CODEWHALE_BASE_URL", self.codewhale_base_url.take());
|
||||
Self::restore_var("NVIDIA_API_KEY", self.nvidia_api_key.take());
|
||||
Self::restore_var("NVIDIA_NIM_API_KEY", self.nvidia_nim_api_key.take());
|
||||
Self::restore_var("NIM_BASE_URL", self.nim_base_url.take());
|
||||
@@ -6484,6 +6522,203 @@ base_url = "https://api.kimi.com/coding/v1"
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Env-var-only path: `CODEWHALE_BASE_URL=https://api.kimi.com/coding/v1`
|
||||
/// combined with `CODEWHALE_PROVIDER=moonshot` must trigger Kimi Code
|
||||
/// model selection even when the TOML has no `base_url`.
|
||||
#[test]
|
||||
fn moonshot_kimi_code_env_base_url_selects_coding_model() -> Result<()> {
|
||||
let _lock = lock_test_env();
|
||||
let nanos = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_nanos();
|
||||
let temp_root = env::temp_dir().join(format!(
|
||||
"codewhale-tui-kimi-code-env-url-{}-{}",
|
||||
std::process::id(),
|
||||
nanos
|
||||
));
|
||||
fs::create_dir_all(&temp_root)?;
|
||||
let _guard = EnvGuard::new(&temp_root);
|
||||
|
||||
let config_path = temp_root.join(".deepseek").join("config.toml");
|
||||
ensure_parent_dir(&config_path)?;
|
||||
fs::write(
|
||||
&config_path,
|
||||
r#"[providers.moonshot]
|
||||
api_key = "kimi-code-env-key"
|
||||
"#,
|
||||
)?;
|
||||
// Safety: test-only env mutation guarded by lock_test_env().
|
||||
unsafe {
|
||||
env::set_var("CODEWHALE_PROVIDER", "moonshot");
|
||||
env::set_var("CODEWHALE_BASE_URL", "https://api.kimi.com/coding/v1");
|
||||
}
|
||||
|
||||
let config = Config::load(None, None)?;
|
||||
assert_eq!(config.api_provider(), ApiProvider::Moonshot);
|
||||
assert_eq!(config.deepseek_base_url(), DEFAULT_KIMI_CODE_BASE_URL);
|
||||
assert_eq!(config.default_model(), DEFAULT_KIMI_CODE_MODEL);
|
||||
assert_eq!(config.deepseek_api_key()?, "kimi-code-env-key");
|
||||
assert!(has_api_key_for(&config, ApiProvider::Moonshot));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Regression for issue #2160: a stale root `default_text_model` carried
|
||||
/// over from a DeepSeek setup must not steer the Kimi Code endpoint to
|
||||
/// `deepseek-v4-pro`. The user-facing trigger here is the legacy
|
||||
/// `DEEPSEEK_PROVIDER` env var (still produced by the `codewhale
|
||||
/// --provider moonshot` dispatcher for compat); the test also has a
|
||||
/// `CODEWHALE_PROVIDER` twin below for the public env path.
|
||||
#[test]
|
||||
fn moonshot_kimi_code_model_overrides_root_deepseek_default() -> Result<()> {
|
||||
let _lock = lock_test_env();
|
||||
let nanos = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_nanos();
|
||||
let temp_root = env::temp_dir().join(format!(
|
||||
"codewhale-tui-kimi-code-root-model-{}-{}",
|
||||
std::process::id(),
|
||||
nanos
|
||||
));
|
||||
fs::create_dir_all(&temp_root)?;
|
||||
let _guard = EnvGuard::new(&temp_root);
|
||||
|
||||
let config_path = temp_root.join(".deepseek").join("config.toml");
|
||||
ensure_parent_dir(&config_path)?;
|
||||
fs::write(
|
||||
&config_path,
|
||||
r#"provider = "deepseek"
|
||||
default_text_model = "deepseek-v4-pro"
|
||||
|
||||
[providers.moonshot]
|
||||
api_key = "kimi-code-key"
|
||||
base_url = "https://api.kimi.com/coding/v1"
|
||||
"#,
|
||||
)?;
|
||||
// Safety: test-only env mutation guarded by lock_test_env().
|
||||
unsafe { env::set_var("DEEPSEEK_PROVIDER", "moonshot") };
|
||||
|
||||
let config = Config::load(None, None)?;
|
||||
assert_eq!(config.api_provider(), ApiProvider::Moonshot);
|
||||
assert_eq!(config.deepseek_base_url(), DEFAULT_KIMI_CODE_BASE_URL);
|
||||
assert_eq!(config.default_model(), DEFAULT_KIMI_CODE_MODEL);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Same regression as above, but driven by the public `CODEWHALE_PROVIDER`
|
||||
/// env var. Documents the recommended user-facing setup path: never
|
||||
/// `DEEPSEEK_PROVIDER=moonshot`, always `CODEWHALE_PROVIDER=moonshot`
|
||||
/// (or `codewhale --provider moonshot`, which also resolves through
|
||||
/// this code path internally).
|
||||
#[test]
|
||||
fn moonshot_kimi_code_model_resolves_via_codewhale_provider_env() -> Result<()> {
|
||||
let _lock = lock_test_env();
|
||||
let nanos = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_nanos();
|
||||
let temp_root = env::temp_dir().join(format!(
|
||||
"codewhale-tui-kimi-code-cw-env-{}-{}",
|
||||
std::process::id(),
|
||||
nanos
|
||||
));
|
||||
fs::create_dir_all(&temp_root)?;
|
||||
let _guard = EnvGuard::new(&temp_root);
|
||||
|
||||
let config_path = temp_root.join(".deepseek").join("config.toml");
|
||||
ensure_parent_dir(&config_path)?;
|
||||
fs::write(
|
||||
&config_path,
|
||||
r#"provider = "deepseek"
|
||||
default_text_model = "deepseek-v4-pro"
|
||||
|
||||
[providers.moonshot]
|
||||
api_key = "kimi-code-key"
|
||||
base_url = "https://api.kimi.com/coding/v1"
|
||||
"#,
|
||||
)?;
|
||||
// Safety: test-only env mutation guarded by lock_test_env().
|
||||
unsafe { env::set_var("CODEWHALE_PROVIDER", "moonshot") };
|
||||
|
||||
let config = Config::load(None, None)?;
|
||||
assert_eq!(config.api_provider(), ApiProvider::Moonshot);
|
||||
assert_eq!(config.deepseek_base_url(), DEFAULT_KIMI_CODE_BASE_URL);
|
||||
assert_eq!(config.default_model(), DEFAULT_KIMI_CODE_MODEL);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// `CODEWHALE_PROVIDER` wins when both it and the legacy
|
||||
/// `DEEPSEEK_PROVIDER` are set, so a user adding the new alias to their
|
||||
/// shell isn't surprised by a stale legacy export.
|
||||
#[test]
|
||||
fn codewhale_provider_env_takes_precedence_over_deepseek_provider() -> Result<()> {
|
||||
let _lock = lock_test_env();
|
||||
let nanos = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_nanos();
|
||||
let temp_root = env::temp_dir().join(format!(
|
||||
"codewhale-tui-cw-vs-ds-provider-{}-{}",
|
||||
std::process::id(),
|
||||
nanos
|
||||
));
|
||||
fs::create_dir_all(&temp_root)?;
|
||||
let _guard = EnvGuard::new(&temp_root);
|
||||
|
||||
let config_path = temp_root.join(".deepseek").join("config.toml");
|
||||
ensure_parent_dir(&config_path)?;
|
||||
fs::write(&config_path, "provider = \"deepseek\"\n")?;
|
||||
// Safety: test-only env mutation guarded by lock_test_env().
|
||||
unsafe {
|
||||
env::set_var("CODEWHALE_PROVIDER", "moonshot");
|
||||
env::set_var("DEEPSEEK_PROVIDER", "openrouter");
|
||||
}
|
||||
|
||||
let config = Config::load(None, None)?;
|
||||
assert_eq!(config.api_provider(), ApiProvider::Moonshot);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Moonshot Platform path: when [providers.moonshot] is empty (or
|
||||
/// missing) and no Kimi Code endpoint is configured, the resolver
|
||||
/// defaults to the Moonshot Platform base URL and the `kimi-k2.6`
|
||||
/// model. This is the "I have a Moonshot Platform API key, not a
|
||||
/// Kimi Code plan key" path.
|
||||
#[test]
|
||||
fn moonshot_platform_defaults_to_kimi_k26() -> Result<()> {
|
||||
let _lock = lock_test_env();
|
||||
let nanos = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_nanos();
|
||||
let temp_root = env::temp_dir().join(format!(
|
||||
"codewhale-tui-moonshot-platform-{}-{}",
|
||||
std::process::id(),
|
||||
nanos
|
||||
));
|
||||
fs::create_dir_all(&temp_root)?;
|
||||
let _guard = EnvGuard::new(&temp_root);
|
||||
|
||||
let config_path = temp_root.join(".deepseek").join("config.toml");
|
||||
ensure_parent_dir(&config_path)?;
|
||||
fs::write(
|
||||
&config_path,
|
||||
r#"provider = "moonshot"
|
||||
|
||||
[providers.moonshot]
|
||||
api_key = "moonshot-platform-key"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let config = Config::load(None, None)?;
|
||||
assert_eq!(config.api_provider(), ApiProvider::Moonshot);
|
||||
assert_eq!(config.deepseek_base_url(), DEFAULT_MOONSHOT_BASE_URL);
|
||||
assert_eq!(config.default_model(), DEFAULT_MOONSHOT_MODEL);
|
||||
assert_eq!(config.deepseek_api_key()?, "moonshot-platform-key");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn has_api_key_for_detects_env_and_config_per_provider() -> Result<()> {
|
||||
let _lock = lock_test_env();
|
||||
|
||||
+19
-13
@@ -63,15 +63,14 @@ provider's keyring entry.
|
||||
|
||||
For hosted, generic OpenAI-compatible, or self-hosted providers, set
|
||||
`provider = "nvidia-nim"`, `"openai"`, `"atlascloud"`, `"wanjie-ark"`, `"fireworks"`,
|
||||
`"moonshot"`, `"sglang"`, `"vllm"`, or `"ollama"` or pass `codewhale --provider <name>`.
|
||||
`"sglang"`, `"vllm"`, or `"ollama"` or pass `codewhale --provider <name>`.
|
||||
The facade saves provider credentials to the shared user config and forwards
|
||||
the resolved key, base URL, provider, and model to the TUI process. Use
|
||||
`codewhale auth set --provider nvidia-nim --api-key "YOUR_NVIDIA_API_KEY"` or
|
||||
`codewhale auth set --provider openai --api-key "YOUR_OPENAI_COMPATIBLE_API_KEY"` or
|
||||
`codewhale auth set --provider atlascloud --api-key "YOUR_ATLASCLOUD_API_KEY"` or
|
||||
`codewhale auth set --provider wanjie-ark --api-key "YOUR_WANJIE_API_KEY"` or
|
||||
`codewhale auth set --provider fireworks --api-key "YOUR_FIREWORKS_API_KEY"` or
|
||||
`codewhale auth set --provider moonshot --api-key "YOUR_MOONSHOT_OR_KIMI_API_KEY"`
|
||||
`codewhale auth set --provider fireworks --api-key "YOUR_FIREWORKS_API_KEY"`
|
||||
to save provider keys through the facade. The generic `openai` provider defaults
|
||||
to `https://api.openai.com/v1`, accepts `OPENAI_BASE_URL`, and defaults to
|
||||
`deepseek-v4-pro` for OpenAI-compatible gateways. `atlascloud` defaults to
|
||||
@@ -80,9 +79,7 @@ to `https://api.openai.com/v1`, accepts `OPENAI_BASE_URL`, and defaults to
|
||||
Wanjie Ark's OpenAI-compatible endpoint at
|
||||
`https://maas-openapi.wanjiedata.com/api/v1`, defaults to `deepseek-reasoner`,
|
||||
and passes model IDs through unchanged because Wanjie model access is
|
||||
account-scoped. `moonshot` targets Moonshot/Kimi, defaults to `kimi-k2.6`,
|
||||
and can use `KIMI_API_KEY` or `auth_mode = "kimi_oauth"` with local Kimi CLI
|
||||
credentials. SGLang, vLLM, and Ollama are
|
||||
account-scoped. SGLang, vLLM, and Ollama are
|
||||
self-hosted and can run without an API key by default. Ollama defaults to
|
||||
`http://localhost:11434/v1` and sends model tags such as `codewhale-coder:1.3b`
|
||||
or `qwen2.5-coder:7b` unchanged. Self-hosted providers and loopback custom
|
||||
@@ -200,13 +197,22 @@ If a profile is selected but missing, codewhale exits with an error listing avai
|
||||
## Environment Variables
|
||||
|
||||
Most runtime environment variables override config values. API-key variables are
|
||||
fallbacks after saved config and keyring credentials:
|
||||
fallbacks after saved config and keyring credentials.
|
||||
|
||||
The three user-facing slots — provider, model, base URL — expose `CODEWHALE_*`
|
||||
aliases. When both forms are set the `CODEWHALE_*` value wins; the
|
||||
`DEEPSEEK_*` form is kept for older shells:
|
||||
|
||||
- `CODEWHALE_PROVIDER` (preferred) / `DEEPSEEK_PROVIDER` (legacy alias) —
|
||||
`deepseek|nvidia-nim|openai|atlascloud|wanjie-ark|openrouter|novita|fireworks|sglang|vllm|ollama`
|
||||
- `CODEWHALE_MODEL` (preferred) / `DEEPSEEK_MODEL` (legacy alias) — default model for the active provider
|
||||
- `CODEWHALE_BASE_URL` (preferred) / `DEEPSEEK_BASE_URL` (legacy alias) — base URL for the active provider
|
||||
|
||||
Remaining variables:
|
||||
|
||||
- `DEEPSEEK_API_KEY`
|
||||
- `DEEPSEEK_BASE_URL`
|
||||
- `DEEPSEEK_HTTP_HEADERS` (custom model request headers, comma-separated `name=value` pairs)
|
||||
- `DEEPSEEK_PROVIDER` (`codewhale|nvidia-nim|openai|atlascloud|wanjie-ark|openrouter|novita|fireworks|moonshot|sglang|vllm|ollama`)
|
||||
- `DEEPSEEK_MODEL` or `DEEPSEEK_DEFAULT_TEXT_MODEL`
|
||||
- `DEEPSEEK_DEFAULT_TEXT_MODEL` (extra legacy alias of `DEEPSEEK_MODEL`)
|
||||
- `DEEPSEEK_STREAM_IDLE_TIMEOUT_SECS` (stream idle timeout in seconds; default `300`, clamped to `1..=3600`)
|
||||
- `DEEPSEEK_STREAM_OPEN_TIMEOUT_SECS` (connection setup + response-header wait in seconds; default `45`, clamped to `5..=300`; distinct from the per-chunk idle timeout)
|
||||
- `NVIDIA_API_KEY` or `NVIDIA_NIM_API_KEY` (preferred when provider is `nvidia-nim`; falls back to `DEEPSEEK_API_KEY`)
|
||||
@@ -429,10 +435,10 @@ If you are upgrading from older releases:
|
||||
|
||||
### Core keys (used by the TUI/engine)
|
||||
|
||||
- `provider` (string, optional): `codewhale` (default), `nvidia-nim`, `openai`, `atlascloud`, `wanjie-ark`, `openrouter`, `novita`, `fireworks`, `moonshot`, `sglang`, `vllm`, or `ollama`. Legacy `deepseek-cn` configs are still accepted as an alias for `codewhale`; DeepSeek uses the same official host [`https://api.deepseek.com`](https://api-docs.deepseek.com/) worldwide. `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`; `atlascloud` targets AtlasCloud's OpenAI-compatible endpoint at `https://api.atlascloud.ai/v1`; `wanjie-ark` targets Wanjie Ark's OpenAI-compatible endpoint at `https://maas-openapi.wanjiedata.com/api/v1`; `fireworks` targets `https://api.fireworks.ai/inference/v1`; `moonshot` targets `https://api.moonshot.ai/v1` by default, with Kimi CLI OAuth mode using `https://api.kimi.com/coding/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): `codewhale` (default), `nvidia-nim`, `openai`, `atlascloud`, `wanjie-ark`, `openrouter`, `novita`, `fireworks`, `sglang`, `vllm`, or `ollama`. Legacy `deepseek-cn` configs are still accepted as an alias for `codewhale`; DeepSeek uses the same official host [`https://api.deepseek.com`](https://api-docs.deepseek.com/) worldwide. `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`; `atlascloud` targets AtlasCloud's OpenAI-compatible endpoint at `https://api.atlascloud.ai/v1`; `wanjie-ark` targets Wanjie Ark's OpenAI-compatible endpoint at `https://maas-openapi.wanjiedata.com/api/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, including legacy `provider = "deepseek-cn"` configs, `https://api.openai.com/v1` for `provider = "openai"`, `https://api.atlascloud.ai/v1` for `provider = "atlascloud"`, `https://maas-openapi.wanjiedata.com/api/v1` for `provider = "wanjie-ark"`, `https://api.moonshot.ai/v1` for `provider = "moonshot"` API-key mode, 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 and generic OpenAI-compatible endpoints, `deepseek-ai/deepseek-v4-pro` for NVIDIA NIM, `deepseek-ai/deepseek-v4-flash` for AtlasCloud, `deepseek-reasoner` for Wanjie Ark, `accounts/fireworks/models/deepseek-v4-pro` for Fireworks, `kimi-k2.6` for Moonshot/Kimi API-key mode, `deepseek-ai/DeepSeek-V4-Pro` for SGLang/vLLM, and `codewhale-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`, `atlascloud`, `wanjie-ark`, and Ollama model IDs are passed through unchanged. OpenRouter provider configs with a custom `base_url` also preserve explicit model values, which lets OpenAI-compatible gateways accept bare model IDs. Use `/models` or `codewhale models` to discover live IDs from your configured endpoint. `DEEPSEEK_MODEL` overrides this for a single process.
|
||||
- `base_url` (string, optional): defaults to `https://api.deepseek.com/beta` for DeepSeek's OpenAI-compatible Chat Completions API, including legacy `provider = "deepseek-cn"` configs, `https://api.openai.com/v1` for `provider = "openai"`, `https://api.atlascloud.ai/v1` for `provider = "atlascloud"`, `https://maas-openapi.wanjiedata.com/api/v1` for `provider = "wanjie-ark"`, 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 and generic OpenAI-compatible endpoints, `deepseek-ai/deepseek-v4-pro` for NVIDIA NIM, `deepseek-ai/deepseek-v4-flash` for AtlasCloud, `deepseek-reasoner` for Wanjie Ark, `accounts/fireworks/models/deepseek-v4-pro` for Fireworks, `deepseek-ai/DeepSeek-V4-Pro` for SGLang/vLLM, and `codewhale-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`, `atlascloud`, `wanjie-ark`, and Ollama model IDs are passed through unchanged. OpenRouter provider configs with a custom `base_url` also preserve explicit model values, which lets OpenAI-compatible gateways accept bare model IDs. Use `/models` or `codewhale 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).
|
||||
- `approval_policy` (string, optional): `on-request`, `untrusted`, or `never`. Runtime `approval_mode` editing in `/config` also accepts `on-request` and `untrusted` aliases.
|
||||
|
||||
Reference in New Issue
Block a user