From cec8d17ca851a302f9b99c8b994b72d1e8fc7a15 Mon Sep 17 00:00:00 2001 From: Yeachan-Heo Date: Thu, 16 Apr 2026 19:44:21 +0000 Subject: [PATCH] Implement US-023: Add automatic routing for kimi models to DashScope Changes in rust/crates/api/src/providers/mod.rs: - Add 'kimi' alias to MODEL_REGISTRY resolving to 'kimi-k2.5' with DashScope config - Add kimi/kimi- prefix routing to DashScope endpoint in metadata_for_model() - Add resolve_model_alias() handling for kimi -> kimi-k2.5 - Add unit tests: kimi_prefix_routes_to_dashscope, kimi_alias_resolves_to_kimi_k2_5 Users can now use: - --model kimi (resolves to kimi-k2.5) - --model kimi-k2.5 (auto-routes to DashScope) - --model kimi/kimi-k2.5 (explicit provider prefix) All 127 tests pass, clippy clean. Co-Authored-By: Claude Opus 4.6 --- prd.json | 18 ++++++++-- rust/crates/api/src/providers/mod.rs | 52 +++++++++++++++++++++++++++- 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/prd.json b/prd.json index 9cc64a7..115d2cf 100644 --- a/prd.json +++ b/prd.json @@ -315,13 +315,27 @@ ], "passes": true, "priority": "P1" + }, + { + "id": "US-023", + "title": "Add automatic routing for kimi models to DashScope", + "description": "Based on dogfood findings with kimi-k2.5 testing, users must manually prefix with dashscope/kimi-k2.5 instead of just using kimi-k2.5. Add automatic routing for kimi/ and kimi- prefixed models to DashScope (similar to qwen models), and add a 'kimi' alias to the model registry.", + "acceptanceCriteria": [ + "kimi/ and kimi- prefix routing to DashScope in metadata_for_model()", + "'kimi' alias in MODEL_REGISTRY that resolves to 'kimi-k2.5'", + "resolve_model_alias() handles the kimi alias correctly", + "Unit tests for kimi routing (similar to qwen routing tests)", + "All tests pass and clippy is clean" + ], + "passes": true, + "priority": "P1" } ], "metadata": { "lastUpdated": "2026-04-16", "completedStories": ["US-001", "US-002", "US-003", "US-004", "US-005", "US-006", "US-007", "US-008", "US-009", "US-010", "US-011", "US-012", "US-013", "US-014", "US-015", "US-016", "US-017", "US-018", "US-019", "US-020", "US-021", "US-022"], - "inProgressStories": [], - "totalStories": 22, + "inProgressStories": ["US-023"], + "totalStories": 23, "status": "in_progress" } } diff --git a/rust/crates/api/src/providers/mod.rs b/rust/crates/api/src/providers/mod.rs index 58978c8..64cfa8b 100644 --- a/rust/crates/api/src/providers/mod.rs +++ b/rust/crates/api/src/providers/mod.rs @@ -122,6 +122,15 @@ const MODEL_REGISTRY: &[(&str, ProviderMetadata)] = &[ default_base_url: openai_compat::DEFAULT_XAI_BASE_URL, }, ), + ( + "kimi", + ProviderMetadata { + provider: ProviderKind::OpenAi, + auth_env: "DASHSCOPE_API_KEY", + base_url_env: "DASHSCOPE_BASE_URL", + default_base_url: openai_compat::DEFAULT_DASHSCOPE_BASE_URL, + }, + ), ]; #[must_use] @@ -144,7 +153,10 @@ pub fn resolve_model_alias(model: &str) -> String { "grok-2" => "grok-2", _ => trimmed, }, - ProviderKind::OpenAi => trimmed, + ProviderKind::OpenAi => match *alias { + "kimi" => "kimi-k2.5", + _ => trimmed, + }, }) }) .map_or_else(|| trimmed.to_string(), ToOwned::to_owned) @@ -194,6 +206,16 @@ pub fn metadata_for_model(model: &str) -> Option { default_base_url: openai_compat::DEFAULT_DASHSCOPE_BASE_URL, }); } + // Kimi models (kimi-k2.5, kimi-k1.5, etc.) via DashScope compatible-mode. + // Routes kimi/* and kimi-* model names to DashScope endpoint. + if canonical.starts_with("kimi/") || canonical.starts_with("kimi-") { + return Some(ProviderMetadata { + provider: ProviderKind::OpenAi, + auth_env: "DASHSCOPE_API_KEY", + base_url_env: "DASHSCOPE_BASE_URL", + default_base_url: openai_compat::DEFAULT_DASHSCOPE_BASE_URL, + }); + } None } @@ -554,6 +576,34 @@ mod tests { ); } + #[test] + fn kimi_prefix_routes_to_dashscope() { + // Kimi models via DashScope (kimi-k2.5, kimi-k1.5, etc.) + let meta = super::metadata_for_model("kimi-k2.5") + .expect("kimi-k2.5 must resolve to DashScope metadata"); + assert_eq!(meta.auth_env, "DASHSCOPE_API_KEY"); + assert_eq!(meta.base_url_env, "DASHSCOPE_BASE_URL"); + assert!(meta.default_base_url.contains("dashscope.aliyuncs.com")); + assert_eq!(meta.provider, ProviderKind::OpenAi); + + // With provider prefix + let meta2 = super::metadata_for_model("kimi/kimi-k2.5") + .expect("kimi/kimi-k2.5 must resolve to DashScope metadata"); + assert_eq!(meta2.auth_env, "DASHSCOPE_API_KEY"); + assert_eq!(meta2.provider, ProviderKind::OpenAi); + + // Different kimi variants + let meta3 = super::metadata_for_model("kimi-k1.5") + .expect("kimi-k1.5 must resolve to DashScope metadata"); + assert_eq!(meta3.auth_env, "DASHSCOPE_API_KEY"); + } + + #[test] + fn kimi_alias_resolves_to_kimi_k2_5() { + assert_eq!(super::resolve_model_alias("kimi"), "kimi-k2.5"); + assert_eq!(super::resolve_model_alias("KIMI"), "kimi-k2.5"); // case insensitive + } + #[test] fn keeps_existing_max_token_heuristic() { assert_eq!(max_tokens_for_model("opus"), 32_000);