mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-26 14:35:22 +08:00
e6f18ba801
* feat(proxy): extract model name from API response for accurate usage tracking - Add model field extraction in TokenUsage parsing for Claude, OpenAI, and Codex - Prioritize response model over request model in usage logging - Update model extractors to use parsed usage.model first - Add tests for model extraction in stream and non-stream responses * feat(proxy): implement streaming timeout control with validation - Add first byte timeout (0 or 1-180s) for streaming requests - Add idle timeout (0 or 60-600s) for streaming data gaps - Add non-streaming timeout (0 or 60-1800s) for total request - Implement timeout logic in response processor - Add 1800s global timeout fallback when disabled - Add database schema migration for timeout fields - Add i18n translations for timeout settings * feat(proxy): add model mapping module for provider-based model substitution - Add model_mapper.rs with ModelMapping struct to extract model configs from Provider - Support ANTHROPIC_MODEL, ANTHROPIC_REASONING_MODEL, and default models for haiku/sonnet/opus - Implement thinking mode detection for reasoning model priority - Include comprehensive unit tests for all mapping scenarios * fix(proxy): bypass circuit breaker for single provider scenario When failover is disabled (single provider), circuit breaker open state would block all requests causing poor UX. Now bypasses circuit breaker check in this scenario. Also integrates model mapping into request flow. * feat(ui): add reasoning model field to Claude provider form Add ANTHROPIC_REASONING_MODEL configuration field for Claude providers, allowing users to specify a dedicated model for thinking/reasoning tasks. * feat(proxy): add openrouter_compat_mode for optional format conversion Add configurable OpenRouter compatibility mode that enables Anthropic to OpenAI format conversion. When enabled, rewrites endpoint to /v1/chat/completions and transforms request/response formats. Defaults to enabled for OpenRouter. * feat(ui): add OpenRouter compatibility mode toggle Add UI toggle for OpenRouter providers to enable/disable compatibility mode which uses OpenAI Chat Completions format with SSE conversion. * feat(stream-check): use provider-configured model for health checks Extract model from provider's settings_config (ANTHROPIC_MODEL, GEMINI_MODEL, or Codex config.toml) instead of always using default test models. * refactor(ui): remove timeout settings from AutoFailoverConfigPanel Remove streaming/non-streaming timeout configuration from failover panel as these settings have been moved to a dedicated location. * refactor(database): migrate proxy_config to per-app three-row structure Replace singleton proxy_config table with app_type primary key structure, allowing independent proxy settings for Claude, Codex, and Gemini. Add GlobalProxyConfig queries and per-app config management in DAO layer. * feat(proxy): add GlobalProxyConfig and AppProxyConfig types Add new type definitions for the refactored proxy configuration: - GlobalProxyConfig: shared settings (enabled, address, port, logging) - AppProxyConfig: per-app settings (failover, timeouts, circuit breaker) * refactor(proxy): update service layer for per-app config structure Adapt proxy service, handler context, and provider router to use the new per-app configuration model. Read enabled/timeout settings from proxy_config table instead of settings table. * feat(commands): add global and per-app proxy config commands Add new Tauri commands for the refactored proxy configuration: - get_global_proxy_config / update_global_proxy_config - get_proxy_config_for_app / update_proxy_config_for_app Update startup restore logic to read from proxy_config table. * feat(api): add frontend API and Query hooks for proxy config Add TypeScript wrappers and TanStack Query hooks for: - Global proxy config (address, port, logging) - Per-app proxy config (failover, timeouts, circuit breaker) - Proxy takeover status management * refactor(ui): redesign proxy panel with inline config controls Replace ProxySettingsDialog with inline controls in ProxyPanel. Add per-app takeover switches and global address/port settings. Simplify AutoFailoverConfigPanel by removing timeout settings. * feat(i18n): add proxy takeover translations and update types Add i18n strings for proxy takeover status in zh/en/ja. Update TypeScript types for GlobalProxyConfig and AppProxyConfig. * refactor(proxy): load circuit breaker config per-app instead of globally Extract app_type from router key and read circuit breaker settings from the corresponding proxy_config row for each application.
185 lines
6.1 KiB
Rust
185 lines
6.1 KiB
Rust
//! Handler 配置模块
|
||
//!
|
||
//! 定义各 API 处理器的配置结构和使用量解析器
|
||
|
||
use crate::app_config::AppType;
|
||
use crate::proxy::usage::parser::TokenUsage;
|
||
use serde_json::Value;
|
||
|
||
/// 使用量解析器类型别名
|
||
pub type StreamUsageParser = fn(&[Value]) -> Option<TokenUsage>;
|
||
pub type ResponseUsageParser = fn(&Value) -> Option<TokenUsage>;
|
||
|
||
/// 模型提取器类型别名
|
||
/// 参数: (流式事件列表, 请求中的模型名称) -> 最终使用的模型名称
|
||
pub type StreamModelExtractor = fn(&[Value], &str) -> String;
|
||
|
||
/// 各 API 的使用量解析配置
|
||
#[derive(Clone, Copy)]
|
||
pub struct UsageParserConfig {
|
||
/// 流式响应解析器
|
||
pub stream_parser: StreamUsageParser,
|
||
/// 非流式响应解析器
|
||
pub response_parser: ResponseUsageParser,
|
||
/// 流式响应中的模型提取器
|
||
pub model_extractor: StreamModelExtractor,
|
||
/// 应用类型字符串(用于日志记录)
|
||
pub app_type_str: &'static str,
|
||
}
|
||
|
||
// ============================================================================
|
||
// 模型提取器实现
|
||
// ============================================================================
|
||
|
||
/// Claude 流式响应模型提取(优先使用 usage.model)
|
||
fn claude_model_extractor(events: &[Value], request_model: &str) -> String {
|
||
// 首先尝试从解析的 usage 中获取模型
|
||
if let Some(usage) = TokenUsage::from_claude_stream_events(events) {
|
||
if let Some(model) = usage.model {
|
||
return model;
|
||
}
|
||
}
|
||
request_model.to_string()
|
||
}
|
||
|
||
/// OpenAI Chat Completions 流式响应模型提取(优先使用 usage.model)
|
||
fn openai_model_extractor(events: &[Value], request_model: &str) -> String {
|
||
// 首先尝试从解析的 usage 中获取模型
|
||
if let Some(usage) = TokenUsage::from_openai_stream_events(events) {
|
||
if let Some(model) = usage.model {
|
||
return model;
|
||
}
|
||
}
|
||
// 回退:从事件中直接提取
|
||
events
|
||
.iter()
|
||
.find_map(|e| e.get("model")?.as_str())
|
||
.unwrap_or(request_model)
|
||
.to_string()
|
||
}
|
||
|
||
/// Codex Responses API 流式响应模型提取(优先使用 usage.model)
|
||
fn codex_model_extractor(events: &[Value], request_model: &str) -> String {
|
||
// 首先尝试从解析的 usage 中获取模型
|
||
if let Some(usage) = TokenUsage::from_codex_stream_events(events) {
|
||
if let Some(model) = usage.model {
|
||
return model;
|
||
}
|
||
}
|
||
// 回退:从 response.completed 事件中提取
|
||
events
|
||
.iter()
|
||
.find_map(|e| {
|
||
if e.get("type")?.as_str()? == "response.completed" {
|
||
e.get("response")?.get("model")?.as_str()
|
||
} else {
|
||
None
|
||
}
|
||
})
|
||
.unwrap_or(request_model)
|
||
.to_string()
|
||
}
|
||
|
||
/// Gemini 流式响应模型提取(优先使用 usage.model)
|
||
fn gemini_model_extractor(events: &[Value], request_model: &str) -> String {
|
||
// 首先尝试从解析的 usage 中获取模型
|
||
if let Some(usage) = TokenUsage::from_gemini_stream_chunks(events) {
|
||
if let Some(model) = usage.model {
|
||
return model;
|
||
}
|
||
}
|
||
request_model.to_string()
|
||
}
|
||
|
||
// ============================================================================
|
||
// 预定义配置
|
||
// ============================================================================
|
||
|
||
/// Claude API 解析配置
|
||
pub const CLAUDE_PARSER_CONFIG: UsageParserConfig = UsageParserConfig {
|
||
stream_parser: TokenUsage::from_claude_stream_events,
|
||
response_parser: TokenUsage::from_claude_response,
|
||
model_extractor: claude_model_extractor,
|
||
app_type_str: "claude",
|
||
};
|
||
|
||
/// OpenAI Chat Completions API 解析配置(用于 Codex /v1/chat/completions)
|
||
pub const OPENAI_PARSER_CONFIG: UsageParserConfig = UsageParserConfig {
|
||
stream_parser: TokenUsage::from_openai_stream_events,
|
||
response_parser: TokenUsage::from_openai_response,
|
||
model_extractor: openai_model_extractor,
|
||
app_type_str: "codex",
|
||
};
|
||
|
||
/// Codex Responses API 解析配置(用于 /v1/responses)
|
||
pub const CODEX_PARSER_CONFIG: UsageParserConfig = UsageParserConfig {
|
||
stream_parser: TokenUsage::from_codex_stream_events,
|
||
response_parser: TokenUsage::from_codex_response,
|
||
model_extractor: codex_model_extractor,
|
||
app_type_str: "codex",
|
||
};
|
||
|
||
/// Gemini API 解析配置
|
||
pub const GEMINI_PARSER_CONFIG: UsageParserConfig = UsageParserConfig {
|
||
stream_parser: TokenUsage::from_gemini_stream_chunks,
|
||
response_parser: TokenUsage::from_gemini_response,
|
||
model_extractor: gemini_model_extractor,
|
||
app_type_str: "gemini",
|
||
};
|
||
|
||
// ============================================================================
|
||
// Handler 配置(预留,用于进一步简化)
|
||
// ============================================================================
|
||
|
||
/// Handler 基础配置
|
||
///
|
||
/// 预留结构,可用于进一步统一各 handler 的配置
|
||
#[allow(dead_code)]
|
||
#[derive(Clone)]
|
||
pub struct HandlerConfig {
|
||
/// 应用类型
|
||
pub app_type: AppType,
|
||
/// 日志标签
|
||
pub tag: &'static str,
|
||
/// 应用类型字符串
|
||
pub app_type_str: &'static str,
|
||
/// 使用量解析配置
|
||
pub parser_config: &'static UsageParserConfig,
|
||
}
|
||
|
||
/// Claude Handler 配置
|
||
#[allow(dead_code)]
|
||
pub const CLAUDE_HANDLER_CONFIG: HandlerConfig = HandlerConfig {
|
||
app_type: AppType::Claude,
|
||
tag: "Claude",
|
||
app_type_str: "claude",
|
||
parser_config: &CLAUDE_PARSER_CONFIG,
|
||
};
|
||
|
||
/// Codex Chat Completions Handler 配置
|
||
#[allow(dead_code)]
|
||
pub const CODEX_CHAT_HANDLER_CONFIG: HandlerConfig = HandlerConfig {
|
||
app_type: AppType::Codex,
|
||
tag: "Codex",
|
||
app_type_str: "codex",
|
||
parser_config: &OPENAI_PARSER_CONFIG,
|
||
};
|
||
|
||
/// Codex Responses Handler 配置
|
||
#[allow(dead_code)]
|
||
pub const CODEX_RESPONSES_HANDLER_CONFIG: HandlerConfig = HandlerConfig {
|
||
app_type: AppType::Codex,
|
||
tag: "Codex",
|
||
app_type_str: "codex",
|
||
parser_config: &CODEX_PARSER_CONFIG,
|
||
};
|
||
|
||
/// Gemini Handler 配置
|
||
#[allow(dead_code)]
|
||
pub const GEMINI_HANDLER_CONFIG: HandlerConfig = HandlerConfig {
|
||
app_type: AppType::Gemini,
|
||
tag: "Gemini",
|
||
app_type_str: "gemini",
|
||
parser_config: &GEMINI_PARSER_CONFIG,
|
||
};
|