feat(rust): 支持 image 同步转流式 SSE、free_team_first 调度预设及账户错误自动重试

- openai:image 同步响应桥接为流式 SSE(image_generation.completed / image_edit.completed 事件)
- image 请求解析与前置校验移除模型白名单,支持任意自定义模型名
- 调度器新增 free_team_first 预设(mode: both / free_only / team_only)
- pool config 解析重构:新增 POOL_ALLOWED_SCHEDULING_PRESETS 白名单,规范化 mode 字段
- 错误分类器新增账户/账单错误模式集,升级为 RetryUpstreamFailure 而非 StopSemanticClientError
- 修复 stream execution 中上游 headers 与输出 headers 混用导致 content-type 判断错误的问题
- codex image 工具始终写入 action 字段(generate/edit),仅 generate 操作填充默认 size/quality
- 前端:pool 节点组只展示实际执行过的候选节点,全部 skipped 时折叠为最后一个节点
- Redis 测试就绪检测从 TCP 连接改为 PING/PONG 协议验证
This commit is contained in:
fawney19
2026-04-24 10:05:55 +08:00
parent a9d10163af
commit 5d710d9d10
16 changed files with 1388 additions and 204 deletions

View File

@@ -57,7 +57,17 @@ fn apply_codex_openai_image_tool_overrides(body_object: &mut serde_json::Map<Str
tool.insert("type".to_string(), json!("image_generation"));
tool.entry("output_format".to_string())
.or_insert_with(|| json!(CODEX_OPENAI_IMAGE_DEFAULT_OUTPUT_FORMAT));
let action = tool
.get("action")
.and_then(Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
.unwrap_or("generate")
.to_string();
if !tool.contains_key("action") {
tool.insert("action".to_string(), json!("generate"));
}
if action == "generate" {
tool.entry("size".to_string())
.or_insert_with(|| json!(CODEX_IMAGE_TOOL_DEFAULT_SIZE));
tool.entry("quality".to_string())
@@ -450,6 +460,10 @@ mod tests {
provider_request_body["tools"][0]["output_format"],
json!("png")
);
assert_eq!(
provider_request_body["tools"][0]["action"],
json!("generate")
);
assert_eq!(
provider_request_body["model"],
json!(CODEX_OPENAI_IMAGE_INTERNAL_MODEL)

View File

@@ -2,6 +2,7 @@ use std::path::PathBuf;
use std::process::{Child, Command, Stdio};
use crate::wait_until;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
#[derive(Debug)]
pub struct ManagedRedisServer {
@@ -76,11 +77,7 @@ impl ManagedRedisServer {
let ready = wait_until(
std::time::Duration::from_secs(5),
std::time::Duration::from_millis(50),
|| async move {
tokio::net::TcpStream::connect(("127.0.0.1", port))
.await
.is_ok()
},
|| async move { redis_ping(("127.0.0.1", port)).await.unwrap_or(false) },
)
.await;
if !ready {
@@ -95,6 +92,14 @@ impl ManagedRedisServer {
}
}
async fn redis_ping(addr: (&str, u16)) -> Result<bool, std::io::Error> {
let mut stream = tokio::net::TcpStream::connect(addr).await?;
stream.write_all(b"*1\r\n$4\r\nPING\r\n").await?;
let mut buffer = [0_u8; 16];
let len = stream.read(&mut buffer).await?;
Ok(buffer[..len].starts_with(b"+PONG"))
}
impl Drop for ManagedRedisServer {
fn drop(&mut self) {
let _ = self.stop();