feat(codex-image): 封装 GPT Image 2 图片接口并收紧错误处理

- 新增 openai:image 路由、planner 与 finalize,内部通过 Codex responses image_generation tool 执行生图

- 补充 Codex OAuth/header 兼容、图片 success report 本地处理与相关前后端/集成测试

- 禁止 chat/completions 使用 gpt-image-2,图片接口限制 n=1,并移除 Provider 模型页的图片能力开关
This commit is contained in:
Entropy.Xu
2026-04-22 21:09:29 +08:00
committed by fawney19
parent 4374f53315
commit f55f22d2e8
55 changed files with 2676 additions and 52 deletions

View File

@@ -40,6 +40,9 @@ const CLIENT_ERROR_PATTERNS: &[&str] = &[
"validationexception",
];
const STRICT_CLIENT_ERROR_PATTERNS: &[&str] =
&["unknown parameter", "invalid model for this endpoint"];
const COMPATIBILITY_ERROR_PATTERNS: &[&str] = &[
"unsupported parameter",
"unsupported model",
@@ -154,6 +157,10 @@ pub(crate) fn classify_local_failover(
return LocalFailoverClassification::RetrySemanticThinkingError;
}
if is_strict_semantic_client_error(input.status_code, &parsed_error) {
return LocalFailoverClassification::StopSemanticClientError;
}
if is_semantic_compatibility_error(input.status_code, &parsed_error) {
return LocalFailoverClassification::RetrySemanticCompatibilityError;
}
@@ -324,6 +331,18 @@ fn is_semantic_client_error(status_code: u16, parsed: &ParsedLocalErrorResponse)
.any(|pattern| search_text.contains(&pattern.to_ascii_lowercase()))
}
fn is_strict_semantic_client_error(status_code: u16, parsed: &ParsedLocalErrorResponse) -> bool {
if status_code < 400 {
return false;
}
let search_text = semantic_search_text(parsed);
!search_text.is_empty()
&& STRICT_CLIENT_ERROR_PATTERNS
.iter()
.any(|pattern| search_text.contains(&pattern.to_ascii_lowercase()))
}
fn is_semantic_compatibility_error(status_code: u16, parsed: &ParsedLocalErrorResponse) -> bool {
if status_code < 400 {
return false;
@@ -462,6 +481,34 @@ mod tests {
);
}
#[test]
fn classifier_stops_unknown_parameter_errors_before_compatibility_retry() {
assert_eq!(
classify_local_failover(
&LocalFailoverPolicy::default(),
LocalFailoverInput::new(
400,
Some("{\"error\":{\"message\":\"Unknown parameter: 'tools[0].n'.\"}}")
)
),
LocalFailoverClassification::StopSemanticClientError
);
}
#[test]
fn classifier_stops_invalid_model_for_endpoint_errors_before_compatibility_retry() {
assert_eq!(
classify_local_failover(
&LocalFailoverPolicy::default(),
LocalFailoverInput::new(
400,
Some("{\"error\":{\"message\":\"invalid model for this endpoint\"}}")
)
),
LocalFailoverClassification::StopSemanticClientError
);
}
#[test]
fn classifier_retries_semantic_thinking_errors() {
assert_eq!(