Skip to content
Closed
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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,10 @@ codewhale --provider nvidia-nim
# AtlasCloud
codewhale auth set --provider atlascloud --api-key "YOUR_ATLASCLOUD_API_KEY"
codewhale --provider atlascloud
# AtlasCloud provider-hinted resolution also supports the validated Atlas chat
# model pool in the static registry, for example:
codewhale --provider atlascloud --model openai/gpt-5.2-chat
codewhale --provider atlascloud --model qwen/qwen3-vl-235b-a22b-thinking

# Wanjie Ark
codewhale auth set --provider wanjie-ark --api-key "YOUR_WANJIE_API_KEY"
Expand Down
127 changes: 106 additions & 21 deletions crates/agent/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,92 @@ pub struct ModelRegistry {
alias_map: HashMap<String, usize>,
}

const ATLASCLOUD_VALIDATED_MODELS: &[&str] = &[
"deepseek-ai/DeepSeek-V3-0324",
"deepseek-ai/deepseek-r1-0528",
"moonshotai/Kimi-K2-Instruct",
"Qwen/Qwen3-Coder",
"Qwen/Qwen3-235B-A22B-Instruct-2507",
"deepseek-ai/DeepSeek-V3.1",
"moonshotai/Kimi-K2-Instruct-0905",
"Qwen/Qwen3-Next-80B-A3B-Instruct",
"Qwen/Qwen3-Next-80B-A3B-Thinking",
"Qwen/Qwen3-30B-A3B-Instruct-2507",
"deepseek-ai/DeepSeek-V3.1-Terminus",
"deepseek-ai/DeepSeek-V3.2-Exp",
"zai-org/GLM-4.6",
"MiniMaxAI/MiniMax-M2",
"Qwen/Qwen3-VL-235B-A22B-Instruct",
"moonshotai/Kimi-K2-Thinking",
"google/gemini-2.5-flash",
"google/gemini-2.5-flash-lite",
"openai/gpt-5.1",
"openai/gpt-5.1-chat",
"openai/gpt-4o",
"openai/gpt-4o-mini",
"openai/gpt-4.1",
"openai/gpt-4.1-mini",
"openai/gpt-4.1-nano",
"openai/o1",
"openai/o3",
"openai/o3-mini",
"openai/o4-mini",
"anthropic/claude-sonnet-4.5-20250929",
"deepseek-ai/deepseek-v3.2",
"openai/gpt-5",
"openai/gpt-5-chat",
"openai/gpt-5-mini",
"openai/gpt-5-nano",
"openai/gpt-5.2",
"openai/gpt-5.2-chat",
"google/gemini-2.5-pro",
"anthropic/claude-opus-4.5-20251101",
"google/gemini-3-flash-preview",
"zai-org/glm-4.7",
"minimaxai/minimax-m2.1",
"google/gemini-2.0-flash",
"qwen/qwen3-8b",
"qwen/qwen3-235b-a22b-thinking-2507",
"qwen/qwen3-vl-235b-a22b-thinking",
"qwen/qwen3-30b-a3b",
"qwen/qwen3-30b-a3b-thinking-2507",
"deepseek-ai/deepseek-ocr",
"xai/grok-4-0709",
];
Comment on lines +52 to +103
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Mixed casing within ATLASCLOUD_VALIDATED_MODELS produces inconsistent API IDs

The list mixes Pascal-cased IDs ("deepseek-ai/DeepSeek-V3-0324", "Qwen/Qwen3-Coder", "MiniMaxAI/MiniMax-M2", "moonshotai/Kimi-K2-Instruct") with lower-snake-cased IDs ("qwen/qwen3-8b", "deepseek-ai/deepseek-r1-0528", "minimaxai/minimax-m2.1"). When a provider-hinted resolution succeeds, the returned resolved.id is the stored ID verbatim (the preserve_requested_model_id_case path is only taken in the alias-map branch, not here). That means a request for qwen/qwen3-coder routes to AtlasCloud with the model string "Qwen/Qwen3-Coder", while a request for qwen/qwen3-8b routes with "qwen/qwen3-8b". If the AtlasCloud endpoint is case-sensitive (which REST APIs typically are), requests for the Pascal-cased entries will behave differently than the lowercase ones.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Codex Fix in Claude Code Fix in Cursor

