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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Add provider-native search for exact Moonshot K3 Formula, legacy K2.6
built-in search, and Kimi Code membership `/search` routes. Treat the exact
Moonshot China endpoint as a first-party direct route.
- Z.ai `GLM-5.3-Flash` and OpenRouter `z-ai/glm-5.3-flash` are first-class
picker rows (`/model GLM-5.3-Flash`). Flash is the faster/explore sibling
of `GLM-5.3`; the Z.ai default stays `GLM-5.3`. List price is $0.15/$0.50
Expand Down
5 changes: 4 additions & 1 deletion crates/config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4862,7 +4862,10 @@ pub fn provider_base_url_is_official(provider: ProviderKind, base_url: &str) ->
"https://api.siliconflow.com/v1" | "https://api.siliconflow.cn/v1"
),
ProviderKind::Moonshot => {
normalized == DEFAULT_MOONSHOT_BASE_URL || moonshot_base_url_uses_kimi_code(base_url)
matches!(
normalized.as_str(),
DEFAULT_MOONSHOT_BASE_URL | MOONSHOT_CN_BASE_URL
) || moonshot_base_url_uses_kimi_code(base_url)
}
ProviderKind::XiaomiMimo => {
xiaomi_mimo_base_url_uses_token_plan(base_url)
Expand Down
17 changes: 11 additions & 6 deletions crates/config/src/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -529,7 +529,9 @@ pub fn migrates_legacy_ollama_cloud_route(kind: ProviderKind, base_url: &str) ->
/// neighboring Moonshot paths do not inherit direct-K3 wire semantics.
#[must_use]
pub fn is_exact_moonshot_platform_route(kind: ProviderKind, base_url: &str) -> bool {
kind == ProviderKind::Moonshot && is_exact_https_route(base_url, "api.moonshot.ai", "v1")
kind == ProviderKind::Moonshot
&& (is_exact_https_route(base_url, "api.moonshot.ai", "v1")
|| is_exact_https_route(base_url, "api.moonshot.cn", "v1"))
}

/// Whether a configured route is exactly xAI's first-party OpenAI-compatible
Expand Down Expand Up @@ -1961,10 +1963,12 @@ mod tests {

#[test]
fn direct_moonshot_route_matching_is_exact() {
assert!(is_exact_moonshot_platform_route(
ProviderKind::Moonshot,
"HTTPS://API.MOONSHOT.AI/v1/"
));
for route in ["HTTPS://API.MOONSHOT.AI/v1/", "HTTPS://API.MOONSHOT.CN/v1/"] {
assert!(is_exact_moonshot_platform_route(
ProviderKind::Moonshot,
route
));
}
for neighboring_route in [
"https://api.moonshot.ai/V1",
"http://api.moonshot.ai/v1",
Expand All @@ -1973,6 +1977,7 @@ mod tests {
"https://api.moonshot.ai/v1#fragment",
"https://api.moonshot.ai/v1//",
"https://api.moonshot.ai/v1/chat/completions",
"https://api.moonshot.cn/v1/chat/completions",
"https://api.kimi.com/coding/v1",
] {
assert!(
Expand All @@ -1982,7 +1987,7 @@ mod tests {
}
assert!(!is_exact_moonshot_platform_route(
ProviderKind::Openai,
DEFAULT_MOONSHOT_BASE_URL
crate::MOONSHOT_CN_BASE_URL
));
}

Expand Down
1 change: 1 addition & 0 deletions crates/config/src/provider_defaults.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ pub(crate) const ARCEE_TRINITY_MINI_MODEL: &str = "trinity-mini";
pub(crate) const DEFAULT_MOONSHOT_MODEL: &str = "kimi-k2.7-code";
pub(crate) const MOONSHOT_KIMI_K2_6_MODEL: &str = "kimi-k2.6";
pub(crate) const DEFAULT_MOONSHOT_BASE_URL: &str = "https://api.moonshot.ai/v1";
pub(crate) const MOONSHOT_CN_BASE_URL: &str = "https://api.moonshot.cn/v1";
pub(crate) const DEFAULT_KIMI_CODE_MODEL: &str = "kimi-for-coding";
pub(crate) const DEFAULT_KIMI_CODE_BASE_URL: &str = "https://api.kimi.com/coding/v1";
pub(crate) const DEFAULT_SGLANG_MODEL: &str = "deepseek-ai/DeepSeek-V4-Pro";
Expand Down
63 changes: 63 additions & 0 deletions crates/config/src/route/capabilities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

use serde::{Deserialize, Serialize};

use crate::{DEFAULT_KIMI_CODE_BASE_URL, ProviderKind};

/// Whether a resolved provider/model offering supports one capability.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
Expand Down Expand Up @@ -50,6 +52,7 @@ impl CapabilityState {
/// - OpenAI Responses web search: <https://developers.openai.com/api/docs/guides/tools-web-search>
/// - Anthropic web search tool: <https://platform.claude.com/docs/en/agents-and-tools/tool-use/web-search-tool>
/// - xAI web search tool: <https://docs.x.ai/developers/tools/web-search>
/// - Kimi built-in and Formula web search: <https://platform.kimi.ai/docs/guide/use-web-search>
#[must_use]
pub(crate) fn documented_server_side_web_search(
provider_id: &str,
Expand All @@ -74,6 +77,7 @@ pub(crate) fn documented_server_side_web_search(
| "claude-sonnet-4-6"
),
"xai" => matches!(wire_model_id.as_str(), "grok-4.6" | "grok-4.5"),
"moonshot" => matches!(wire_model_id.as_str(), "kimi-k3" | "kimi-k2.6"),
_ => false,
};
if supported {
Expand All @@ -83,6 +87,34 @@ pub(crate) fn documented_server_side_web_search(
}
}

/// Return the native-search fact for exact Moonshot direct and Kimi Code
/// product routes. Adjacent coding paths and cross-product model ids remain
/// unknown even though they share one provider identity.
#[must_use]
pub(crate) fn documented_moonshot_web_search_for_route(
provider: ProviderKind,
wire_model_id: &str,
base_url: &str,
) -> CapabilityState {
if provider != ProviderKind::Moonshot {
return CapabilityState::Unknown;
}
let normalized = base_url.trim().trim_end_matches('/').to_ascii_lowercase();
let model = wire_model_id.trim().to_ascii_lowercase();
if normalized == DEFAULT_KIMI_CODE_BASE_URL
&& matches!(
model.as_str(),
"k3" | "k3-256k" | "kimi-for-coding" | "kimi-for-coding-highspeed"
)
{
return CapabilityState::Supported;
}
if crate::provider::is_exact_moonshot_platform_route(provider, base_url) {
return documented_server_side_web_search("moonshot", &model);
}
CapabilityState::Unknown
}

/// Capability facts owned by one provider/model route offering.
///
/// Fields without a current authoritative catalog source remain `Unknown`.
Expand Down Expand Up @@ -159,6 +191,10 @@ mod tests {
documented_server_side_web_search("anthropic", "claude-sonnet-4-6"),
CapabilityState::Supported
);
assert_eq!(
documented_server_side_web_search("moonshot", "kimi-k3"),
CapabilityState::Supported
);

for (provider, model) in [
("openrouter", "openai/gpt-5.6"),
Expand All @@ -168,6 +204,7 @@ mod tests {
("xai", "grok-4.6-latest"),
("xai", "grok-4.5-fast"),
("anthropic", "claude-haiku-4-5"),
("moonshot", "kimi-k2.7-code"),
] {
assert_eq!(
documented_server_side_web_search(provider, model),
Expand All @@ -176,4 +213,30 @@ mod tests {
);
}
}

#[test]
fn moonshot_route_fact_is_exact_to_product_and_model() {
for (model, base_url) in [
("kimi-k3", "https://api.moonshot.ai/v1"),
("kimi-k3", "https://api.moonshot.cn/v1"),
("kimi-k2.6", "https://api.moonshot.ai/v1"),
("k3", DEFAULT_KIMI_CODE_BASE_URL),
("kimi-for-coding", DEFAULT_KIMI_CODE_BASE_URL),
] {
assert_eq!(
documented_moonshot_web_search_for_route(ProviderKind::Moonshot, model, base_url,),
CapabilityState::Supported
);
}
for (model, base_url) in [
("kimi-k3", "https://api.kimi.com/coding/v2"),
("kimi-k2.6", "https://api.kimi.com/coding/v1/preview"),
("k3", "https://api.moonshot.ai/v1"),
] {
assert_eq!(
documented_moonshot_web_search_for_route(ProviderKind::Moonshot, model, base_url,),
CapabilityState::Unknown
);
}
}
}
13 changes: 12 additions & 1 deletion crates/config/src/route/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ use super::candidate::{
LimitField, PricingSku, ReadyRouteCandidate, ResolvedAuthSource, ResolvedEndpoint,
SourcedLimitOverride, ValidationReport,
};
use super::capabilities::RouteCapabilities;
use super::capabilities::{RouteCapabilities, documented_moonshot_web_search_for_route};
use super::descriptor::ProviderDescriptor;
use super::errors::RouteError;
use super::ids::{LogicalModelRef, ModelId, ProviderId, WireModelId};
Expand Down Expand Up @@ -243,6 +243,17 @@ impl RouteResolver {
selected.capabilities = RouteCapabilities::default();
selected.pricing = PricingSku::UnknownOrStale;
}
if provider_kind == ProviderKind::Moonshot {
let effective_base_url = req
.base_url_override
.as_deref()
.unwrap_or_else(|| descriptor.default_base_url());
selected.capabilities.server_side_web_search = documented_moonshot_web_search_for_route(
provider_kind,
selected.wire_model_id.as_str(),
effective_base_url,
);
}

let protocol = descriptor
.protocol_for_endpoint(&selected.endpoint_key)
Expand Down
51 changes: 51 additions & 0 deletions crates/config/src/route/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1421,6 +1421,57 @@ fn provider_native_web_search_requires_exact_direct_endpoint_offering() {
);
}

#[test]
fn moonshot_native_search_requires_exact_product_model_pair() {
use crate::route::CapabilityState;

let resolver = RouteResolver::new();
for (model, base_url) in [
("kimi-k3", "https://api.moonshot.ai/v1"),
("kimi-k3", "https://api.moonshot.cn/v1"),
("kimi-k2.6", "https://api.moonshot.ai/v1"),
("k3", "https://api.kimi.com/coding/v1"),
("kimi-for-coding", "https://api.kimi.com/coding/v1"),
] {
let direct = resolver
.resolve(&RouteRequest {
explicit_provider: Some(ProviderKind::Moonshot),
model_selector: Some(LogicalModelRef::from(model)),
saved_provider_model: None,
base_url_override: Some(base_url.to_string()),
limit_overrides: Vec::new(),
})
.expect("documented Moonshot/Kimi route resolves");
assert_eq!(
direct.capabilities().server_side_web_search,
CapabilityState::Supported,
"{base_url}/{model} should expose native search"
);
}

for (model, base_url) in [
("kimi-k3", "https://api.kimi.com/coding/v2"),
("kimi-k2.6", "https://api.kimi.com/coding/v1/preview"),
("k3", "https://api.moonshot.ai/v1"),
("kimi-k2.7-code", "https://api.moonshot.ai/v1"),
] {
let adjacent = resolver
.resolve(&RouteRequest {
explicit_provider: Some(ProviderKind::Moonshot),
model_selector: Some(LogicalModelRef::from(model)),
saved_provider_model: None,
base_url_override: Some(base_url.to_string()),
limit_overrides: Vec::new(),
})
.expect("adjacent Moonshot/Kimi route resolves");
assert_eq!(
adjacent.capabilities().server_side_web_search,
CapabilityState::Unknown,
"{base_url}/{model} must remain fail-closed"
);
}
}

#[test]
fn priced_offering_yields_token_pricing_sku() {
use super::candidate::PricingSku;
Expand Down
3 changes: 3 additions & 0 deletions crates/tui/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Add provider-native search for exact Moonshot K3 Formula, legacy K2.6
built-in search, and Kimi Code membership `/search` routes. Treat the exact
Moonshot China endpoint as a first-party direct route.
- Z.ai `GLM-5.3-Flash` and OpenRouter `z-ai/glm-5.3-flash` are first-class
picker rows (`/model GLM-5.3-Flash`). Flash is the faster/explore sibling
of `GLM-5.3`; the Z.ai default stays `GLM-5.3`. List price is $0.15/$0.50
Expand Down
Loading
Loading