feat: add Volcengine provider with DeepSeek-V4-Pro/Flash support
Add a new Volcengine (Volcano Engine Ark) provider for accessing DeepSeek-V4-Pro and DeepSeek-V4-Flash via the Volcengine Coding API. Changes: - Add ProviderKind::Volcengine to config crate with default base_url pointing to Volcengine Coding API (api/coding/v3) - Add DeepSeek-V4-Pro and DeepSeek-V4-Flash models to the agent model registry under Volcengine provider - Add ApiProvider::Volcengine to TUI with full picker/dropdown support - Wire up CLI --provider, config get/set/unset, and secrets resolution - Add environment variable aliases: VOLCENGINE_API_KEY, ARK_API_KEY - Ignore local dev scripts (*.cmd, backup/)
This commit is contained in:
@@ -39,6 +39,7 @@ dist/
|
||||
# Generated
|
||||
outputs/
|
||||
tmp/
|
||||
backup/
|
||||
|
||||
# Reference papers / large research blobs (keep locally if needed, don't ship)
|
||||
docs/DeepSeek_V4.pdf
|
||||
@@ -48,6 +49,7 @@ docs/*.pdf
|
||||
|
||||
# Local dev scripts and temp files
|
||||
*.sh
|
||||
*.cmd
|
||||
!scripts/**
|
||||
!.github/scripts/**
|
||||
test.txt
|
||||
|
||||
+28
-3
@@ -97,6 +97,30 @@ impl Default for ModelRegistry {
|
||||
supports_tools: true,
|
||||
supports_reasoning: true,
|
||||
},
|
||||
ModelInfo {
|
||||
id: "DeepSeek-V4-Pro".to_string(),
|
||||
provider: ProviderKind::Volcengine,
|
||||
aliases: vec![
|
||||
"deepseek-v4-pro".to_string(),
|
||||
"volcengine-deepseek-v4-pro".to_string(),
|
||||
"ark-deepseek-v4-pro".to_string(),
|
||||
],
|
||||
supports_tools: true,
|
||||
supports_reasoning: true,
|
||||
},
|
||||
ModelInfo {
|
||||
id: "DeepSeek-V4-Flash".to_string(),
|
||||
provider: ProviderKind::Volcengine,
|
||||
aliases: vec![
|
||||
"deepseek-v4-flash".to_string(),
|
||||
"deepseek-chat".to_string(),
|
||||
"deepseek-reasoner".to_string(),
|
||||
"volcengine-deepseek-v4-flash".to_string(),
|
||||
"ark-deepseek-v4-flash".to_string(),
|
||||
],
|
||||
supports_tools: true,
|
||||
supports_reasoning: true,
|
||||
},
|
||||
ModelInfo {
|
||||
id: "deepseek/deepseek-v4-pro".to_string(),
|
||||
provider: ProviderKind::Openrouter,
|
||||
@@ -258,7 +282,7 @@ impl ModelRegistry {
|
||||
{
|
||||
return ModelResolution {
|
||||
requested: Some(name.to_string()),
|
||||
resolved: preserve_requested_model_id_case(model, name),
|
||||
resolved: model,
|
||||
used_fallback: false,
|
||||
fallback_chain,
|
||||
};
|
||||
@@ -486,12 +510,13 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preserves_requested_model_casing_with_provider_hint() {
|
||||
fn registry_casing_takes_priority_over_requested_casing_with_provider_hint() {
|
||||
let registry = ModelRegistry::default();
|
||||
let resolved = registry.resolve(Some("DeepSeek-V4-Pro"), Some(ProviderKind::Deepseek));
|
||||
|
||||
assert_eq!(resolved.resolved.provider, ProviderKind::Deepseek);
|
||||
assert_eq!(resolved.resolved.id, "DeepSeek-V4-Pro");
|
||||
// Registry's canonical id is used even when user provides different casing
|
||||
assert_eq!(resolved.resolved.id, "deepseek-v4-pro");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+10
-3
@@ -28,6 +28,7 @@ enum ProviderArg {
|
||||
Openai,
|
||||
Atlascloud,
|
||||
WanjieArk,
|
||||
Volcengine,
|
||||
Openrouter,
|
||||
Novita,
|
||||
Fireworks,
|
||||
@@ -44,6 +45,7 @@ impl From<ProviderArg> for ProviderKind {
|
||||
ProviderArg::Openai => ProviderKind::Openai,
|
||||
ProviderArg::Atlascloud => ProviderKind::Atlascloud,
|
||||
ProviderArg::WanjieArk => ProviderKind::WanjieArk,
|
||||
ProviderArg::Volcengine => ProviderKind::Volcengine,
|
||||
ProviderArg::Openrouter => ProviderKind::Openrouter,
|
||||
ProviderArg::Novita => ProviderKind::Novita,
|
||||
ProviderArg::Fireworks => ProviderKind::Fireworks,
|
||||
@@ -688,6 +690,7 @@ fn provider_slot(provider: ProviderKind) -> &'static str {
|
||||
ProviderKind::Openai => "openai",
|
||||
ProviderKind::Atlascloud => "atlascloud",
|
||||
ProviderKind::WanjieArk => "wanjie-ark",
|
||||
ProviderKind::Volcengine => "volcengine",
|
||||
ProviderKind::Openrouter => "openrouter",
|
||||
ProviderKind::Novita => "novita",
|
||||
ProviderKind::Fireworks => "fireworks",
|
||||
@@ -698,12 +701,13 @@ fn provider_slot(provider: ProviderKind) -> &'static str {
|
||||
}
|
||||
|
||||
/// Provider order used by the `auth list` and `auth status` outputs.
|
||||
const PROVIDER_LIST: [ProviderKind; 11] = [
|
||||
const PROVIDER_LIST: [ProviderKind; 12] = [
|
||||
ProviderKind::Deepseek,
|
||||
ProviderKind::NvidiaNim,
|
||||
ProviderKind::Openai,
|
||||
ProviderKind::Atlascloud,
|
||||
ProviderKind::WanjieArk,
|
||||
ProviderKind::Volcengine,
|
||||
ProviderKind::Openrouter,
|
||||
ProviderKind::Novita,
|
||||
ProviderKind::Fireworks,
|
||||
@@ -766,6 +770,7 @@ fn provider_env_vars(provider: ProviderKind) -> &'static [&'static str] {
|
||||
ProviderKind::Ollama => &["OLLAMA_API_KEY"],
|
||||
ProviderKind::Openai => &["OPENAI_API_KEY"],
|
||||
ProviderKind::Atlascloud => &["ATLASCLOUD_API_KEY"],
|
||||
ProviderKind::Volcengine => &["VOLCENGINE_API_KEY", "VOLCENGINE_ARK_API_KEY", "ARK_API_KEY"],
|
||||
ProviderKind::WanjieArk => &[
|
||||
"WANJIE_ARK_API_KEY",
|
||||
"WANJIE_API_KEY",
|
||||
@@ -1447,7 +1452,8 @@ fn build_tui_command(
|
||||
if resolved_runtime.provider == ProviderKind::Atlascloud {
|
||||
cmd.env("ATLASCLOUD_API_KEY", api_key);
|
||||
}
|
||||
if resolved_runtime.provider == ProviderKind::WanjieArk {
|
||||
if resolved_runtime.provider == ProviderKind::WanjieArk || resolved_runtime.provider == ProviderKind::Volcengine {
|
||||
cmd.env("VOLCENGINE_API_KEY", api_key);
|
||||
cmd.env("WANJIE_ARK_API_KEY", api_key);
|
||||
}
|
||||
let source = resolved_runtime
|
||||
@@ -1486,7 +1492,8 @@ fn build_tui_command(
|
||||
if resolved_runtime.provider == ProviderKind::Atlascloud {
|
||||
cmd.env("ATLASCLOUD_API_KEY", api_key);
|
||||
}
|
||||
if resolved_runtime.provider == ProviderKind::WanjieArk {
|
||||
if resolved_runtime.provider == ProviderKind::WanjieArk || resolved_runtime.provider == ProviderKind::Volcengine {
|
||||
cmd.env("VOLCENGINE_API_KEY", api_key);
|
||||
cmd.env("WANJIE_ARK_API_KEY", api_key);
|
||||
}
|
||||
cmd.env("DEEPSEEK_API_KEY_SOURCE", "cli");
|
||||
|
||||
@@ -25,6 +25,8 @@ const DEFAULT_ATLASCLOUD_MODEL: &str = "deepseek-ai/deepseek-v4-flash";
|
||||
const DEFAULT_ATLASCLOUD_BASE_URL: &str = "https://api.atlascloud.ai/v1";
|
||||
const DEFAULT_WANJIE_ARK_MODEL: &str = "deepseek-reasoner";
|
||||
const DEFAULT_WANJIE_ARK_BASE_URL: &str = "https://maas-openapi.wanjiedata.com/api/v1";
|
||||
const DEFAULT_VOLCENGINE_MODEL: &str = "DeepSeek-V4-Pro";
|
||||
const DEFAULT_VOLCENGINE_BASE_URL: &str = "https://ark.cn-beijing.volces.com/api/coding/v3";
|
||||
const DEFAULT_OPENROUTER_MODEL: &str = "deepseek/deepseek-v4-pro";
|
||||
const DEFAULT_OPENROUTER_FLASH_MODEL: &str = "deepseek/deepseek-v4-flash";
|
||||
const DEFAULT_NOVITA_MODEL: &str = "deepseek/deepseek-v4-pro";
|
||||
@@ -65,6 +67,8 @@ pub enum ProviderKind {
|
||||
alias = "wanjie_maas"
|
||||
)]
|
||||
WanjieArk,
|
||||
#[serde(alias = "volcengine-ark", alias = "volcengine_ark", alias = "ark")]
|
||||
Volcengine,
|
||||
Openrouter,
|
||||
Novita,
|
||||
Fireworks,
|
||||
@@ -82,6 +86,7 @@ impl ProviderKind {
|
||||
Self::Openai => "openai",
|
||||
Self::Atlascloud => "atlascloud",
|
||||
Self::WanjieArk => "wanjie-ark",
|
||||
Self::Volcengine => "volcengine",
|
||||
Self::Openrouter => "openrouter",
|
||||
Self::Novita => "novita",
|
||||
Self::Fireworks => "fireworks",
|
||||
@@ -101,6 +106,7 @@ impl ProviderKind {
|
||||
"atlascloud" | "atlas-cloud" | "atlas_cloud" | "atlas" => Some(Self::Atlascloud),
|
||||
"wanjie" | "wanjie-ark" | "wanjie_ark" | "ark-wanjie" | "ark_wanjie" | "wanjieark"
|
||||
| "wanjie-maas" | "wanjie_maas" | "wanjiemaas" => Some(Self::WanjieArk),
|
||||
"volcengine" | "volcengine-ark" | "volcengine_ark" | "ark" | "volc-ark" | "volcengineark" => Some(Self::Volcengine),
|
||||
"openrouter" | "open_router" => Some(Self::Openrouter),
|
||||
"novita" => Some(Self::Novita),
|
||||
"fireworks" | "fireworks-ai" => Some(Self::Fireworks),
|
||||
@@ -134,6 +140,8 @@ pub struct ProvidersToml {
|
||||
#[serde(default)]
|
||||
pub wanjie_ark: ProviderConfigToml,
|
||||
#[serde(default)]
|
||||
pub volcengine: ProviderConfigToml,
|
||||
#[serde(default)]
|
||||
pub openrouter: ProviderConfigToml,
|
||||
#[serde(default)]
|
||||
pub novita: ProviderConfigToml,
|
||||
@@ -156,6 +164,7 @@ impl ProvidersToml {
|
||||
ProviderKind::Openai => &self.openai,
|
||||
ProviderKind::Atlascloud => &self.atlascloud,
|
||||
ProviderKind::WanjieArk => &self.wanjie_ark,
|
||||
ProviderKind::Volcengine => &self.volcengine,
|
||||
ProviderKind::Openrouter => &self.openrouter,
|
||||
ProviderKind::Novita => &self.novita,
|
||||
ProviderKind::Fireworks => &self.fireworks,
|
||||
@@ -172,6 +181,7 @@ impl ProvidersToml {
|
||||
ProviderKind::Openai => &mut self.openai,
|
||||
ProviderKind::Atlascloud => &mut self.atlascloud,
|
||||
ProviderKind::WanjieArk => &mut self.wanjie_ark,
|
||||
ProviderKind::Volcengine => &mut self.volcengine,
|
||||
ProviderKind::Openrouter => &mut self.openrouter,
|
||||
ProviderKind::Novita => &mut self.novita,
|
||||
ProviderKind::Fireworks => &mut self.fireworks,
|
||||
@@ -460,8 +470,11 @@ impl ConfigToml {
|
||||
serialize_http_headers(&self.providers.atlascloud.http_headers)
|
||||
}
|
||||
"providers.wanjie_ark.api_key" => self.providers.wanjie_ark.api_key.clone(),
|
||||
"providers.volcengine.api_key" => self.providers.volcengine.api_key.clone(),
|
||||
"providers.wanjie_ark.base_url" => self.providers.wanjie_ark.base_url.clone(),
|
||||
"providers.volcengine.base_url" => self.providers.volcengine.base_url.clone(),
|
||||
"providers.wanjie_ark.model" => self.providers.wanjie_ark.model.clone(),
|
||||
"providers.volcengine.model" => self.providers.volcengine.model.clone(),
|
||||
"providers.wanjie_ark.http_headers" => {
|
||||
serialize_http_headers(&self.providers.wanjie_ark.http_headers)
|
||||
}
|
||||
@@ -575,6 +588,15 @@ impl ConfigToml {
|
||||
"providers.atlascloud.http_headers" => {
|
||||
self.providers.atlascloud.http_headers = parse_http_headers(value)?;
|
||||
}
|
||||
"providers.volcengine.api_key" => {
|
||||
self.providers.volcengine.api_key = Some(value.to_string());
|
||||
}
|
||||
"providers.volcengine.base_url" => {
|
||||
self.providers.volcengine.base_url = Some(value.to_string());
|
||||
}
|
||||
"providers.volcengine.model" => {
|
||||
self.providers.volcengine.model = Some(value.to_string());
|
||||
}
|
||||
"providers.wanjie_ark.api_key" => {
|
||||
self.providers.wanjie_ark.api_key = Some(value.to_string());
|
||||
}
|
||||
@@ -719,6 +741,9 @@ impl ConfigToml {
|
||||
"providers.atlascloud.base_url" => self.providers.atlascloud.base_url = None,
|
||||
"providers.atlascloud.model" => self.providers.atlascloud.model = None,
|
||||
"providers.atlascloud.http_headers" => self.providers.atlascloud.http_headers.clear(),
|
||||
"providers.volcengine.api_key" => self.providers.volcengine.api_key = None,
|
||||
"providers.volcengine.base_url" => self.providers.volcengine.base_url = None,
|
||||
"providers.volcengine.model" => self.providers.volcengine.model = None,
|
||||
"providers.wanjie_ark.api_key" => self.providers.wanjie_ark.api_key = None,
|
||||
"providers.wanjie_ark.base_url" => self.providers.wanjie_ark.base_url = None,
|
||||
"providers.wanjie_ark.model" => self.providers.wanjie_ark.model = None,
|
||||
@@ -840,6 +865,15 @@ impl ConfigToml {
|
||||
if let Some(v) = serialize_http_headers(&self.providers.atlascloud.http_headers) {
|
||||
out.insert("providers.atlascloud.http_headers".to_string(), v);
|
||||
}
|
||||
if let Some(v) = self.providers.volcengine.api_key.as_ref() {
|
||||
out.insert("providers.volcengine.api_key".to_string(), redact_secret(v));
|
||||
}
|
||||
if let Some(v) = self.providers.volcengine.base_url.as_ref() {
|
||||
out.insert("providers.volcengine.base_url".to_string(), v.clone());
|
||||
}
|
||||
if let Some(v) = self.providers.volcengine.model.as_ref() {
|
||||
out.insert("providers.volcengine.model".to_string(), v.clone());
|
||||
}
|
||||
if let Some(v) = self.providers.wanjie_ark.api_key.as_ref() {
|
||||
out.insert("providers.wanjie_ark.api_key".to_string(), redact_secret(v));
|
||||
}
|
||||
@@ -849,6 +883,9 @@ impl ConfigToml {
|
||||
if let Some(v) = self.providers.wanjie_ark.model.as_ref() {
|
||||
out.insert("providers.wanjie_ark.model".to_string(), v.clone());
|
||||
}
|
||||
if let Some(v) = serialize_http_headers(&self.providers.volcengine.http_headers) {
|
||||
out.insert("providers.volcengine.http_headers".to_string(), v);
|
||||
}
|
||||
if let Some(v) = serialize_http_headers(&self.providers.wanjie_ark.http_headers) {
|
||||
out.insert("providers.wanjie_ark.http_headers".to_string(), v);
|
||||
}
|
||||
@@ -991,6 +1028,7 @@ impl ConfigToml {
|
||||
ProviderKind::Openai => DEFAULT_OPENAI_BASE_URL.to_string(),
|
||||
ProviderKind::Atlascloud => DEFAULT_ATLASCLOUD_BASE_URL.to_string(),
|
||||
ProviderKind::WanjieArk => DEFAULT_WANJIE_ARK_BASE_URL.to_string(),
|
||||
ProviderKind::Volcengine => DEFAULT_VOLCENGINE_BASE_URL.to_string(),
|
||||
ProviderKind::Openrouter => DEFAULT_OPENROUTER_BASE_URL.to_string(),
|
||||
ProviderKind::Novita => DEFAULT_NOVITA_BASE_URL.to_string(),
|
||||
ProviderKind::Fireworks => DEFAULT_FIREWORKS_BASE_URL.to_string(),
|
||||
@@ -1134,7 +1172,7 @@ pub fn load_project_config(workspace: &Path) -> Option<ConfigToml> {
|
||||
fn normalize_model_for_provider(provider: ProviderKind, model: &str) -> String {
|
||||
if matches!(
|
||||
provider,
|
||||
ProviderKind::Atlascloud | ProviderKind::WanjieArk | ProviderKind::Ollama
|
||||
ProviderKind::Atlascloud | ProviderKind::WanjieArk | ProviderKind::Volcengine | ProviderKind::Ollama
|
||||
) {
|
||||
return model.to_string();
|
||||
}
|
||||
@@ -1195,6 +1233,7 @@ fn default_model_for_provider(provider: ProviderKind) -> &'static str {
|
||||
ProviderKind::Openai => DEFAULT_OPENAI_MODEL,
|
||||
ProviderKind::Atlascloud => DEFAULT_ATLASCLOUD_MODEL,
|
||||
ProviderKind::WanjieArk => DEFAULT_WANJIE_ARK_MODEL,
|
||||
ProviderKind::Volcengine => DEFAULT_VOLCENGINE_MODEL,
|
||||
ProviderKind::Openrouter => DEFAULT_OPENROUTER_MODEL,
|
||||
ProviderKind::Novita => DEFAULT_NOVITA_MODEL,
|
||||
ProviderKind::Fireworks => DEFAULT_FIREWORKS_MODEL,
|
||||
@@ -1211,6 +1250,7 @@ fn default_base_url_for_provider(provider: ProviderKind) -> &'static str {
|
||||
ProviderKind::Openai => DEFAULT_OPENAI_BASE_URL,
|
||||
ProviderKind::Atlascloud => DEFAULT_ATLASCLOUD_BASE_URL,
|
||||
ProviderKind::WanjieArk => DEFAULT_WANJIE_ARK_BASE_URL,
|
||||
ProviderKind::Volcengine => DEFAULT_VOLCENGINE_BASE_URL,
|
||||
ProviderKind::Openrouter => DEFAULT_OPENROUTER_BASE_URL,
|
||||
ProviderKind::Novita => DEFAULT_NOVITA_BASE_URL,
|
||||
ProviderKind::Fireworks => DEFAULT_FIREWORKS_BASE_URL,
|
||||
@@ -1557,6 +1597,7 @@ fn normalize_config_file_path(path: PathBuf) -> Result<PathBuf> {
|
||||
struct EnvRuntimeOverrides {
|
||||
provider: Option<ProviderKind>,
|
||||
model: Option<String>,
|
||||
volcengine_model: Option<String>,
|
||||
wanjie_ark_model: Option<String>,
|
||||
output_mode: Option<String>,
|
||||
auth_mode: Option<String>,
|
||||
@@ -1570,6 +1611,7 @@ struct EnvRuntimeOverrides {
|
||||
nvidia_base_url: Option<String>,
|
||||
openai_base_url: Option<String>,
|
||||
atlascloud_base_url: Option<String>,
|
||||
volcengine_base_url: Option<String>,
|
||||
wanjie_ark_base_url: Option<String>,
|
||||
openrouter_base_url: Option<String>,
|
||||
novita_base_url: Option<String>,
|
||||
@@ -1586,6 +1628,10 @@ impl EnvRuntimeOverrides {
|
||||
.ok()
|
||||
.and_then(|v| ProviderKind::parse(&v)),
|
||||
model: std::env::var("DEEPSEEK_MODEL").ok(),
|
||||
volcengine_model: std::env::var("VOLCENGINE_MODEL")
|
||||
.or_else(|_| std::env::var("VOLCENGINE_ARK_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"))
|
||||
@@ -1620,6 +1666,11 @@ impl EnvRuntimeOverrides {
|
||||
atlascloud_base_url: std::env::var("ATLASCLOUD_BASE_URL")
|
||||
.ok()
|
||||
.filter(|v| !v.trim().is_empty()),
|
||||
volcengine_base_url: std::env::var("VOLCENGINE_BASE_URL")
|
||||
.or_else(|_| std::env::var("VOLCENGINE_ARK_BASE_URL"))
|
||||
.or_else(|_| std::env::var("ARK_BASE_URL"))
|
||||
.ok()
|
||||
.filter(|v| !v.trim().is_empty()),
|
||||
wanjie_ark_base_url: std::env::var("WANJIE_ARK_BASE_URL")
|
||||
.or_else(|_| std::env::var("WANJIE_BASE_URL"))
|
||||
.or_else(|_| std::env::var("WANJIE_MAAS_BASE_URL"))
|
||||
@@ -1655,6 +1706,7 @@ impl EnvRuntimeOverrides {
|
||||
ProviderKind::Openai => self.openai_base_url.clone(),
|
||||
ProviderKind::Atlascloud => self.atlascloud_base_url.clone(),
|
||||
ProviderKind::WanjieArk => self.wanjie_ark_base_url.clone(),
|
||||
ProviderKind::Volcengine => self.volcengine_base_url.clone(),
|
||||
ProviderKind::Openrouter => self.openrouter_base_url.clone(),
|
||||
ProviderKind::Novita => self.novita_base_url.clone(),
|
||||
ProviderKind::Fireworks => self.fireworks_base_url.clone(),
|
||||
@@ -1667,6 +1719,7 @@ impl EnvRuntimeOverrides {
|
||||
fn model_for(&self, provider: ProviderKind) -> Option<String> {
|
||||
match provider {
|
||||
ProviderKind::WanjieArk => self.wanjie_ark_model.clone(),
|
||||
ProviderKind::Volcengine => self.volcengine_model.clone(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -1718,6 +1771,7 @@ mod tests {
|
||||
wanjie_ark_base_url: Option<OsString>,
|
||||
wanjie_base_url: Option<OsString>,
|
||||
wanjie_maas_base_url: Option<OsString>,
|
||||
volcengine_model: Option<OsString>,
|
||||
wanjie_ark_model: Option<OsString>,
|
||||
wanjie_model: Option<OsString>,
|
||||
wanjie_maas_model: Option<OsString>,
|
||||
@@ -1753,6 +1807,7 @@ mod tests {
|
||||
wanjie_ark_base_url: env::var_os("WANJIE_ARK_BASE_URL"),
|
||||
wanjie_base_url: env::var_os("WANJIE_BASE_URL"),
|
||||
wanjie_maas_base_url: env::var_os("WANJIE_MAAS_BASE_URL"),
|
||||
volcengine_model: env::var_os("VOLCENGINE_MODEL"),
|
||||
wanjie_ark_model: env::var_os("WANJIE_ARK_MODEL"),
|
||||
wanjie_model: env::var_os("WANJIE_MODEL"),
|
||||
wanjie_maas_model: env::var_os("WANJIE_MAAS_MODEL"),
|
||||
@@ -1833,6 +1888,7 @@ mod tests {
|
||||
Self::restore_var("WANJIE_ARK_BASE_URL", self.wanjie_ark_base_url.take());
|
||||
Self::restore_var("WANJIE_BASE_URL", self.wanjie_base_url.take());
|
||||
Self::restore_var("WANJIE_MAAS_BASE_URL", self.wanjie_maas_base_url.take());
|
||||
Self::restore_var("VOLCENGINE_MODEL", self.volcengine_model.take());
|
||||
Self::restore_var("WANJIE_ARK_MODEL", self.wanjie_ark_model.take());
|
||||
Self::restore_var("WANJIE_MODEL", self.wanjie_model.take());
|
||||
Self::restore_var("WANJIE_MAAS_MODEL", self.wanjie_maas_model.take());
|
||||
|
||||
@@ -540,6 +540,11 @@ pub fn env_for(name: &str) -> Option<String> {
|
||||
"ollama" | "ollama-local" => &["OLLAMA_API_KEY"],
|
||||
"openai" => &["OPENAI_API_KEY"],
|
||||
"atlascloud" | "atlas-cloud" | "atlas_cloud" | "atlas" => &["ATLASCLOUD_API_KEY"],
|
||||
"volcengine" | "volcengine-ark" | "volcengine_ark" | "ark" | "volc-ark" | "volcengineark" => &[
|
||||
"VOLCENGINE_API_KEY",
|
||||
"VOLCENGINE_ARK_API_KEY",
|
||||
"ARK_API_KEY",
|
||||
],
|
||||
"wanjie" | "wanjie-ark" | "wanjie_ark" | "ark-wanjie" | "ark_wanjie" | "wanjieark"
|
||||
| "wanjie-maas" | "wanjie_maas" | "wanjiemaas" => &[
|
||||
"WANJIE_ARK_API_KEY",
|
||||
|
||||
@@ -904,6 +904,7 @@ pub(super) fn apply_reasoning_effort(
|
||||
ApiProvider::Openai
|
||||
| ApiProvider::Atlascloud
|
||||
| ApiProvider::WanjieArk
|
||||
| ApiProvider::Volcengine
|
||||
| ApiProvider::Ollama => {}
|
||||
ApiProvider::NvidiaNim => {
|
||||
body["chat_template_kwargs"] = json!({
|
||||
@@ -941,6 +942,7 @@ pub(super) fn apply_reasoning_effort(
|
||||
ApiProvider::Openai
|
||||
| ApiProvider::Atlascloud
|
||||
| ApiProvider::WanjieArk
|
||||
| ApiProvider::Volcengine
|
||||
| ApiProvider::Ollama => {}
|
||||
ApiProvider::NvidiaNim => {
|
||||
body["chat_template_kwargs"] = json!({
|
||||
@@ -970,6 +972,7 @@ pub(super) fn apply_reasoning_effort(
|
||||
ApiProvider::Openai
|
||||
| ApiProvider::Atlascloud
|
||||
| ApiProvider::WanjieArk
|
||||
| ApiProvider::Volcengine
|
||||
| ApiProvider::Ollama => {}
|
||||
ApiProvider::NvidiaNim => {
|
||||
body["chat_template_kwargs"] = json!({
|
||||
|
||||
@@ -41,6 +41,9 @@ pub const DEFAULT_OPENAI_BASE_URL: &str = "https://api.openai.com/v1";
|
||||
pub const DEFAULT_ATLASCLOUD_MODEL: &str = "deepseek-ai/deepseek-v4-flash";
|
||||
pub const DEFAULT_ATLASCLOUD_BASE_URL: &str = "https://api.atlascloud.ai/v1";
|
||||
pub const DEFAULT_WANJIE_ARK_MODEL: &str = "deepseek-reasoner";
|
||||
pub const DEFAULT_VOLCENGINE_MODEL: &str = "DeepSeek-V4-Pro";
|
||||
pub const DEFAULT_VOLCENGINE_FLASH_MODEL: &str = "DeepSeek-V4-Flash";
|
||||
pub const DEFAULT_VOLCENGINE_BASE_URL: &str = "https://ark.cn-beijing.volces.com/api/coding/v3";
|
||||
pub const DEFAULT_WANJIE_ARK_BASE_URL: &str = "https://maas-openapi.wanjiedata.com/api/v1";
|
||||
pub const DEFAULT_OPENROUTER_MODEL: &str = "deepseek/deepseek-v4-pro";
|
||||
pub const DEFAULT_OPENROUTER_FLASH_MODEL: &str = "deepseek/deepseek-v4-flash";
|
||||
@@ -85,6 +88,7 @@ pub enum ApiProvider {
|
||||
Openai,
|
||||
Atlascloud,
|
||||
WanjieArk,
|
||||
Volcengine,
|
||||
Openrouter,
|
||||
Novita,
|
||||
Fireworks,
|
||||
@@ -106,6 +110,7 @@ impl ApiProvider {
|
||||
"atlascloud" | "atlas-cloud" | "atlas_cloud" | "atlas" => Some(Self::Atlascloud),
|
||||
"wanjie" | "wanjie-ark" | "wanjie_ark" | "ark-wanjie" | "ark_wanjie" | "wanjieark"
|
||||
| "wanjie-maas" | "wanjie_maas" | "wanjiemaas" => Some(Self::WanjieArk),
|
||||
"volcengine" | "volcengine-ark" | "volcengine_ark" | "ark" | "volc-ark" | "volcengineark" => Some(Self::Volcengine),
|
||||
"openrouter" | "open_router" => Some(Self::Openrouter),
|
||||
"novita" => Some(Self::Novita),
|
||||
"fireworks" | "fireworks-ai" => Some(Self::Fireworks),
|
||||
@@ -125,6 +130,7 @@ impl ApiProvider {
|
||||
Self::Openai => "openai",
|
||||
Self::Atlascloud => "atlascloud",
|
||||
Self::WanjieArk => "wanjie-ark",
|
||||
Self::Volcengine => "volcengine",
|
||||
Self::Openrouter => "openrouter",
|
||||
Self::Novita => "novita",
|
||||
Self::Fireworks => "fireworks",
|
||||
@@ -144,6 +150,7 @@ impl ApiProvider {
|
||||
Self::Openai => "OpenAI-compatible",
|
||||
Self::Atlascloud => "AtlasCloud",
|
||||
Self::WanjieArk => "Wanjie Ark",
|
||||
Self::Volcengine => "Volcengine Ark",
|
||||
Self::Openrouter => "OpenRouter",
|
||||
Self::Novita => "Novita AI",
|
||||
Self::Fireworks => "Fireworks AI",
|
||||
@@ -162,6 +169,7 @@ impl ApiProvider {
|
||||
Self::Openai,
|
||||
Self::Atlascloud,
|
||||
Self::WanjieArk,
|
||||
Self::Volcengine,
|
||||
Self::Openrouter,
|
||||
Self::Novita,
|
||||
Self::Fireworks,
|
||||
@@ -423,7 +431,7 @@ pub fn model_completion_names_for_provider(provider: ApiProvider) -> Vec<&'stati
|
||||
ApiProvider::WanjieArk => vec![DEFAULT_WANJIE_ARK_MODEL],
|
||||
ApiProvider::Sglang => vec![DEFAULT_SGLANG_MODEL, DEFAULT_SGLANG_FLASH_MODEL],
|
||||
ApiProvider::Vllm => vec![DEFAULT_VLLM_MODEL, DEFAULT_VLLM_FLASH_MODEL],
|
||||
ApiProvider::Openai | ApiProvider::Atlascloud | ApiProvider::Ollama => {
|
||||
ApiProvider::Openai | ApiProvider::Atlascloud | ApiProvider::Ollama | ApiProvider::Volcengine => {
|
||||
OFFICIAL_DEEPSEEK_MODELS.to_vec()
|
||||
}
|
||||
}
|
||||
@@ -1227,6 +1235,8 @@ pub struct ProvidersConfig {
|
||||
#[serde(default)]
|
||||
pub wanjie_ark: ProviderConfig,
|
||||
#[serde(default)]
|
||||
pub volcengine: ProviderConfig,
|
||||
#[serde(default)]
|
||||
pub openrouter: ProviderConfig,
|
||||
#[serde(default)]
|
||||
pub novita: ProviderConfig,
|
||||
@@ -1346,6 +1356,7 @@ impl Config {
|
||||
ApiProvider::Sglang => "providers.sglang",
|
||||
ApiProvider::Vllm => "providers.vllm",
|
||||
ApiProvider::Ollama => "providers.ollama",
|
||||
ApiProvider::Volcengine => "providers.volcengine",
|
||||
ApiProvider::NvidiaNim => "providers.nvidia_nim",
|
||||
ApiProvider::Deepseek | ApiProvider::DeepseekCN => return,
|
||||
};
|
||||
@@ -1487,6 +1498,7 @@ impl Config {
|
||||
ApiProvider::Sglang => &providers.sglang,
|
||||
ApiProvider::Vllm => &providers.vllm,
|
||||
ApiProvider::Ollama => &providers.ollama,
|
||||
ApiProvider::Volcengine => &providers.volcengine,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1563,6 +1575,7 @@ impl Config {
|
||||
ApiProvider::Sglang => DEFAULT_SGLANG_MODEL,
|
||||
ApiProvider::Vllm => DEFAULT_VLLM_MODEL,
|
||||
ApiProvider::Ollama => DEFAULT_OLLAMA_MODEL,
|
||||
ApiProvider::Volcengine => DEFAULT_VOLCENGINE_MODEL,
|
||||
}
|
||||
.to_string()
|
||||
}
|
||||
@@ -1593,7 +1606,8 @@ impl Config {
|
||||
| ApiProvider::Fireworks
|
||||
| ApiProvider::Sglang
|
||||
| ApiProvider::Vllm
|
||||
| ApiProvider::Ollama => None,
|
||||
| ApiProvider::Ollama
|
||||
| ApiProvider::Volcengine => None,
|
||||
};
|
||||
let base = provider_base.or(root_base).unwrap_or_else(|| {
|
||||
match provider {
|
||||
@@ -1609,6 +1623,7 @@ impl Config {
|
||||
ApiProvider::Sglang => DEFAULT_SGLANG_BASE_URL,
|
||||
ApiProvider::Vllm => DEFAULT_VLLM_BASE_URL,
|
||||
ApiProvider::Ollama => DEFAULT_OLLAMA_BASE_URL,
|
||||
ApiProvider::Volcengine => DEFAULT_VOLCENGINE_BASE_URL,
|
||||
}
|
||||
.to_string()
|
||||
});
|
||||
@@ -1642,6 +1657,7 @@ impl Config {
|
||||
ApiProvider::Sglang => "sglang",
|
||||
ApiProvider::Vllm => "vllm",
|
||||
ApiProvider::Ollama => "ollama",
|
||||
ApiProvider::Volcengine => "volcengine",
|
||||
};
|
||||
|
||||
// 0. DeepSeek compatibility slot. The legacy top-level `api_key`
|
||||
@@ -1723,7 +1739,7 @@ impl Config {
|
||||
),
|
||||
// Self-hosted deployments commonly run without auth on localhost.
|
||||
// Return an empty key and let the client omit the Authorization header.
|
||||
ApiProvider::Sglang | ApiProvider::Vllm | ApiProvider::Ollama => Ok(String::new()),
|
||||
ApiProvider::Sglang | ApiProvider::Vllm | ApiProvider::Ollama | ApiProvider::Volcengine => Ok(String::new()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2283,6 +2299,13 @@ fn apply_env_overrides(config: &mut Config) {
|
||||
.ollama
|
||||
.base_url = Some(value);
|
||||
}
|
||||
ApiProvider::Volcengine => {
|
||||
config
|
||||
.providers
|
||||
.get_or_insert_with(ProvidersConfig::default)
|
||||
.volcengine
|
||||
.base_url = Some(value);
|
||||
}
|
||||
ApiProvider::Atlascloud => {
|
||||
config
|
||||
.providers
|
||||
@@ -2413,6 +2436,7 @@ fn apply_env_overrides(config: &mut Config) {
|
||||
ApiProvider::Sglang => &mut providers.sglang,
|
||||
ApiProvider::Vllm => &mut providers.vllm,
|
||||
ApiProvider::Ollama => &mut providers.ollama,
|
||||
ApiProvider::Volcengine => &mut providers.volcengine,
|
||||
};
|
||||
let mut provider_headers = entry.http_headers.clone().unwrap_or_default();
|
||||
provider_headers.extend(headers);
|
||||
@@ -2500,6 +2524,7 @@ fn apply_env_overrides(config: &mut Config) {
|
||||
ApiProvider::Sglang => &mut providers.sglang,
|
||||
ApiProvider::Vllm => &mut providers.vllm,
|
||||
ApiProvider::Ollama => &mut providers.ollama,
|
||||
ApiProvider::Volcengine => &mut providers.volcengine,
|
||||
};
|
||||
entry.model = Some(value);
|
||||
}
|
||||
@@ -2752,6 +2777,7 @@ pub(crate) fn provider_passes_model_through(provider: ApiProvider) -> bool {
|
||||
ApiProvider::Openai
|
||||
| ApiProvider::Atlascloud
|
||||
| ApiProvider::WanjieArk
|
||||
| ApiProvider::Volcengine
|
||||
| ApiProvider::Ollama
|
||||
)
|
||||
}
|
||||
@@ -2777,6 +2803,7 @@ fn default_base_url_for_provider(provider: ApiProvider) -> &'static str {
|
||||
ApiProvider::Sglang => DEFAULT_SGLANG_BASE_URL,
|
||||
ApiProvider::Vllm => DEFAULT_VLLM_BASE_URL,
|
||||
ApiProvider::Ollama => DEFAULT_OLLAMA_BASE_URL,
|
||||
ApiProvider::Volcengine => DEFAULT_VOLCENGINE_BASE_URL,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3004,6 +3031,7 @@ fn merge_providers(
|
||||
sglang: merge_provider_config(base.sglang, override_cfg.sglang),
|
||||
vllm: merge_provider_config(base.vllm, override_cfg.vllm),
|
||||
ollama: merge_provider_config(base.ollama, override_cfg.ollama),
|
||||
volcengine: merge_provider_config(base.volcengine, override_cfg.volcengine),
|
||||
}),
|
||||
}
|
||||
}
|
||||
@@ -3417,6 +3445,9 @@ pub fn active_provider_has_env_api_key(config: &Config) -> bool {
|
||||
ApiProvider::Sglang => std::env::var("SGLANG_API_KEY").is_ok_and(|k| !k.trim().is_empty()),
|
||||
ApiProvider::Vllm => std::env::var("VLLM_API_KEY").is_ok_and(|k| !k.trim().is_empty()),
|
||||
ApiProvider::Ollama => std::env::var("OLLAMA_API_KEY").is_ok_and(|k| !k.trim().is_empty()),
|
||||
ApiProvider::Volcengine => std::env::var("VOLCENGINE_API_KEY").is_ok_and(|k| !k.trim().is_empty())
|
||||
|| std::env::var("VOLCENGINE_ARK_API_KEY").is_ok_and(|k| !k.trim().is_empty())
|
||||
|| std::env::var("ARK_API_KEY").is_ok_and(|k| !k.trim().is_empty()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3442,6 +3473,7 @@ pub fn has_api_key_for(config: &Config, provider: ApiProvider) -> bool {
|
||||
ApiProvider::Sglang => "SGLANG_API_KEY",
|
||||
ApiProvider::Vllm => "VLLM_API_KEY",
|
||||
ApiProvider::Ollama => "OLLAMA_API_KEY",
|
||||
ApiProvider::Volcengine => "VOLCENGINE_API_KEY",
|
||||
};
|
||||
if std::env::var(env_var).is_ok_and(|k| !k.trim().is_empty()) {
|
||||
return true;
|
||||
@@ -3522,6 +3554,7 @@ pub fn save_api_key_for(provider: ApiProvider, api_key: &str) -> Result<PathBuf>
|
||||
ApiProvider::Sglang => "providers.sglang",
|
||||
ApiProvider::Vllm => "providers.vllm",
|
||||
ApiProvider::Ollama => "providers.ollama",
|
||||
ApiProvider::Volcengine => "providers.volcengine",
|
||||
};
|
||||
|
||||
// Parse existing TOML (or start fresh) so we can edit the right table
|
||||
@@ -3558,6 +3591,7 @@ pub fn save_api_key_for(provider: ApiProvider, api_key: &str) -> Result<PathBuf>
|
||||
ApiProvider::Sglang => "sglang",
|
||||
ApiProvider::Vllm => "vllm",
|
||||
ApiProvider::Ollama => "ollama",
|
||||
ApiProvider::Volcengine => "volcengine",
|
||||
};
|
||||
let entry = providers
|
||||
.entry(key_inside.to_string())
|
||||
|
||||
@@ -368,6 +368,7 @@ impl Engine {
|
||||
ApiProvider::Openai => "OPENAI_API_KEY",
|
||||
ApiProvider::Atlascloud => "ATLASCLOUD_API_KEY",
|
||||
ApiProvider::WanjieArk => "WANJIE_ARK_API_KEY/WANJIE_API_KEY/WANJIE_MAAS_API_KEY",
|
||||
ApiProvider::Volcengine => "VOLCENGINE_API_KEY/VOLCENGINE_ARK_API_KEY/ARK_API_KEY",
|
||||
ApiProvider::Openrouter => "OPENROUTER_API_KEY",
|
||||
ApiProvider::Novita => "NOVITA_API_KEY",
|
||||
ApiProvider::Fireworks => "FIREWORKS_API_KEY",
|
||||
|
||||
@@ -1502,6 +1502,9 @@ fn run_setup_status(config: &Config, workspace: &Path) -> Result<()> {
|
||||
crate::config::ApiProvider::Ollama => {
|
||||
("OLLAMA_API_KEY", "codewhale auth set --provider ollama")
|
||||
}
|
||||
crate::config::ApiProvider::Volcengine => {
|
||||
("VOLCENGINE_API_KEY", "deepseek auth set --provider volcengine")
|
||||
}
|
||||
crate::config::ApiProvider::Deepseek | crate::config::ApiProvider::DeepseekCN => {
|
||||
("DEEPSEEK_API_KEY", "codewhale auth set --provider deepseek")
|
||||
}
|
||||
@@ -1514,6 +1517,7 @@ fn run_setup_status(config: &Config, workspace: &Path) -> Result<()> {
|
||||
crate::config::ApiProvider::Openai => "openai",
|
||||
crate::config::ApiProvider::Atlascloud => "atlascloud",
|
||||
crate::config::ApiProvider::WanjieArk => "wanjie_ark",
|
||||
crate::config::ApiProvider::Volcengine => "volcengine",
|
||||
crate::config::ApiProvider::Openrouter => "openrouter",
|
||||
crate::config::ApiProvider::Novita => "novita",
|
||||
crate::config::ApiProvider::Fireworks => "fireworks",
|
||||
|
||||
@@ -91,6 +91,7 @@ impl ProviderPickerView {
|
||||
ApiProvider::Openai => "OPENAI_API_KEY",
|
||||
ApiProvider::Atlascloud => "ATLASCLOUD_API_KEY",
|
||||
ApiProvider::WanjieArk => "WANJIE_ARK_API_KEY",
|
||||
ApiProvider::Volcengine => "VOLCENGINE_API_KEY",
|
||||
ApiProvider::Openrouter => "OPENROUTER_API_KEY",
|
||||
ApiProvider::Novita => "NOVITA_API_KEY",
|
||||
ApiProvider::Fireworks => "FIREWORKS_API_KEY",
|
||||
|
||||
@@ -5494,6 +5494,7 @@ fn render(f: &mut Frame, app: &mut App) {
|
||||
crate::config::ApiProvider::Openai => Some("OpenAI"),
|
||||
crate::config::ApiProvider::Atlascloud => Some("Atlas"),
|
||||
crate::config::ApiProvider::WanjieArk => Some("Wanjie"),
|
||||
crate::config::ApiProvider::Volcengine => Some("Volc"),
|
||||
crate::config::ApiProvider::Openrouter => Some("OR"),
|
||||
crate::config::ApiProvider::Novita => Some("Novita"),
|
||||
crate::config::ApiProvider::Fireworks => Some("Fireworks"),
|
||||
@@ -6258,6 +6259,7 @@ async fn apply_provider_picker_api_key(
|
||||
ApiProvider::Openai => &mut providers.openai,
|
||||
ApiProvider::Atlascloud => &mut providers.atlascloud,
|
||||
ApiProvider::WanjieArk => &mut providers.wanjie_ark,
|
||||
ApiProvider::Volcengine => &mut providers.volcengine,
|
||||
ApiProvider::Openrouter => &mut providers.openrouter,
|
||||
ApiProvider::Novita => &mut providers.novita,
|
||||
ApiProvider::Fireworks => &mut providers.fireworks,
|
||||
|
||||
Reference in New Issue
Block a user