Comment on lines +101 to +103
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 deepseek-ai/deepseek-ocr is an OCR-specialised model that does not expose a chat-with-tools API. Marking it as supports_tools: true and supports_reasoning: true will cause the agent to send tool-call payloads to it, which the endpoint will reject or silently ignore. The atlascloud_model helper currently applies these flags uniformly to every entry in ATLASCLOUD_VALIDATED_MODELS, so there is no way to flag per-model capability differences. At minimum, deepseek-ocr should be excluded from the blanket helper.

Suggested change
"deepseek-ai/deepseek-ocr",
"xai/grok-4-0709",
];
// deepseek-ocr is an OCR-only model; add it manually with appropriate
// capability flags instead of through the blanket atlascloud_model helper.
"xai/grok-4-0709",
];

Fix in Codex Fix in Claude Code Fix in Cursor


fn atlascloud_model(id: &str, aliases: &[&str]) -> ModelInfo {
ModelInfo {
id: id.to_string(),
provider: ProviderKind::Atlascloud,
aliases: aliases.iter().map(|alias| (*alias).to_string()).collect(),
supports_tools: true,
supports_reasoning: true,
}
}

fn atlascloud_models() -> Vec<ModelInfo> {
let mut models = vec![
atlascloud_model(
"deepseek-ai/deepseek-v4-flash",
&["deepseek-v4-flash", "atlascloud-deepseek-v4-flash"],
),
atlascloud_model(
"deepseek-ai/deepseek-v4-pro",
&["deepseek-v4-pro", "atlascloud-deepseek-v4-pro"],
),
];
models.extend(
ATLASCLOUD_VALIDATED_MODELS
.iter()
.map(|model_id| atlascloud_model(model_id, &[])),
);
models
}

