mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 01:10:23 +08:00
migrate ai format conversion to responses adapters
This commit is contained in:
@@ -7,7 +7,11 @@ use regex::Regex;
|
||||
use serde_json::{json, Value};
|
||||
|
||||
const MODEL_FETCH_FORMAT_PRIORITY: &[&[&str]] = &[
|
||||
&["openai:chat", "openai:cli", "openai:compact"],
|
||||
&[
|
||||
"openai:chat",
|
||||
"openai:responses",
|
||||
"openai:responses:compact",
|
||||
],
|
||||
&["claude:chat", "claude:cli"],
|
||||
&["gemini:chat", "gemini:cli"],
|
||||
];
|
||||
@@ -180,9 +184,15 @@ pub fn selected_models_fetch_endpoints(
|
||||
if !key_formats.is_empty() && !key_formats.contains(&api_format) {
|
||||
continue;
|
||||
}
|
||||
by_format
|
||||
.entry(api_format)
|
||||
.or_insert_with(|| endpoint.clone());
|
||||
if let Some(existing) = by_format.get_mut(&api_format) {
|
||||
if endpoint.api_format.trim().eq_ignore_ascii_case(&api_format)
|
||||
&& !existing.api_format.trim().eq_ignore_ascii_case(&api_format)
|
||||
{
|
||||
*existing = endpoint.clone();
|
||||
}
|
||||
} else {
|
||||
by_format.insert(api_format, endpoint.clone());
|
||||
}
|
||||
}
|
||||
|
||||
MODEL_FETCH_FORMAT_PRIORITY
|
||||
@@ -209,8 +219,8 @@ pub fn endpoint_supports_rust_models_fetch(api_format: &str) -> bool {
|
||||
matches!(
|
||||
api_format.as_str(),
|
||||
"openai:chat"
|
||||
| "openai:cli"
|
||||
| "openai:compact"
|
||||
| "openai:responses"
|
||||
| "openai:responses:compact"
|
||||
| "claude:chat"
|
||||
| "claude:cli"
|
||||
| "gemini:chat"
|
||||
@@ -250,7 +260,7 @@ pub fn preset_models_for_provider(provider_type: &str) -> Option<Vec<Value>> {
|
||||
preset_model("claude-haiku-4-5-20251001", "anthropic", "Claude Haiku 4.5", "claude:cli"),
|
||||
],
|
||||
"codex" => vec![
|
||||
preset_model("gpt-5", "openai", "GPT-5", "openai:cli"),
|
||||
preset_model("gpt-5", "openai", "GPT-5", "openai:responses"),
|
||||
preset_model("gpt-image-1", "openai", "GPT Image 1", "openai:image"),
|
||||
preset_model("gpt-image-1.5", "openai", "GPT Image 1.5", "openai:image"),
|
||||
preset_model("gpt-image-1-mini", "openai", "GPT Image 1 Mini", "openai:image"),
|
||||
@@ -258,16 +268,26 @@ pub fn preset_models_for_provider(provider_type: &str) -> Option<Vec<Value>> {
|
||||
preset_model("chatgpt-image-latest", "openai", "ChatGPT Image Latest", "openai:image"),
|
||||
preset_model("dall-e-2", "openai", "DALL-E 2", "openai:image"),
|
||||
preset_model("dall-e-3", "openai", "DALL-E 3", "openai:image"),
|
||||
preset_model("gpt-5-codex", "openai", "GPT-5 Codex", "openai:cli"),
|
||||
preset_model("gpt-5-codex-mini", "openai", "GPT-5 Codex Mini", "openai:cli"),
|
||||
preset_model("gpt-5.1", "openai", "GPT-5.1", "openai:cli"),
|
||||
preset_model("gpt-5.1-codex", "openai", "GPT-5.1 Codex", "openai:cli"),
|
||||
preset_model("gpt-5.1-codex-mini", "openai", "GPT-5.1 Codex Mini", "openai:cli"),
|
||||
preset_model("gpt-5.1-codex-max", "openai", "GPT-5.1 Codex Max", "openai:cli"),
|
||||
preset_model("gpt-5.2", "openai", "GPT-5.2", "openai:cli"),
|
||||
preset_model("gpt-5.2-codex", "openai", "GPT-5.2 Codex", "openai:cli"),
|
||||
preset_model("gpt-5.3-codex", "openai", "GPT-5.3 Codex", "openai:cli"),
|
||||
preset_model("gpt-5.4", "openai", "GPT-5.4", "openai:cli"),
|
||||
preset_model("gpt-5-codex", "openai", "GPT-5 Codex", "openai:responses"),
|
||||
preset_model("gpt-5-codex-mini", "openai", "GPT-5 Codex Mini", "openai:responses"),
|
||||
preset_model("gpt-5.1", "openai", "GPT-5.1", "openai:responses"),
|
||||
preset_model("gpt-5.1-codex", "openai", "GPT-5.1 Codex", "openai:responses"),
|
||||
preset_model(
|
||||
"gpt-5.1-codex-mini",
|
||||
"openai",
|
||||
"GPT-5.1 Codex Mini",
|
||||
"openai:responses",
|
||||
),
|
||||
preset_model(
|
||||
"gpt-5.1-codex-max",
|
||||
"openai",
|
||||
"GPT-5.1 Codex Max",
|
||||
"openai:responses",
|
||||
),
|
||||
preset_model("gpt-5.2", "openai", "GPT-5.2", "openai:responses"),
|
||||
preset_model("gpt-5.2-codex", "openai", "GPT-5.2 Codex", "openai:responses"),
|
||||
preset_model("gpt-5.3-codex", "openai", "GPT-5.3 Codex", "openai:responses"),
|
||||
preset_model("gpt-5.4", "openai", "GPT-5.4", "openai:responses"),
|
||||
],
|
||||
_ => return None,
|
||||
};
|
||||
@@ -550,7 +570,11 @@ fn wildcard_matches(pattern: &str, model_id: &str) -> bool {
|
||||
}
|
||||
|
||||
fn normalize_api_format(value: &str) -> String {
|
||||
value.trim().to_ascii_lowercase()
|
||||
match value.trim().to_ascii_lowercase().as_str() {
|
||||
"openai:cli" => "openai:responses".to_string(),
|
||||
"openai:compact" => "openai:responses:compact".to_string(),
|
||||
other => other.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -644,7 +668,7 @@ mod tests {
|
||||
fn aggregate_models_for_cache_merges_api_formats_and_sorts_by_model_id() {
|
||||
let aggregated = aggregate_models_for_cache(&[
|
||||
json!({"id":"zeta","api_formats":["openai:chat"]}),
|
||||
json!({"id":"alpha","api_formats":["openai:cli"]}),
|
||||
json!({"id":"alpha","api_formats":["openai:responses"]}),
|
||||
json!({"id":"alpha","api_formats":["openai:chat"]}),
|
||||
]);
|
||||
assert_eq!(aggregated.len(), 2);
|
||||
@@ -652,7 +676,7 @@ mod tests {
|
||||
assert_eq!(aggregated[1]["id"], "zeta");
|
||||
assert_eq!(
|
||||
aggregated[0]["api_formats"],
|
||||
json!(["openai:chat", "openai:cli"])
|
||||
json!(["openai:chat", "openai:responses"])
|
||||
);
|
||||
}
|
||||
|
||||
@@ -679,10 +703,13 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_models_fetch_url_excludes_openai_responses() {
|
||||
fn build_models_fetch_url_supports_openai_responses() {
|
||||
assert_eq!(
|
||||
build_models_fetch_url("openai", "openai:responses", "https://example.com"),
|
||||
None
|
||||
Some((
|
||||
"https://example.com/v1/models".to_string(),
|
||||
"openai:responses".to_string()
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -716,7 +743,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn selected_models_fetch_endpoints_prefers_chat_and_excludes_responses() {
|
||||
fn selected_models_fetch_endpoints_prefers_chat_then_responses() {
|
||||
let key = sample_key("provider-1", "key-1", &["openai:chat", "openai:responses"]);
|
||||
let endpoints = vec![
|
||||
sample_endpoint(
|
||||
@@ -741,6 +768,25 @@ mod tests {
|
||||
let selected = selected_models_fetch_endpoints(&endpoints, &key);
|
||||
assert_eq!(selected.len(), 1);
|
||||
assert_eq!(selected[0].id, "endpoint-chat");
|
||||
|
||||
let key = sample_key("provider-1", "key-1", &["openai:responses"]);
|
||||
let endpoints = vec![
|
||||
sample_endpoint(
|
||||
"provider-1",
|
||||
"endpoint-cli",
|
||||
"openai:cli",
|
||||
"https://example.com",
|
||||
),
|
||||
sample_endpoint(
|
||||
"provider-1",
|
||||
"endpoint-responses",
|
||||
"openai:responses",
|
||||
"https://example.com",
|
||||
),
|
||||
];
|
||||
let selected = selected_models_fetch_endpoints(&endpoints, &key);
|
||||
assert_eq!(selected.len(), 1);
|
||||
assert_eq!(selected[0].id, "endpoint-responses");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -19,7 +19,7 @@ use serde_json::json;
|
||||
|
||||
use crate::build_models_fetch_url;
|
||||
|
||||
const OPENAI_CLI_USER_AGENT: &str = "openai-codex/1.0";
|
||||
const OPENAI_RESPONSES_USER_AGENT: &str = "openai-codex/1.0";
|
||||
const CLAUDE_CLI_USER_AGENT: &str = "claude-code/1.0.1";
|
||||
const GEMINI_CLI_USER_AGENT: &str = "GeminiCLI/0.1.5 (Windows; AMD64)";
|
||||
const CLAUDE_VERSION_HEADER: &str = "2023-06-01";
|
||||
@@ -383,8 +383,11 @@ fn apply_fetch_header_rules(
|
||||
fn standard_models_fetch_headers(api_format: &str) -> BTreeMap<String, String> {
|
||||
let api_format = api_format.trim().to_ascii_lowercase();
|
||||
match api_format.as_str() {
|
||||
"openai:cli" | "openai:compact" => {
|
||||
BTreeMap::from([("user-agent".to_string(), OPENAI_CLI_USER_AGENT.to_string())])
|
||||
"openai:responses" | "openai:responses:compact" | "openai:cli" | "openai:compact" => {
|
||||
BTreeMap::from([(
|
||||
"user-agent".to_string(),
|
||||
OPENAI_RESPONSES_USER_AGENT.to_string(),
|
||||
)])
|
||||
}
|
||||
"claude:chat" => BTreeMap::from([(
|
||||
"anthropic-version".to_string(),
|
||||
@@ -577,12 +580,12 @@ mod tests {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn builds_openai_cli_models_fetch_plan_with_cli_user_agent() {
|
||||
async fn builds_openai_responses_models_fetch_plan_with_codex_user_agent() {
|
||||
let runtime = TestRuntime {
|
||||
oauth_auth: None,
|
||||
proxy: None,
|
||||
};
|
||||
let mut transport = sample_transport("openai", "openai:cli", "api_key");
|
||||
let mut transport = sample_transport("openai", "openai:responses", "api_key");
|
||||
transport.key.decrypted_auth_config = None;
|
||||
let plan = build_models_fetch_execution_plan(&runtime, &transport)
|
||||
.await
|
||||
@@ -600,12 +603,12 @@ mod tests {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn builds_openai_compact_models_fetch_plan_with_bearer_authorization() {
|
||||
async fn builds_openai_responses_compact_models_fetch_plan_with_bearer_authorization() {
|
||||
let runtime = TestRuntime {
|
||||
oauth_auth: None,
|
||||
proxy: None,
|
||||
};
|
||||
let mut transport = sample_transport("openai", "openai:compact", "api_key");
|
||||
let mut transport = sample_transport("openai", "openai:responses:compact", "api_key");
|
||||
transport.key.decrypted_auth_config = None;
|
||||
let plan = build_models_fetch_execution_plan(&runtime, &transport)
|
||||
.await
|
||||
|
||||
Reference in New Issue
Block a user