mirror of
https://github.com/katanemo/plano.git
synced 2026-06-05 14:45:15 +02:00
Support Kimi Code API for Claude Code routing (#951)
* Support Kimi Code API and Claude Code protocol compatibility Co-authored-by: Musa <musa@spherrrical.dev> * Fix black formatting in config_generator Co-authored-by: Musa <musa@spherrrical.dev> * Warn when stripping unsupported Kimi Code request fields Co-authored-by: Musa <musa@spherrrical.dev> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
This commit is contained in:
parent
554a3d1f6a
commit
f3d6ea41ad
8 changed files with 183 additions and 2 deletions
|
|
@ -1,3 +1,4 @@
|
|||
use log::warn;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use serde_with::skip_serializing_none;
|
||||
|
|
@ -136,6 +137,39 @@ impl ChatCompletionsRequest {
|
|||
self.temperature = Some(1.0);
|
||||
}
|
||||
}
|
||||
|
||||
/// Strip request fields that Kimi Code API (`kimi-for-coding`) rejects or mishandles.
|
||||
pub fn normalize_for_kimi_code_api(&mut self) {
|
||||
if self.stream_options.is_some() {
|
||||
warn!("kimi-for-coding: stripping unsupported stream_options from upstream request");
|
||||
self.stream_options = None;
|
||||
}
|
||||
if self.reasoning_effort.is_some() {
|
||||
warn!(
|
||||
"kimi-for-coding: stripping unsupported reasoning_effort from upstream request"
|
||||
);
|
||||
self.reasoning_effort = None;
|
||||
}
|
||||
if self.web_search_options.is_some() {
|
||||
warn!(
|
||||
"kimi-for-coding: stripping unsupported web_search_options from upstream request"
|
||||
);
|
||||
self.web_search_options = None;
|
||||
}
|
||||
if self.service_tier.is_some() {
|
||||
warn!("kimi-for-coding: stripping unsupported service_tier from upstream request");
|
||||
self.service_tier = None;
|
||||
}
|
||||
if self.store.is_some() {
|
||||
warn!("kimi-for-coding: stripping unsupported store from upstream request");
|
||||
self.store = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// True when the upstream model id is Moonshot's Kimi Code endpoint model.
|
||||
pub fn is_kimi_code_model(model: &str) -> bool {
|
||||
model == "kimi-for-coding"
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
|
|
|||
|
|
@ -312,6 +312,7 @@ providers:
|
|||
- deepseek/deepseek-chat
|
||||
- deepseek/deepseek-reasoner
|
||||
moonshotai:
|
||||
- moonshotai/kimi-for-coding
|
||||
- moonshotai/kimi-k2-thinking
|
||||
- moonshotai/moonshot-v1-auto
|
||||
- moonshotai/moonshot-v1-32k-vision-preview
|
||||
|
|
|
|||
|
|
@ -500,6 +500,19 @@ mod tests {
|
|||
"/custom/api/v2/chat/completions"
|
||||
);
|
||||
|
||||
// Kimi Code API: base_url path prefix already includes /coding/v1
|
||||
assert_eq!(
|
||||
api.target_endpoint_for_provider(
|
||||
&ProviderId::Moonshotai,
|
||||
"/v1/messages",
|
||||
"kimi-for-coding",
|
||||
false,
|
||||
Some("/coding/v1"),
|
||||
false
|
||||
),
|
||||
"/coding/v1/chat/completions"
|
||||
);
|
||||
|
||||
// Test Groq with custom prefix
|
||||
assert_eq!(
|
||||
api.target_endpoint_for_provider(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use crate::apis::anthropic::MessagesRequest;
|
||||
use crate::apis::openai::ChatCompletionsRequest;
|
||||
use crate::apis::openai::{is_kimi_code_model, ChatCompletionsRequest};
|
||||
use log::warn;
|
||||
|
||||
use crate::apis::amazon_bedrock::{ConverseRequest, ConverseStreamRequest};
|
||||
use crate::apis::openai_responses::ResponsesAPIRequest;
|
||||
|
|
@ -90,6 +91,24 @@ impl ProviderRequestType {
|
|||
}
|
||||
}
|
||||
|
||||
if matches!(
|
||||
upstream_api,
|
||||
SupportedUpstreamAPIs::OpenAIChatCompletions(_)
|
||||
) {
|
||||
if let Self::ChatCompletionsRequest(req) = self {
|
||||
if is_kimi_code_model(req.model()) {
|
||||
req.normalize_for_kimi_code_api();
|
||||
}
|
||||
} else if let Self::MessagesRequest(req) = self {
|
||||
if is_kimi_code_model(req.model.as_str()) && req.thinking.is_some() {
|
||||
warn!(
|
||||
"kimi-for-coding: stripping unsupported thinking config from upstream request"
|
||||
);
|
||||
req.thinking = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ChatGPT requires instructions, store=false, and input as a list
|
||||
if provider_id == ProviderId::ChatGPT {
|
||||
if let Self::ResponsesAPIRequest(req) = self {
|
||||
|
|
@ -879,6 +898,42 @@ mod tests {
|
|||
assert!(req.web_search_options.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normalize_for_upstream_kimi_code_strips_unsupported_chat_fields() {
|
||||
use crate::apis::openai::{Message, MessageContent, OpenAIApi, Role, StreamOptions};
|
||||
|
||||
let mut request = ProviderRequestType::ChatCompletionsRequest(ChatCompletionsRequest {
|
||||
model: "kimi-for-coding".to_string(),
|
||||
messages: vec![Message {
|
||||
role: Role::User,
|
||||
content: Some(MessageContent::Text("hello".to_string())),
|
||||
name: None,
|
||||
tool_calls: None,
|
||||
tool_call_id: None,
|
||||
}],
|
||||
stream_options: Some(StreamOptions {
|
||||
include_usage: Some(true),
|
||||
}),
|
||||
reasoning_effort: Some("high".to_string()),
|
||||
web_search_options: Some(serde_json::json!({"search_context_size":"medium"})),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
request
|
||||
.normalize_for_upstream(
|
||||
ProviderId::Moonshotai,
|
||||
&SupportedUpstreamAPIs::OpenAIChatCompletions(OpenAIApi::ChatCompletions),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let ProviderRequestType::ChatCompletionsRequest(req) = request else {
|
||||
panic!("expected chat request");
|
||||
};
|
||||
assert!(req.stream_options.is_none());
|
||||
assert!(req.reasoning_effort.is_none());
|
||||
assert!(req.web_search_options.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normalize_for_upstream_non_xai_keeps_chat_web_search_options() {
|
||||
use crate::apis::openai::{Message, MessageContent, OpenAIApi, Role};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue