feat(provider): add dedicated kimi-code provider support
This commit is contained in:
parent
ce104bed45
commit
88dcd17a30
3 changed files with 91 additions and 9 deletions
|
|
@ -495,6 +495,7 @@ fn default_model_for_provider(provider: &str) -> String {
|
|||
"groq" => "llama-3.3-70b-versatile".into(),
|
||||
"deepseek" => "deepseek-chat".into(),
|
||||
"gemini" => "gemini-2.5-pro".into(),
|
||||
"kimi-code" => "kimi-for-coding".into(),
|
||||
_ => "anthropic/claude-sonnet-4.5".into(),
|
||||
}
|
||||
}
|
||||
|
|
@ -1427,6 +1428,10 @@ fn setup_provider(workspace_dir: &Path) -> Result<(String, String, String, Optio
|
|||
("bedrock", "Amazon Bedrock — AWS managed models"),
|
||||
],
|
||||
3 => vec![
|
||||
(
|
||||
"kimi-code",
|
||||
"Kimi Code — coding-optimized Kimi API (KimiCLI)",
|
||||
),
|
||||
("moonshot", "Moonshot — Kimi API (China endpoint)"),
|
||||
(
|
||||
"moonshot-intl",
|
||||
|
|
@ -4611,6 +4616,7 @@ mod tests {
|
|||
assert_eq!(default_model_for_provider("zai-cn"), "glm-5");
|
||||
assert_eq!(default_model_for_provider("gemini"), "gemini-2.5-pro");
|
||||
assert_eq!(default_model_for_provider("google"), "gemini-2.5-pro");
|
||||
assert_eq!(default_model_for_provider("kimi-code"), "kimi-for-coding");
|
||||
assert_eq!(
|
||||
default_model_for_provider("google-gemini"),
|
||||
"gemini-2.5-pro"
|
||||
|
|
|
|||
|
|
@ -8,7 +8,10 @@ use crate::providers::traits::{
|
|||
};
|
||||
use async_trait::async_trait;
|
||||
use futures_util::{stream, StreamExt};
|
||||
use reqwest::Client;
|
||||
use reqwest::{
|
||||
header::{HeaderMap, HeaderValue, USER_AGENT},
|
||||
Client,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// A provider that speaks the OpenAI-compatible chat completions API.
|
||||
|
|
@ -22,6 +25,7 @@ pub struct OpenAiCompatibleProvider {
|
|||
/// When false, do not fall back to /v1/responses on chat completions 404.
|
||||
/// GLM/Zhipu does not support the responses API.
|
||||
supports_responses_fallback: bool,
|
||||
user_agent: Option<String>,
|
||||
}
|
||||
|
||||
/// How the provider expects the API key to be sent.
|
||||
|
|
@ -42,13 +46,7 @@ impl OpenAiCompatibleProvider {
|
|||
credential: Option<&str>,
|
||||
auth_style: AuthStyle,
|
||||
) -> Self {
|
||||
Self {
|
||||
name: name.to_string(),
|
||||
base_url: base_url.trim_end_matches('/').to_string(),
|
||||
credential: credential.map(ToString::to_string),
|
||||
auth_header: auth_style,
|
||||
supports_responses_fallback: true,
|
||||
}
|
||||
Self::new_with_options(name, base_url, credential, auth_style, true, None)
|
||||
}
|
||||
|
||||
/// Same as `new` but skips the /v1/responses fallback on 404.
|
||||
|
|
@ -58,17 +56,69 @@ impl OpenAiCompatibleProvider {
|
|||
base_url: &str,
|
||||
credential: Option<&str>,
|
||||
auth_style: AuthStyle,
|
||||
) -> Self {
|
||||
Self::new_with_options(name, base_url, credential, auth_style, false, None)
|
||||
}
|
||||
|
||||
/// Create a provider with a custom User-Agent header.
|
||||
///
|
||||
/// Some providers (for example Kimi Code) require a specific User-Agent
|
||||
/// for request routing and policy enforcement.
|
||||
pub fn new_with_user_agent(
|
||||
name: &str,
|
||||
base_url: &str,
|
||||
credential: Option<&str>,
|
||||
auth_style: AuthStyle,
|
||||
user_agent: &str,
|
||||
) -> Self {
|
||||
Self::new_with_options(
|
||||
name,
|
||||
base_url,
|
||||
credential,
|
||||
auth_style,
|
||||
true,
|
||||
Some(user_agent),
|
||||
)
|
||||
}
|
||||
|
||||
fn new_with_options(
|
||||
name: &str,
|
||||
base_url: &str,
|
||||
credential: Option<&str>,
|
||||
auth_style: AuthStyle,
|
||||
supports_responses_fallback: bool,
|
||||
user_agent: Option<&str>,
|
||||
) -> Self {
|
||||
Self {
|
||||
name: name.to_string(),
|
||||
base_url: base_url.trim_end_matches('/').to_string(),
|
||||
credential: credential.map(ToString::to_string),
|
||||
auth_header: auth_style,
|
||||
supports_responses_fallback: false,
|
||||
supports_responses_fallback,
|
||||
user_agent: user_agent.map(ToString::to_string),
|
||||
}
|
||||
}
|
||||
|
||||
fn http_client(&self) -> Client {
|
||||
if let Some(ua) = self.user_agent.as_deref() {
|
||||
let mut headers = HeaderMap::new();
|
||||
if let Ok(value) = HeaderValue::from_str(ua) {
|
||||
headers.insert(USER_AGENT, value);
|
||||
}
|
||||
|
||||
let builder = Client::builder()
|
||||
.timeout(std::time::Duration::from_secs(120))
|
||||
.connect_timeout(std::time::Duration::from_secs(10))
|
||||
.default_headers(headers);
|
||||
let builder =
|
||||
crate::config::apply_runtime_proxy_to_builder(builder, "provider.compatible");
|
||||
|
||||
return builder.build().unwrap_or_else(|error| {
|
||||
tracing::warn!("Failed to build proxied timeout client with user-agent: {error}");
|
||||
Client::new()
|
||||
});
|
||||
}
|
||||
|
||||
crate::config::build_runtime_proxy_client_with_timeouts("provider.compatible", 120, 10)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -314,6 +314,9 @@ fn resolve_provider_credential(name: &str, credential_override: Option<&str>) ->
|
|||
"perplexity" => vec!["PERPLEXITY_API_KEY"],
|
||||
"cohere" => vec!["COHERE_API_KEY"],
|
||||
name if is_moonshot_alias(name) => vec!["MOONSHOT_API_KEY"],
|
||||
"kimi-code" | "kimi_coding" | "kimi_for_coding" => {
|
||||
vec!["KIMI_CODE_API_KEY", "MOONSHOT_API_KEY"]
|
||||
}
|
||||
name if is_glm_alias(name) => vec!["GLM_API_KEY"],
|
||||
name if is_minimax_alias(name) => vec!["MINIMAX_API_KEY"],
|
||||
name if is_qianfan_alias(name) => vec!["QIANFAN_API_KEY"],
|
||||
|
|
@ -432,6 +435,15 @@ pub fn create_provider_with_url(
|
|||
key,
|
||||
AuthStyle::Bearer,
|
||||
))),
|
||||
"kimi-code" | "kimi_coding" | "kimi_for_coding" => Ok(Box::new(
|
||||
OpenAiCompatibleProvider::new_with_user_agent(
|
||||
"Kimi Code",
|
||||
"https://api.kimi.com/coding/v1",
|
||||
key,
|
||||
AuthStyle::Bearer,
|
||||
"KimiCLI/0.77",
|
||||
),
|
||||
)),
|
||||
"synthetic" => Ok(Box::new(OpenAiCompatibleProvider::new(
|
||||
"Synthetic", "https://api.synthetic.com", key, AuthStyle::Bearer,
|
||||
))),
|
||||
|
|
@ -787,6 +799,12 @@ pub fn list_providers() -> Vec<ProviderInfo> {
|
|||
aliases: &["kimi"],
|
||||
local: false,
|
||||
},
|
||||
ProviderInfo {
|
||||
name: "kimi-code",
|
||||
display_name: "Kimi Code",
|
||||
aliases: &["kimi_coding", "kimi_for_coding"],
|
||||
local: false,
|
||||
},
|
||||
ProviderInfo {
|
||||
name: "synthetic",
|
||||
display_name: "Synthetic",
|
||||
|
|
@ -1067,6 +1085,13 @@ mod tests {
|
|||
assert!(create_provider("kimi-cn", Some("key")).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn factory_kimi_code() {
|
||||
assert!(create_provider("kimi-code", Some("key")).is_ok());
|
||||
assert!(create_provider("kimi_coding", Some("key")).is_ok());
|
||||
assert!(create_provider("kimi_for_coding", Some("key")).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn factory_synthetic() {
|
||||
assert!(create_provider("synthetic", Some("key")).is_ok());
|
||||
|
|
@ -1399,6 +1424,7 @@ mod tests {
|
|||
"cloudflare",
|
||||
"moonshot",
|
||||
"moonshot-intl",
|
||||
"kimi-code",
|
||||
"moonshot-cn",
|
||||
"synthetic",
|
||||
"opencode",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue