mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-12 12:09:50 +08:00
feat: provider api_formats 可空继承、OpenAI 图片 edit/variation 与用量配额多项补强
- 鉴权: provider_api_keys.api_formats 改为可空,OAuth 托管 key 自动继承 provider endpoints 激活格式,相关 handler/测试同步更新 - 图片 planner: OpenAI 图片路由新增 edit/variation 操作并完善参数校验、响应合并与流式处理 - 用量: user me usage 返回区分 client_requested_stream/upstream_is_stream,前端 usage 列表筛选与展示增强 - 统计: stats_daily_model 新增 cache_creation_ephemeral_5m/1h tokens 字段与回填链路 - 配额/observability: quota repository 新增内存与 SQL 扩展,admin observability usage 字段扩充 - 其它: OAuth 导入/轮询收敛、provider 汇总与 pool admin 读写链路小修、新增 system_config 缓存与 provider template handler Closes #318 Co-authored-by: Entropy.Xu <[email protected]>
This commit is contained in:
@@ -11,6 +11,146 @@ pub struct ProviderOAuthTemplate {
|
||||
pub use_pkce: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum FixedProviderEndpointConfigValue {
|
||||
String(&'static str),
|
||||
Bool(bool),
|
||||
I64(i64),
|
||||
}
|
||||
|
||||
impl FixedProviderEndpointConfigValue {
|
||||
pub fn to_json_value(self) -> serde_json::Value {
|
||||
match self {
|
||||
Self::String(value) => serde_json::Value::String(value.to_string()),
|
||||
Self::Bool(value) => serde_json::Value::Bool(value),
|
||||
Self::I64(value) => serde_json::Value::Number(value.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct FixedProviderEndpointConfigDefault {
|
||||
pub key: &'static str,
|
||||
pub value: FixedProviderEndpointConfigValue,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct FixedProviderEndpointTemplate {
|
||||
pub item_key: &'static str,
|
||||
pub api_format: &'static str,
|
||||
pub custom_path: Option<&'static str>,
|
||||
pub config_defaults: &'static [FixedProviderEndpointConfigDefault],
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct FixedProviderTemplate {
|
||||
pub provider_type: &'static str,
|
||||
pub version: u32,
|
||||
pub base_url: &'static str,
|
||||
pub endpoints: &'static [FixedProviderEndpointTemplate],
|
||||
}
|
||||
|
||||
const EMPTY_ENDPOINT_CONFIG_DEFAULTS: &[FixedProviderEndpointConfigDefault] = &[];
|
||||
const FORCE_STREAM_ENDPOINT_CONFIG_DEFAULTS: &[FixedProviderEndpointConfigDefault] =
|
||||
&[FixedProviderEndpointConfigDefault {
|
||||
key: "upstream_stream_policy",
|
||||
value: FixedProviderEndpointConfigValue::String("force_stream"),
|
||||
}];
|
||||
|
||||
const CLAUDE_CODE_FIXED_PROVIDER_TEMPLATE: FixedProviderTemplate = FixedProviderTemplate {
|
||||
provider_type: "claude_code",
|
||||
version: 1,
|
||||
base_url: "https://api.anthropic.com",
|
||||
endpoints: &[FixedProviderEndpointTemplate {
|
||||
item_key: "claude:cli",
|
||||
api_format: "claude:cli",
|
||||
custom_path: None,
|
||||
config_defaults: EMPTY_ENDPOINT_CONFIG_DEFAULTS,
|
||||
}],
|
||||
};
|
||||
|
||||
const CODEX_FIXED_PROVIDER_TEMPLATE: FixedProviderTemplate = FixedProviderTemplate {
|
||||
provider_type: "codex",
|
||||
version: 1,
|
||||
base_url: "https://chatgpt.com/backend-api/codex",
|
||||
endpoints: &[
|
||||
FixedProviderEndpointTemplate {
|
||||
item_key: "openai:cli",
|
||||
api_format: "openai:cli",
|
||||
custom_path: None,
|
||||
config_defaults: FORCE_STREAM_ENDPOINT_CONFIG_DEFAULTS,
|
||||
},
|
||||
FixedProviderEndpointTemplate {
|
||||
item_key: "openai:compact",
|
||||
api_format: "openai:compact",
|
||||
custom_path: None,
|
||||
config_defaults: EMPTY_ENDPOINT_CONFIG_DEFAULTS,
|
||||
},
|
||||
FixedProviderEndpointTemplate {
|
||||
item_key: "openai:image",
|
||||
api_format: "openai:image",
|
||||
custom_path: None,
|
||||
config_defaults: FORCE_STREAM_ENDPOINT_CONFIG_DEFAULTS,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const KIRO_FIXED_PROVIDER_TEMPLATE: FixedProviderTemplate = FixedProviderTemplate {
|
||||
provider_type: "kiro",
|
||||
version: 1,
|
||||
base_url: "https://q.{region}.amazonaws.com",
|
||||
endpoints: &[FixedProviderEndpointTemplate {
|
||||
item_key: "claude:cli",
|
||||
api_format: "claude:cli",
|
||||
custom_path: None,
|
||||
config_defaults: EMPTY_ENDPOINT_CONFIG_DEFAULTS,
|
||||
}],
|
||||
};
|
||||
|
||||
const GEMINI_CLI_FIXED_PROVIDER_TEMPLATE: FixedProviderTemplate = FixedProviderTemplate {
|
||||
provider_type: "gemini_cli",
|
||||
version: 1,
|
||||
base_url: "https://cloudcode-pa.googleapis.com",
|
||||
endpoints: &[FixedProviderEndpointTemplate {
|
||||
item_key: "gemini:cli",
|
||||
api_format: "gemini:cli",
|
||||
custom_path: None,
|
||||
config_defaults: EMPTY_ENDPOINT_CONFIG_DEFAULTS,
|
||||
}],
|
||||
};
|
||||
|
||||
const VERTEX_AI_FIXED_PROVIDER_TEMPLATE: FixedProviderTemplate = FixedProviderTemplate {
|
||||
provider_type: "vertex_ai",
|
||||
version: 1,
|
||||
base_url: "https://aiplatform.googleapis.com",
|
||||
endpoints: &[
|
||||
FixedProviderEndpointTemplate {
|
||||
item_key: "gemini:chat",
|
||||
api_format: "gemini:chat",
|
||||
custom_path: None,
|
||||
config_defaults: EMPTY_ENDPOINT_CONFIG_DEFAULTS,
|
||||
},
|
||||
FixedProviderEndpointTemplate {
|
||||
item_key: "claude:chat",
|
||||
api_format: "claude:chat",
|
||||
custom_path: None,
|
||||
config_defaults: EMPTY_ENDPOINT_CONFIG_DEFAULTS,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const ANTIGRAVITY_FIXED_PROVIDER_TEMPLATE: FixedProviderTemplate = FixedProviderTemplate {
|
||||
provider_type: "antigravity",
|
||||
version: 1,
|
||||
base_url: "https://cloudcode-pa.googleapis.com",
|
||||
endpoints: &[FixedProviderEndpointTemplate {
|
||||
item_key: "gemini:chat",
|
||||
api_format: "gemini:chat",
|
||||
custom_path: None,
|
||||
config_defaults: EMPTY_ENDPOINT_CONFIG_DEFAULTS,
|
||||
}],
|
||||
};
|
||||
|
||||
pub fn provider_type_is_fixed(provider_type: &str) -> bool {
|
||||
matches!(
|
||||
provider_type.trim().to_ascii_lowercase().as_str(),
|
||||
@@ -25,26 +165,29 @@ pub fn provider_type_enables_format_conversion_by_default(provider_type: &str) -
|
||||
)
|
||||
}
|
||||
|
||||
pub fn fixed_provider_template(
|
||||
provider_type: &str,
|
||||
) -> Option<(&'static str, &'static [&'static str])> {
|
||||
pub fn fixed_provider_template(provider_type: &str) -> Option<&'static FixedProviderTemplate> {
|
||||
match provider_type.trim().to_ascii_lowercase().as_str() {
|
||||
"claude_code" => Some(("https://api.anthropic.com", &["claude:cli"])),
|
||||
"codex" => Some((
|
||||
"https://chatgpt.com/backend-api/codex",
|
||||
&["openai:cli", "openai:compact"],
|
||||
)),
|
||||
"kiro" => Some(("https://q.{region}.amazonaws.com", &["claude:cli"])),
|
||||
"gemini_cli" => Some(("https://cloudcode-pa.googleapis.com", &["gemini:cli"])),
|
||||
"vertex_ai" => Some((
|
||||
"https://aiplatform.googleapis.com",
|
||||
&["gemini:chat", "claude:chat"],
|
||||
)),
|
||||
"antigravity" => Some(("https://cloudcode-pa.googleapis.com", &["gemini:chat"])),
|
||||
"claude_code" => Some(&CLAUDE_CODE_FIXED_PROVIDER_TEMPLATE),
|
||||
"codex" => Some(&CODEX_FIXED_PROVIDER_TEMPLATE),
|
||||
"kiro" => Some(&KIRO_FIXED_PROVIDER_TEMPLATE),
|
||||
"gemini_cli" => Some(&GEMINI_CLI_FIXED_PROVIDER_TEMPLATE),
|
||||
"vertex_ai" => Some(&VERTEX_AI_FIXED_PROVIDER_TEMPLATE),
|
||||
"antigravity" => Some(&ANTIGRAVITY_FIXED_PROVIDER_TEMPLATE),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fixed_provider_endpoint_template_by_api_format(
|
||||
provider_type: &str,
|
||||
api_format: &str,
|
||||
) -> Option<&'static FixedProviderEndpointTemplate> {
|
||||
let normalized = api_format.trim();
|
||||
fixed_provider_template(provider_type)?
|
||||
.endpoints
|
||||
.iter()
|
||||
.find(|item| item.api_format.eq_ignore_ascii_case(normalized))
|
||||
}
|
||||
|
||||
pub fn provider_type_supports_model_fetch(provider_type: &str) -> bool {
|
||||
!matches!(
|
||||
provider_type.trim().to_ascii_lowercase().as_str(),
|
||||
@@ -137,3 +280,41 @@ pub fn provider_type_admin_oauth_template(provider_type: &str) -> Option<Provide
|
||||
|
||||
pub const ADMIN_PROVIDER_OAUTH_TEMPLATE_TYPES: &[&str] =
|
||||
&["claude_code", "codex", "gemini_cli", "antigravity"];
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
fixed_provider_endpoint_template_by_api_format, fixed_provider_template,
|
||||
FixedProviderEndpointConfigValue,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn codex_fixed_provider_template_includes_openai_image() {
|
||||
let template = fixed_provider_template("codex").expect("codex template should exist");
|
||||
assert_eq!(template.base_url, "https://chatgpt.com/backend-api/codex");
|
||||
assert_eq!(template.version, 1);
|
||||
assert_eq!(
|
||||
template
|
||||
.endpoints
|
||||
.iter()
|
||||
.map(|item| item.api_format)
|
||||
.collect::<Vec<_>>(),
|
||||
vec!["openai:cli", "openai:compact", "openai:image"]
|
||||
);
|
||||
|
||||
let image_template =
|
||||
fixed_provider_endpoint_template_by_api_format("codex", "openai:image")
|
||||
.expect("codex image endpoint should exist");
|
||||
assert_eq!(
|
||||
image_template
|
||||
.config_defaults
|
||||
.iter()
|
||||
.map(|item| (item.key, item.value))
|
||||
.collect::<Vec<_>>(),
|
||||
vec![(
|
||||
"upstream_stream_policy",
|
||||
FixedProviderEndpointConfigValue::String("force_stream")
|
||||
)]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user