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
23 changes: 19 additions & 4 deletions crates/openfang-runtime/src/drivers/anthropic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ pub struct AnthropicDriver {
api_key: Zeroizing<String>,
base_url: String,
client: reqwest::Client,
extra_headers: Vec<(String, String)>,
}

impl AnthropicDriver {
Expand All @@ -31,8 +32,14 @@ impl AnthropicDriver {
.user_agent(crate::USER_AGENT)
.build()
.unwrap_or_default(),
extra_headers: Vec::new(),
}
}

pub fn with_extra_headers(mut self, headers: Vec<(String, String)>) -> Self {
self.extra_headers = headers;
self
}
}

/// Anthropic Messages API request body.
Expand Down Expand Up @@ -204,12 +211,16 @@ impl LlmDriver for AnthropicDriver {
let url = format!("{}/v1/messages", self.base_url);
debug!(url = %url, attempt, "Sending Anthropic API request");

let resp = self
let mut req_builder = self
.client
.post(&url)
.header("x-api-key", self.api_key.as_str())
.header("anthropic-version", "2023-06-01")
.header("content-type", "application/json")
.header("content-type", "application/json");
for (k, v) in &self.extra_headers {
req_builder = req_builder.header(k, v);
}
let resp = req_builder
.json(&api_request)
.send()
.await
Expand Down Expand Up @@ -311,12 +322,16 @@ impl LlmDriver for AnthropicDriver {
let url = format!("{}/v1/messages", self.base_url);
debug!(url = %url, attempt, "Sending Anthropic streaming request");

let resp = self
let mut req_builder = self
.client
.post(&url)
.header("x-api-key", self.api_key.as_str())
.header("anthropic-version", "2023-06-01")
.header("content-type", "application/json")
.header("content-type", "application/json");
for (k, v) in &self.extra_headers {
req_builder = req_builder.header(k, v);
}
let resp = req_builder
.json(&api_request)
.send()
.await
Expand Down
87 changes: 78 additions & 9 deletions crates/openfang-runtime/src/drivers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ pub mod openai;

use crate::llm_driver::{DriverConfig, LlmDriver, LlmError};
use openfang_types::model_catalog::{
AI21_BASE_URL, ANTHROPIC_BASE_URL, CEREBRAS_BASE_URL, CHUTES_BASE_URL, COHERE_BASE_URL,
AI21_BASE_URL, ANTHROPIC_BASE_URL, BAILIAN_CODING_ANTHROPIC_BASE_URL,
BAILIAN_CODING_OPENAI_BASE_URL, CEREBRAS_BASE_URL, CHUTES_BASE_URL, COHERE_BASE_URL,
DEEPSEEK_BASE_URL, FIREWORKS_BASE_URL, GEMINI_BASE_URL, GROQ_BASE_URL, HUGGINGFACE_BASE_URL,
KIMI_CODING_BASE_URL, LEMONADE_BASE_URL, LMSTUDIO_BASE_URL, MINIMAX_BASE_URL,
MISTRAL_BASE_URL, MOONSHOT_BASE_URL, OLLAMA_BASE_URL, OPENAI_BASE_URL,
OPENROUTER_BASE_URL, PERPLEXITY_BASE_URL, QIANFAN_BASE_URL, QWEN_BASE_URL,
REPLICATE_BASE_URL, SAMBANOVA_BASE_URL, TOGETHER_BASE_URL, VENICE_BASE_URL, VLLM_BASE_URL,
VOLCENGINE_BASE_URL, VOLCENGINE_CODING_BASE_URL, XAI_BASE_URL, ZAI_BASE_URL,
ZAI_CODING_BASE_URL, ZHIPU_BASE_URL, ZHIPU_CODING_BASE_URL,
KIMI_CODING_BASE_URL, LEMONADE_BASE_URL, LMSTUDIO_BASE_URL, MINIMAX_BASE_URL, MISTRAL_BASE_URL,
MOONSHOT_BASE_URL, OLLAMA_BASE_URL, OPENAI_BASE_URL, OPENROUTER_BASE_URL, PERPLEXITY_BASE_URL,
QIANFAN_BASE_URL, QWEN_BASE_URL, REPLICATE_BASE_URL, SAMBANOVA_BASE_URL, TOGETHER_BASE_URL,
VENICE_BASE_URL, VLLM_BASE_URL, VOLCENGINE_BASE_URL, VOLCENGINE_CODING_BASE_URL, XAI_BASE_URL,
ZAI_BASE_URL, ZAI_CODING_BASE_URL, ZHIPU_BASE_URL, ZHIPU_CODING_BASE_URL,
};
use std::sync::Arc;

Expand Down Expand Up @@ -205,6 +205,11 @@ fn provider_defaults(provider: &str) -> Option<ProviderDefaults> {
api_key_env: "VOLCENGINE_API_KEY",
key_required: true,
}),
"bailian_coding_openai" => Some(ProviderDefaults {
base_url: BAILIAN_CODING_OPENAI_BASE_URL,
api_key_env: "BAILIAN_API_KEY",
key_required: true,
}),
"chutes" => Some(ProviderDefaults {
base_url: CHUTES_BASE_URL,
api_key_env: "CHUTES_API_KEY",
Expand Down Expand Up @@ -329,6 +334,44 @@ pub fn create_driver(config: &DriverConfig) -> Result<Arc<dyn LlmDriver>, LlmErr
)));
}

