mirror of
https://github.com/aiclientproxy/proxycast.git
synced 2026-09-24 23:10:56 +08:00
feat: 添加 X-Provider-Id header 支持实现精确 Provider 路由
- 在 API 请求中添加 X-Provider-Id header 用于精确路由到指定 Provider - 修复 Chat 专家模式使用 Deepseek 等自定义 Provider 时路由错误的问题 - 优化 toast 通知持续时间和去重机制 - 移除 App.tsx 中重复的 Toaster 组件 修改文件: - api.rs: 解析 X-Provider-Id header 并支持 provider_id_hint 路由 - api_key_provider_service.rs: 添加 get_next_api_key_with_provider_info 方法 - protocols/mod.rs, openai.rs, anthropic.rs: Protocol trait 添加 provider_id 参数 - native_agent.rs, native_agent_cmd.rs: 传递 provider_id 到请求中 - useAgentChat.ts: 增加错误 toast 持续时间到 8 秒 - sonner.tsx: 默认 toast 持续时间从 3 秒增加到 5 秒 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
committed by
coso
co-authored by
Claude Opus 4.5
parent
735304a941
commit
3a740969b9
@@ -375,12 +375,14 @@ impl Protocol for AnthropicProtocol {
|
||||
config: &AgentConfig,
|
||||
tools: Option<&[Tool]>,
|
||||
tx: mpsc::Sender<StreamEvent>,
|
||||
provider_id: Option<&str>,
|
||||
) -> Result<StreamResult, String> {
|
||||
info!(
|
||||
"[AnthropicProtocol] 发送流式请求: model={}, history_len={}, tools_count={}",
|
||||
"[AnthropicProtocol] 发送流式请求: model={}, history_len={}, tools_count={}, provider_id={:?}",
|
||||
model,
|
||||
messages.len(),
|
||||
tools.map(|t| t.len()).unwrap_or(0)
|
||||
tools.map(|t| t.len()).unwrap_or(0),
|
||||
provider_id
|
||||
);
|
||||
|
||||
let (anthropic_messages, system) =
|
||||
@@ -400,11 +402,18 @@ impl Protocol for AnthropicProtocol {
|
||||
|
||||
let url = format!("{}{}", base_url, self.endpoint());
|
||||
|
||||
let response = client
|
||||
let mut req_builder = client
|
||||
.post(&url)
|
||||
.header("Authorization", format!("Bearer {}", api_key))
|
||||
.header("Content-Type", "application/json")
|
||||
.header("anthropic-version", "2023-06-01")
|
||||
.header("anthropic-version", "2023-06-01");
|
||||
|
||||
// 添加 X-Provider-Id header 用于精确路由
|
||||
if let Some(pid) = provider_id {
|
||||
req_builder = req_builder.header("X-Provider-Id", pid);
|
||||
}
|
||||
|
||||
let response = req_builder
|
||||
.json(&request)
|
||||
.send()
|
||||
.await
|
||||
@@ -435,12 +444,14 @@ impl Protocol for AnthropicProtocol {
|
||||
config: &AgentConfig,
|
||||
tools: Option<&[Tool]>,
|
||||
tx: mpsc::Sender<StreamEvent>,
|
||||
provider_id: Option<&str>,
|
||||
) -> Result<StreamResult, String> {
|
||||
debug!(
|
||||
"[AnthropicProtocol] 继续流式对话: model={}, history_len={}, tools_count={}",
|
||||
"[AnthropicProtocol] 继续流式对话: model={}, history_len={}, tools_count={}, provider_id={:?}",
|
||||
model,
|
||||
messages.len(),
|
||||
tools.map(|t| t.len()).unwrap_or(0)
|
||||
tools.map(|t| t.len()).unwrap_or(0),
|
||||
provider_id
|
||||
);
|
||||
|
||||
let (anthropic_messages, system) = Self::build_messages_from_history(messages, config);
|
||||
@@ -459,11 +470,18 @@ impl Protocol for AnthropicProtocol {
|
||||
|
||||
let url = format!("{}{}", base_url, self.endpoint());
|
||||
|
||||
let response = client
|
||||
let mut req_builder = client
|
||||
.post(&url)
|
||||
.header("Authorization", format!("Bearer {}", api_key))
|
||||
.header("Content-Type", "application/json")
|
||||
.header("anthropic-version", "2023-06-01")
|
||||
.header("anthropic-version", "2023-06-01");
|
||||
|
||||
// 添加 X-Provider-Id header 用于精确路由
|
||||
if let Some(pid) = provider_id {
|
||||
req_builder = req_builder.header("X-Provider-Id", pid);
|
||||
}
|
||||
|
||||
let response = req_builder
|
||||
.json(&request)
|
||||
.send()
|
||||
.await
|
||||
|
||||
@@ -36,6 +36,7 @@ pub trait Protocol: Send + Sync {
|
||||
config: &AgentConfig,
|
||||
tools: Option<&[Tool]>,
|
||||
tx: mpsc::Sender<StreamEvent>,
|
||||
provider_id: Option<&str>,
|
||||
) -> Result<StreamResult, String>;
|
||||
|
||||
/// 继续流式对话(工具调用后)
|
||||
@@ -51,6 +52,7 @@ pub trait Protocol: Send + Sync {
|
||||
config: &AgentConfig,
|
||||
tools: Option<&[Tool]>,
|
||||
tx: mpsc::Sender<StreamEvent>,
|
||||
provider_id: Option<&str>,
|
||||
) -> Result<StreamResult, String>;
|
||||
|
||||
/// 获取 API 端点
|
||||
|
||||
@@ -358,12 +358,14 @@ impl Protocol for OpenAIProtocol {
|
||||
config: &AgentConfig,
|
||||
tools: Option<&[Tool]>,
|
||||
tx: mpsc::Sender<StreamEvent>,
|
||||
provider_id: Option<&str>,
|
||||
) -> Result<StreamResult, String> {
|
||||
info!(
|
||||
"[OpenAIProtocol] 发送流式请求: model={}, history_len={}, tools_count={}",
|
||||
"[OpenAIProtocol] 发送流式请求: model={}, history_len={}, tools_count={}, provider_id={:?}",
|
||||
model,
|
||||
messages.len(),
|
||||
tools.map(|t| t.len()).unwrap_or(0)
|
||||
tools.map(|t| t.len()).unwrap_or(0),
|
||||
provider_id
|
||||
);
|
||||
|
||||
let chat_messages = Self::build_messages(messages, user_message, images, config);
|
||||
@@ -387,14 +389,21 @@ impl Protocol for OpenAIProtocol {
|
||||
let url = format!("{}{}", base_url, self.endpoint());
|
||||
|
||||
eprintln!(
|
||||
"[OpenAIProtocol] 发送请求到: {} model={} stream={}",
|
||||
url, model, request.stream
|
||||
"[OpenAIProtocol] 发送请求到: {} model={} stream={} provider_id={:?}",
|
||||
url, model, request.stream, provider_id
|
||||
);
|
||||
|
||||
let response = client
|
||||
let mut req_builder = client
|
||||
.post(&url)
|
||||
.header("Authorization", format!("Bearer {}", api_key))
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Content-Type", "application/json");
|
||||
|
||||
// 添加 X-Provider-Id header 用于精确路由
|
||||
if let Some(pid) = provider_id {
|
||||
req_builder = req_builder.header("X-Provider-Id", pid);
|
||||
}
|
||||
|
||||
let response = req_builder
|
||||
.json(&request)
|
||||
.send()
|
||||
.await
|
||||
@@ -429,12 +438,14 @@ impl Protocol for OpenAIProtocol {
|
||||
config: &AgentConfig,
|
||||
tools: Option<&[Tool]>,
|
||||
tx: mpsc::Sender<StreamEvent>,
|
||||
provider_id: Option<&str>,
|
||||
) -> Result<StreamResult, String> {
|
||||
debug!(
|
||||
"[OpenAIProtocol] 继续流式对话: model={}, history_len={}, tools_count={}",
|
||||
"[OpenAIProtocol] 继续流式对话: model={}, history_len={}, tools_count={}, provider_id={:?}",
|
||||
model,
|
||||
messages.len(),
|
||||
tools.map(|t| t.len()).unwrap_or(0)
|
||||
tools.map(|t| t.len()).unwrap_or(0),
|
||||
provider_id
|
||||
);
|
||||
|
||||
let chat_messages = Self::build_messages_from_history(messages, config);
|
||||
@@ -457,10 +468,17 @@ impl Protocol for OpenAIProtocol {
|
||||
|
||||
let url = format!("{}{}", base_url, self.endpoint());
|
||||
|
||||
let response = client
|
||||
let mut req_builder = client
|
||||
.post(&url)
|
||||
.header("Authorization", format!("Bearer {}", api_key))
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Content-Type", "application/json");
|
||||
|
||||
// 添加 X-Provider-Id header 用于精确路由
|
||||
if let Some(pid) = provider_id {
|
||||
req_builder = req_builder.header("X-Provider-Id", pid);
|
||||
}
|
||||
|
||||
let response = req_builder
|
||||
.json(&request)
|
||||
.send()
|
||||
.await
|
||||
|
||||
@@ -492,6 +492,53 @@ impl ApiKeyProviderService {
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
/// 获取下一个可用的 API Key 以及 Provider 信息(按 provider_id 精确查找)
|
||||
/// 用于支持 X-Provider-Id 请求头指定具体的 Provider
|
||||
pub fn get_next_api_key_with_provider_info(
|
||||
&self,
|
||||
db: &DbConnection,
|
||||
provider_id: &str,
|
||||
) -> Result<Option<(String, ApiKeyProvider)>, String> {
|
||||
let conn = db.lock().map_err(|e| e.to_string())?;
|
||||
|
||||
// 获取 Provider 信息
|
||||
let provider = match ApiKeyProviderDao::get_provider_by_id(&conn, provider_id)
|
||||
.map_err(|e| e.to_string())?
|
||||
{
|
||||
Some(p) => p,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
// 检查 Provider 是否启用
|
||||
if !provider.enabled {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// 获取该 Provider 的所有启用的 API Keys
|
||||
let keys = ApiKeyProviderDao::get_enabled_api_keys_by_provider(&conn, provider_id)
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
if keys.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// 获取或创建轮询索引
|
||||
let index = {
|
||||
let mut indices = self.round_robin_index.write().map_err(|e| e.to_string())?;
|
||||
indices
|
||||
.entry(provider_id.to_string())
|
||||
.or_insert_with(|| AtomicUsize::new(0))
|
||||
.fetch_add(1, Ordering::SeqCst)
|
||||
};
|
||||
|
||||
// 选择 API Key
|
||||
let selected_key = &keys[index % keys.len()];
|
||||
|
||||
// 解密并返回
|
||||
let decrypted = self.encryption.decrypt(&selected_key.api_key_encrypted)?;
|
||||
Ok(Some((decrypted, provider)))
|
||||
}
|
||||
|
||||
/// 按 Provider 类型获取下一个可用的 API Key(轮询负载均衡)
|
||||
/// 这个方法会查找所有该类型的 Provider(包括自定义 Provider)
|
||||
pub fn get_next_api_key_by_type(
|
||||
|
||||
@@ -19,7 +19,6 @@ import { ToolsPage } from "./components/tools/ToolsPage";
|
||||
import { AgentChatPage } from "./components/agent";
|
||||
import { PluginUIRenderer } from "./components/plugins/PluginUIRenderer";
|
||||
import { PluginsPage } from "./components/plugins/PluginsPage";
|
||||
import { Toaster } from "./components/ui/sonner";
|
||||
import { flowEventManager } from "./lib/flowEventManager";
|
||||
import { OnboardingWizard, useOnboardingState } from "./components/onboarding";
|
||||
import { ConnectConfirmDialog } from "./components/connect";
|
||||
@@ -209,7 +208,6 @@ function App() {
|
||||
<AppContainer>
|
||||
<AppSidebar currentPage={currentPage} onNavigate={setCurrentPage} />
|
||||
<MainContent>{renderPage()}</MainContent>
|
||||
<Toaster />
|
||||
{/* ProxyCast Connect 确认弹窗 */}
|
||||
{/* _Requirements: 5.2_ */}
|
||||
<ConnectConfirmDialog
|
||||
|
||||
@@ -7,7 +7,7 @@ export function Toaster() {
|
||||
richColors
|
||||
theme="system"
|
||||
toastOptions={{
|
||||
duration: 3000,
|
||||
duration: 5000,
|
||||
classNames: {
|
||||
toast:
|
||||
"group rounded-md border bg-background text-foreground shadow-lg",
|
||||
|
||||
Reference in New Issue
Block a user