/// Creates a registry pre-populated with all built-in models and their aliases.
impl Default for ModelRegistry {
fn default() -> Self {
let models = vec![
let mut models = vec![
ModelInfo {
id: "deepseek-v4-pro".to_string(),
provider: ProviderKind::Deepseek,
Expand Down Expand Up @@ -111,26 +193,6 @@ impl Default for ModelRegistry {
supports_tools: true,
supports_reasoning: true,
},
ModelInfo {
id: "deepseek-ai/deepseek-v4-flash".to_string(),
provider: ProviderKind::Atlascloud,
aliases: vec![
"deepseek-v4-flash".to_string(),
"atlascloud-deepseek-v4-flash".to_string(),
],
supports_tools: true,
supports_reasoning: true,
},
ModelInfo {
id: "deepseek-ai/deepseek-v4-pro".to_string(),
provider: ProviderKind::Atlascloud,
aliases: vec![
"deepseek-v4-pro".to_string(),
"atlascloud-deepseek-v4-pro".to_string(),
],
supports_tools: true,
supports_reasoning: true,
},
ModelInfo {
id: "deepseek-reasoner".to_string(),
provider: ProviderKind::WanjieArk,
Expand Down Expand Up @@ -426,6 +488,7 @@ impl Default for ModelRegistry {
supports_reasoning: false,
},
];
models.splice(6..6, atlascloud_models());
Self::new(models)
}
}
Expand Down Expand Up @@ -621,6 +684,28 @@ mod tests {
assert_eq!(resolved.resolved.id, "deepseek-ai/deepseek-v4-flash");
}

#[test]
fn atlascloud_validated_model_resolves_when_provider_hinted() {
let registry = ModelRegistry::default();
let resolved =
registry.resolve(Some("openai/gpt-5.2-chat"), Some(ProviderKind::Atlascloud));

assert_eq!(resolved.resolved.provider, ProviderKind::Atlascloud);
assert_eq!(resolved.resolved.id, "openai/gpt-5.2-chat");
}

#[test]
fn atlascloud_registry_includes_validated_model_pool() {
let registry = ModelRegistry::default();
let atlas_count = registry
.list()
.into_iter()
.filter(|model| model.provider == ProviderKind::Atlascloud)
.count();

assert!(atlas_count >= ATLASCLOUD_VALIDATED_MODELS.len() + 2);
}

#[test]
fn deepseek_v4_pro_alias_resolves_to_atlascloud_when_provider_hinted() {
let registry = ModelRegistry::default();
Expand Down
2 changes: 1 addition & 1 deletion docs/PROVIDERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ endpoint.
| `deepseek` | `[providers.deepseek]` | `DEEPSEEK_API_KEY` | `CODEWHALE_BASE_URL` / `DEEPSEEK_BASE_URL`; default `https://api.deepseek.com/beta` | `deepseek-v4-pro`, `deepseek-v4-flash`; compatibility aliases `deepseek-chat`, `deepseek-reasoner` | First-class default. Beta URL enables strict tool mode, chat prefix completion, and FIM completion. Set `https://api.deepseek.com` or `/v1` explicitly to opt out of beta-only features. |
| `nvidia-nim` | `[providers.nvidia_nim]` | `NVIDIA_API_KEY`, `NVIDIA_NIM_API_KEY`, fallback `DEEPSEEK_API_KEY` | `NVIDIA_NIM_BASE_URL`, `NIM_BASE_URL`, `NVIDIA_BASE_URL`; default `https://integrate.api.nvidia.com/v1` | `deepseek-ai/deepseek-v4-pro`, `deepseek-ai/deepseek-v4-flash` | Hosted DeepSeek V4 through NVIDIA NIM. `NVIDIA_NIM_MODEL` is accepted by the TUI config path. |
| `openai` | `[providers.openai]` | `OPENAI_API_KEY` | `OPENAI_BASE_URL`; default `https://api.openai.com/v1` | Registry entries: `deepseek-v4-pro`, `deepseek-v4-flash`; default config model `deepseek-v4-pro` | Generic OpenAI-compatible route for gateways and custom endpoints. Use this for explicit third-party OpenAI-compatible routes instead of inventing a new provider ID. `OPENAI_MODEL` is accepted. |
| `atlascloud` | `[providers.atlascloud]` | `ATLASCLOUD_API_KEY` | `ATLASCLOUD_BASE_URL`; default `https://api.atlascloud.ai/v1` | `deepseek-ai/deepseek-v4-flash`, `deepseek-ai/deepseek-v4-pro` | OpenAI-compatible hosted route. `ATLASCLOUD_MODEL` is accepted by the TUI config path, and the static `ModelRegistry` includes AtlasCloud fallback rows for CLI model resolution. |
| `atlascloud` | `[providers.atlascloud]` | `ATLASCLOUD_API_KEY` | `ATLASCLOUD_BASE_URL`; default `https://api.atlascloud.ai/v1` | Default `deepseek-ai/deepseek-v4-flash`; static registry also includes the validated Atlas chat model pool | OpenAI-compatible hosted route. `ATLASCLOUD_MODEL` is accepted by the TUI config path, and the static `ModelRegistry` includes AtlasCloud fallback rows for CLI model resolution and provider-hinted model resolution. |
| `wanjie-ark` | `[providers.wanjie_ark]` | `WANJIE_ARK_API_KEY`, `WANJIE_API_KEY`, `WANJIE_MAAS_API_KEY` | `WANJIE_ARK_BASE_URL`, `WANJIE_BASE_URL`, `WANJIE_MAAS_BASE_URL`; default `https://maas-openapi.wanjiedata.com/api/v1` | `deepseek-reasoner` | OpenAI-compatible hosted route. `WANJIE_ARK_MODEL`, `WANJIE_MODEL`, and `WANJIE_MAAS_MODEL` are accepted. |
| `volcengine` | `[providers.volcengine]` | `VOLCENGINE_API_KEY`, `VOLCENGINE_ARK_API_KEY`, `ARK_API_KEY` | `VOLCENGINE_BASE_URL`, `VOLCENGINE_ARK_BASE_URL`, `ARK_BASE_URL`; default `https://ark.cn-beijing.volces.com/api/coding/v3` | `DeepSeek-V4-Pro`, `DeepSeek-V4-Flash` | Volcengine/Volcano Engine Ark OpenAI-compatible coding endpoint. `VOLCENGINE_MODEL` and `VOLCENGINE_ARK_MODEL` are accepted. |
| `openrouter` | `[providers.openrouter]` | `OPENROUTER_API_KEY` | `OPENROUTER_BASE_URL`; default `https://openrouter.ai/api/v1` | `deepseek/deepseek-v4-pro`, `deepseek/deepseek-v4-flash`; recent large IDs include `arcee-ai/trinity-large-thinking`, `minimax/minimax-m3`, `xiaomi/mimo-v2.5-pro`, `qwen/qwen3.6-35b-a3b`, `google/gemma-4-31b-it`, `z-ai/glm-5.1`, `moonshotai/kimi-k2.6` | Additive open-model routing layer. It does not replace DeepSeek; it lets users route supported model IDs through OpenRouter when they choose it. |
Expand Down