// Bailian Coding Plan — Anthropic-compatible endpoint.
// Requires DashScope-specific headers just like the OpenAI endpoint.
// Base URL does NOT include /v1 — AnthropicDriver appends /v1/messages automatically.
if provider == "bailian_coding_anthropic" {
let api_key = config
.api_key
.clone()
.or_else(|| std::env::var("BAILIAN_API_KEY").ok())
.ok_or_else(|| {
LlmError::MissingApiKey(
"Set BAILIAN_API_KEY environment variable for Bailian Coding Anthropic"
.to_string(),
)
})?;
let base_url = config
.base_url
.clone()
.unwrap_or_else(|| BAILIAN_CODING_ANTHROPIC_BASE_URL.to_string());
let user_agent = format!(
"OpenFang/{} ({}; {})",
env!("CARGO_PKG_VERSION"),
std::env::consts::OS,
std::env::consts::ARCH
);
let dashscope_headers = vec![
("User-Agent".to_string(), user_agent.clone()),
("X-DashScope-UserAgent".to_string(), user_agent),
("X-DashScope-AuthType".to_string(), "qwen-oauth".to_string()),
(
"X-DashScope-CacheControl".to_string(),
"enable".to_string(),
),
];
return Ok(Arc::new(
anthropic::AnthropicDriver::new(api_key, base_url).with_extra_headers(dashscope_headers),
));
}

// Kimi for Code — Anthropic-compatible endpoint
if provider == "kimi_coding" {
let api_key = config
Expand Down Expand Up @@ -365,6 +408,28 @@ pub fn create_driver(config: &DriverConfig) -> Result<Arc<dyn LlmDriver>, LlmErr
.clone()
.unwrap_or_else(|| defaults.base_url.to_string());

// Bailian Coding Plan (OpenAI endpoint) requires specific headers to identify
// the caller as a "Coding Agent"; without them the API returns HTTP 405.
if provider == "bailian_coding_openai" {
let user_agent = format!(
"OpenFang/{} ({}; {})",
env!("CARGO_PKG_VERSION"),
std::env::consts::OS,
std::env::consts::ARCH,
);
return Ok(Arc::new(
openai::OpenAIDriver::new(api_key, base_url).with_extra_headers(vec![
("User-Agent".to_string(), user_agent.clone()),
("X-DashScope-UserAgent".to_string(), user_agent),
("X-DashScope-AuthType".to_string(), "qwen-oauth".to_string()),
(
"X-DashScope-CacheControl".to_string(),
"enable".to_string(),
),
]),
));
}

return Ok(Arc::new(openai::OpenAIDriver::new(api_key, base_url)));
}

Expand Down Expand Up @@ -408,7 +473,7 @@ pub fn create_driver(config: &DriverConfig) -> Result<Arc<dyn LlmDriver>, LlmErr
"Unknown provider '{}'. Supported: anthropic, gemini, openai, groq, openrouter, \
deepseek, together, mistral, fireworks, ollama, vllm, lmstudio, perplexity, \
cohere, ai21, cerebras, sambanova, huggingface, xai, replicate, github-copilot, \
chutes, venice, codex, claude-code. Or set base_url for a custom OpenAI-compatible endpoint.",
chutes, venice, bailian_coding_openai, bailian_coding_anthropic, codex, claude-code. Or set base_url for a custom OpenAI-compatible endpoint.",
provider
),
})
Expand Down Expand Up @@ -479,6 +544,8 @@ pub fn known_providers() -> &'static [&'static str] {
"kimi_coding",
"qianfan",
"volcengine",
"bailian_coding_openai",
"bailian_coding_anthropic",
"chutes",
"venice",
"codex",
Expand Down Expand Up @@ -579,10 +646,12 @@ mod tests {
assert!(providers.contains(&"kimi_coding"));
assert!(providers.contains(&"qianfan"));
assert!(providers.contains(&"volcengine"));
assert!(providers.contains(&"bailian_coding_openai"));
assert!(providers.contains(&"bailian_coding_anthropic"));
assert!(providers.contains(&"chutes"));
assert!(providers.contains(&"codex"));
assert!(providers.contains(&"claude-code"));
assert_eq!(providers.len(), 34);
assert_eq!(providers.len(), 36);
}

#[test]
Expand Down
Loading