Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -48,6 +49,7 @@ docs/*.pdf

# Local dev scripts and temp files
*.sh
*.cmd
!scripts/**
!.github/scripts/**
test.txt
Expand Down
30 changes: 27 additions & 3 deletions crates/agent/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,29 @@ 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(),
"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,
Expand Down Expand Up @@ -258,7 +281,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,
};
Expand Down Expand Up @@ -486,12 +509,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]
Expand Down
13 changes: 12 additions & 1 deletion crates/cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ enum ProviderArg {
Openai,
Atlascloud,
WanjieArk,
Volcengine,
Openrouter,
Novita,
Fireworks,
Expand All @@ -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,
Expand Down Expand Up @@ -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",
Expand All @@ -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,
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -1450,6 +1455,9 @@ fn build_tui_command(
if resolved_runtime.provider == ProviderKind::WanjieArk {
cmd.env("WANJIE_ARK_API_KEY", api_key);
}
if resolved_runtime.provider == ProviderKind::Volcengine {
cmd.env("VOLCENGINE_API_KEY", api_key);
}
let source = resolved_runtime
.api_key_source
.unwrap_or(RuntimeApiKeySource::Env)
Expand Down Expand Up @@ -1489,6 +1497,9 @@ fn build_tui_command(
if resolved_runtime.provider == ProviderKind::WanjieArk {
cmd.env("WANJIE_ARK_API_KEY", api_key);
}
if resolved_runtime.provider == ProviderKind::Volcengine {
cmd.env("VOLCENGINE_API_KEY", api_key);
}
cmd.env("DEEPSEEK_API_KEY_SOURCE", "cli");
}
if let Some(base_url) = cli.base_url.as_ref() {
Expand Down
58 changes: 57 additions & 1 deletion crates/config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -65,6 +67,8 @@ pub enum ProviderKind {
alias = "wanjie_maas"
)]
WanjieArk,
#[serde(alias = "volcengine-ark", alias = "volcengine_ark", alias = "ark")]
Volcengine,
Openrouter,
Novita,
Fireworks,
Expand All @@ -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",
Expand All @@ -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),
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -462,6 +472,9 @@ impl ConfigToml {
"providers.wanjie_ark.api_key" => self.providers.wanjie_ark.api_key.clone(),
"providers.wanjie_ark.base_url" => self.providers.wanjie_ark.base_url.clone(),
"providers.wanjie_ark.model" => self.providers.wanjie_ark.model.clone(),
"providers.volcengine.api_key" => self.providers.volcengine.api_key.clone(),
"providers.volcengine.base_url" => self.providers.volcengine.base_url.clone(),
"providers.volcengine.model" => self.providers.volcengine.model.clone(),
"providers.wanjie_ark.http_headers" => {
serialize_http_headers(&self.providers.wanjie_ark.http_headers)
}
Expand Down Expand Up @@ -584,6 +597,15 @@ impl ConfigToml {
"providers.wanjie_ark.model" => {
self.providers.wanjie_ark.model = Some(value.to_string());
}
"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.http_headers" => {
self.providers.wanjie_ark.http_headers = parse_http_headers(value)?;
}
Expand Down Expand Up @@ -722,6 +744,9 @@ impl ConfigToml {
"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,
"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.http_headers" => {
self.providers.wanjie_ark.http_headers.clear();
}
Expand Down Expand Up @@ -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));
}
Expand All @@ -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);
}
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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>,
Expand All @@ -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>,
Expand All @@ -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"))
Expand Down Expand Up @@ -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"))
Expand Down Expand Up @@ -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(),
Expand All @@ -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,
}
}
Expand Down Expand Up @@ -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>,
Expand Down Expand Up @@ -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"),
Expand Down Expand Up @@ -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());
Expand Down
5 changes: 5 additions & 0 deletions crates/secrets/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Loading