对齐 Codex CLI 0.159.3 的通用画像、模型能力与原生协议

This commit is contained in:
MMEXA
2026-10-01 17:54:04 +08:00
parent 54fbcc25a1
commit 14befeda2c
38 changed files with 1938 additions and 383 deletions
+1
View File
@@ -10,6 +10,7 @@ description = "Pure AI API format, surface, planning, and finalize logic for Aet
aether-contracts.workspace = true
base64.workspace = true
http.workspace = true
os_info = { version = "=3.12.0", default-features = false }
regex.workspace = true
serde.workspace = true
serde_json.workspace = true
+21 -5
View File
@@ -1,4 +1,6 @@
use std::sync::{OnceLock, RwLock};
use std::sync::{LazyLock, OnceLock, RwLock};
static OS_INFO: LazyLock<os_info::Info> = LazyLock::new(os_info::get);
/// 当前支持的 Codex 客户端类型。
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
@@ -32,7 +34,19 @@ impl CodexClientProfile {
Ok(Self {
client_kind: CodexClientKind::Cli,
codex_version: version.to_owned(),
user_agent: format!("{}/{}", originator, version),
// 按 CLI 格式使用当前网关的公开平台信息。无客户端终端时使用官方
// unknown 标识,不复制调用方终端后缀、安装标识或个人身份。
user_agent: format!(
"{}/{} ({} {}; {}) unknown",
originator,
version,
OS_INFO.os_type(),
OS_INFO.version(),
OS_INFO.architecture().unwrap_or(std::env::consts::ARCH),
)
.chars()
.map(|ch| if matches!(ch, ' '..='~') { ch } else { '_' })
.collect(),
originator,
})
}
@@ -40,8 +54,8 @@ impl CodexClientProfile {
impl Default for CodexClientProfile {
fn default() -> Self {
// 远程发布检查不可用时仍保持现有线上行为,避免启动或请求被版本服务拖住。
Self::cli("0.153.4").expect("built-in Codex CLI profile must be valid")
// 最新已核验稳定版本;后台版本刷新继续作为版本真源。
Self::cli("0.159.3").expect("built-in Codex CLI profile must be valid")
}
}
@@ -97,7 +111,9 @@ mod tests {
let profile = CodexClientProfile::cli("0.200.1").expect("valid version");
assert_eq!(profile.client_kind, CodexClientKind::Cli);
assert_eq!(profile.originator, "codex_cli_rs");
assert_eq!(profile.user_agent, "codex_cli_rs/0.200.1");
assert!(profile.user_agent.starts_with("codex_cli_rs/0.200.1 ("));
assert!(profile.user_agent.ends_with(") unknown"));
assert!(profile.user_agent.contains(std::env::consts::ARCH));
}
#[test]
@@ -22,7 +22,7 @@ pub use plan_kinds::{
GEMINI_INTERACTIONS_SYNC_PLAN_KIND, GEMINI_VIDEO_CANCEL_SYNC_PLAN_KIND,
GEMINI_VIDEO_CREATE_SYNC_PLAN_KIND, OPENAI_CHAT_STREAM_PLAN_KIND, OPENAI_CHAT_SYNC_PLAN_KIND,
OPENAI_EMBEDDING_SYNC_PLAN_KIND, OPENAI_IMAGE_STREAM_PLAN_KIND, OPENAI_IMAGE_SYNC_PLAN_KIND,
OPENAI_REALTIME_STREAM_PLAN_KIND, OPENAI_RERANK_SYNC_PLAN_KIND,
OPENAI_MEMORIES_SYNC_PLAN_KIND, OPENAI_REALTIME_STREAM_PLAN_KIND, OPENAI_RERANK_SYNC_PLAN_KIND,
OPENAI_RESPONSES_COMPACT_STREAM_PLAN_KIND, OPENAI_RESPONSES_COMPACT_SYNC_PLAN_KIND,
OPENAI_RESPONSES_STREAM_PLAN_KIND, OPENAI_RESPONSES_SYNC_PLAN_KIND,
OPENAI_SEARCH_SYNC_PLAN_KIND, OPENAI_VIDEO_CANCEL_SYNC_PLAN_KIND,
@@ -49,7 +49,8 @@ pub use report_kinds::{
OPENAI_EMBEDDING_SYNC_ERROR_REPORT_KIND, OPENAI_EMBEDDING_SYNC_FINALIZE_REPORT_KIND,
OPENAI_EMBEDDING_SYNC_SUCCESS_REPORT_KIND, OPENAI_IMAGE_STREAM_SUCCESS_REPORT_KIND,
OPENAI_IMAGE_SYNC_ERROR_REPORT_KIND, OPENAI_IMAGE_SYNC_FINALIZE_REPORT_KIND,
OPENAI_IMAGE_SYNC_SUCCESS_REPORT_KIND, OPENAI_RESPONSES_COMPACT_STREAM_SUCCESS_REPORT_KIND,
OPENAI_IMAGE_SYNC_SUCCESS_REPORT_KIND, OPENAI_MEMORIES_SYNC_SUCCESS_REPORT_KIND,
OPENAI_RESPONSES_COMPACT_STREAM_SUCCESS_REPORT_KIND,
OPENAI_RESPONSES_COMPACT_SYNC_ERROR_REPORT_KIND,
OPENAI_RESPONSES_COMPACT_SYNC_FINALIZE_REPORT_KIND,
OPENAI_RESPONSES_COMPACT_SYNC_SUCCESS_REPORT_KIND, OPENAI_RESPONSES_STREAM_SUCCESS_REPORT_KIND,
@@ -5,6 +5,7 @@ pub const GEMINI_FILES_DELETE_PLAN_KIND: &str = "gemini_files_delete";
pub const GEMINI_FILES_DOWNLOAD_PLAN_KIND: &str = "gemini_files_download";
pub const OPENAI_IMAGE_STREAM_PLAN_KIND: &str = "openai_image_stream";
pub const OPENAI_IMAGE_SYNC_PLAN_KIND: &str = "openai_image_sync";
pub const OPENAI_MEMORIES_SYNC_PLAN_KIND: &str = "openai_memories_sync";
pub const OPENAI_VIDEO_CONTENT_PLAN_KIND: &str = "openai_video_content";
pub const OPENAI_VIDEO_CANCEL_SYNC_PLAN_KIND: &str = "openai_video_cancel_sync";
pub const OPENAI_VIDEO_REMIX_SYNC_PLAN_KIND: &str = "openai_video_remix_sync";
@@ -31,6 +31,8 @@ pub const OPENAI_RESPONSES_COMPACT_SYNC_SUCCESS_REPORT_KIND: &str =
"openai_responses_compact_sync_success";
pub const OPENAI_EMBEDDING_SYNC_SUCCESS_REPORT_KIND: &str = "openai_embedding_sync_success";
pub const OPENAI_SEARCH_SYNC_SUCCESS_REPORT_KIND: &str = "openai_search_sync_success";
pub const OPENAI_MEMORIES_SYNC_FINALIZE_REPORT_KIND: &str = "openai_memories_sync_finalize";
pub const OPENAI_MEMORIES_SYNC_SUCCESS_REPORT_KIND: &str = "openai_memories_sync_success";
pub const GEMINI_EMBEDDING_SYNC_SUCCESS_REPORT_KIND: &str = "gemini_embedding_sync_success";
pub const OPENAI_IMAGE_SYNC_SUCCESS_REPORT_KIND: &str = "openai_image_sync_success";
pub const CLAUDE_CLI_SYNC_SUCCESS_REPORT_KIND: &str = "claude_cli_sync_success";
@@ -62,6 +64,9 @@ pub const GEMINI_CLI_SYNC_ERROR_REPORT_KIND: &str = "gemini_cli_sync_error";
pub fn implicit_sync_finalize_report_kind(plan_kind: &str) -> Option<&'static str> {
match plan_kind {
super::plan_kinds::OPENAI_MEMORIES_SYNC_PLAN_KIND => {
Some(OPENAI_MEMORIES_SYNC_FINALIZE_REPORT_KIND)
}
OPENAI_CHAT_SYNC_PLAN_KIND => Some(OPENAI_CHAT_SYNC_FINALIZE_REPORT_KIND),
CLAUDE_CHAT_SYNC_PLAN_KIND => Some(CLAUDE_CHAT_SYNC_FINALIZE_REPORT_KIND),
GEMINI_CHAT_SYNC_PLAN_KIND => Some(GEMINI_CHAT_SYNC_FINALIZE_REPORT_KIND),
@@ -80,6 +85,7 @@ pub fn implicit_sync_finalize_report_kind(plan_kind: &str) -> Option<&'static st
pub fn core_error_default_client_api_format(report_kind: &str) -> Option<&'static str> {
match report_kind {
OPENAI_MEMORIES_SYNC_FINALIZE_REPORT_KIND => Some("openai:responses"),
OPENAI_CHAT_SYNC_FINALIZE_REPORT_KIND => Some("openai:chat"),
CLAUDE_CHAT_SYNC_FINALIZE_REPORT_KIND => Some("claude:messages"),
GEMINI_CHAT_SYNC_FINALIZE_REPORT_KIND => Some("gemini:generate_content"),
@@ -98,6 +104,7 @@ pub fn core_error_default_client_api_format(report_kind: &str) -> Option<&'stati
pub fn core_error_background_report_kind(report_kind: &str) -> Option<&'static str> {
match report_kind {
OPENAI_MEMORIES_SYNC_FINALIZE_REPORT_KIND => Some(OPENAI_RESPONSES_SYNC_ERROR_REPORT_KIND),
OPENAI_CHAT_SYNC_FINALIZE_REPORT_KIND => Some(OPENAI_CHAT_SYNC_ERROR_REPORT_KIND),
CLAUDE_CHAT_SYNC_FINALIZE_REPORT_KIND => Some(CLAUDE_CHAT_SYNC_ERROR_REPORT_KIND),
GEMINI_CHAT_SYNC_FINALIZE_REPORT_KIND => Some(GEMINI_CHAT_SYNC_ERROR_REPORT_KIND),
@@ -124,6 +131,7 @@ pub fn core_error_background_report_kind(report_kind: &str) -> Option<&'static s
pub fn core_success_background_report_kind(report_kind: &str) -> Option<&'static str> {
match report_kind {
OPENAI_MEMORIES_SYNC_FINALIZE_REPORT_KIND => Some(OPENAI_MEMORIES_SYNC_SUCCESS_REPORT_KIND),
OPENAI_CHAT_SYNC_FINALIZE_REPORT_KIND => Some(OPENAI_CHAT_SYNC_SUCCESS_REPORT_KIND),
CLAUDE_CHAT_SYNC_FINALIZE_REPORT_KIND => Some(CLAUDE_CHAT_SYNC_SUCCESS_REPORT_KIND),
GEMINI_CHAT_SYNC_FINALIZE_REPORT_KIND => Some(GEMINI_CHAT_SYNC_SUCCESS_REPORT_KIND),
@@ -34,6 +34,7 @@ pub enum ApiOperation {
ClaudeMessagesCreate,
ClaudeCountTokens,
OpenAiResponsesCompact,
OpenAiMemoriesSummarize,
}
impl ApiOperation {
@@ -42,6 +43,7 @@ impl ApiOperation {
Self::ClaudeMessagesCreate => "messages",
Self::ClaudeCountTokens => "count_tokens",
Self::OpenAiResponsesCompact => "compact",
Self::OpenAiMemoriesSummarize => "trace_summarize",
}
}
}
@@ -55,5 +57,9 @@ mod tests {
assert_eq!(ClientSurface::ClaudeCode.as_str(), "claude_code");
assert_eq!(ApiOperation::ClaudeMessagesCreate.as_str(), "messages");
assert_eq!(ApiOperation::ClaudeCountTokens.as_str(), "count_tokens");
assert_eq!(
ApiOperation::OpenAiMemoriesSummarize.as_str(),
"trace_summarize"
);
}
}
@@ -380,149 +380,22 @@ fn bundled_codex_model_card(spec: BundledCodexModelCardSpec<'_>) -> Value {
})
}
fn bundled_gpt_5_6_codex_model_card(
model_id: &str,
display_name: &str,
description: &str,
default_reasoning_level: &str,
priority: u64,
multi_agent_version: &str,
supports_ultra: bool,
) -> Value {
let mut efforts = vec!["low", "medium", "high", "xhigh", "max"];
if supports_ultra {
efforts.push("ultra");
}
let mut card = bundled_codex_model_card(BundledCodexModelCardSpec {
model_id,
display_name,
description,
default_reasoning_level,
default_reasoning_summary: "none",
use_responses_lite: true,
efforts: &efforts,
default_verbosity: "low",
supports_priority_tier: true,
});
let object = card
.as_object_mut()
.expect("bundled Codex model card must be an object");
object.extend(
json!({
"shell_type": "shell_command",
"supports_image_detail_original": true,
"supports_search_tool": true,
"input_modalities": ["text", "image"],
"context_window": 372_000,
"max_context_window": 372_000,
"comp_hash": "3000",
"experimental_supported_tools": [],
"visibility": "list",
"supported_in_api": true,
"priority": priority,
"additional_speed_tiers": ["fast"],
"multi_agent_version": multi_agent_version,
"tool_mode": "code_mode_only",
"prefer_websockets": true,
"reasoning_summary_format": "experimental",
"include_skills_usage_instructions": false,
"apply_patch_tool_type": "freeform",
"web_search_tool_type": "text_and_image",
"truncation_policy": { "mode": "tokens", "limit": 10_000 },
"minimal_client_version": "0.144.0",
})
.as_object()
.expect("bundled Codex model card extension must be an object")
.clone(),
);
card
}
fn bundled_codex_auto_review_model_card() -> Value {
let mut card = bundled_codex_model_card(BundledCodexModelCardSpec {
model_id: "codex-auto-review",
display_name: "Codex Auto Review",
description: "Automatic approval review model for Codex.",
default_reasoning_level: "medium",
default_reasoning_summary: "none",
use_responses_lite: false,
efforts: &["low", "medium", "high", "xhigh"],
default_verbosity: "low",
supports_priority_tier: false,
});
let object = card
.as_object_mut()
.expect("bundled Codex model card must be an object");
object.extend(
json!({
"shell_type": "shell_command",
"supports_image_detail_original": true,
"supports_search_tool": true,
"input_modalities": ["text", "image"],
"context_window": 272_000,
"max_context_window": 1_000_000,
"experimental_supported_tools": [],
"visibility": "hide",
"supported_in_api": true,
"priority": 43,
"additional_speed_tiers": [],
"prefer_websockets": true,
"reasoning_summary_format": "experimental",
"include_skills_usage_instructions": false,
"apply_patch_tool_type": "freeform",
"web_search_tool_type": "text_and_image",
"truncation_policy": { "mode": "tokens", "limit": 10_000 },
"minimal_client_version": "0.98.0",
})
.as_object()
.expect("bundled Codex model card extension must be an object")
.clone(),
);
card
}
pub fn bundled_codex_model_cards() -> &'static [Value] {
static CARDS: OnceLock<Vec<Value>> = OnceLock::new();
CARDS.get_or_init(|| {
vec![
bundled_gpt_5_6_codex_model_card(
"gpt-5.6-sol",
"GPT-5.6-Sol",
"Latest frontier agentic coding model.",
"low",
1,
"v2",
true,
),
bundled_gpt_5_6_codex_model_card(
"gpt-5.6-terra",
"GPT-5.6-Terra",
"Balanced agentic coding model for everyday work.",
"medium",
2,
"v2",
true,
),
bundled_gpt_5_6_codex_model_card(
"gpt-5.6-luna",
"GPT-5.6-Luna",
"Fast and affordable agentic coding model.",
"medium",
3,
"v1",
false,
),
bundled_codex_model_card(BundledCodexModelCardSpec {
model_id: "gpt-5.5",
display_name: "GPT-5.5",
description: "Frontier model for complex coding, research, and real-world work.",
default_reasoning_level: "medium",
default_reasoning_summary: "none",
use_responses_lite: false,
efforts: &["low", "medium", "high", "xhigh"],
default_verbosity: "low",
supports_priority_tier: true,
}),
// 公开能力快照来自 openai/codex rust-v0.159.3 的
// codex-rs/models-manager/models.json;账户远端目录继续优先。
let mut cards: Vec<Value> = serde_json::from_str(include_str!("codex_models_0_159_3.json"))
.expect("bundled Codex model capability snapshot must be valid JSON");
for card in &mut cards {
let object = card.as_object_mut().expect("model card must be an object");
object.insert("id".to_string(), object["slug"].clone());
object.insert("object".to_string(), json!("model"));
object.insert("owned_by".to_string(), json!("openai"));
object.insert("api_formats".to_string(), json!(["openai:responses"]));
}
// 保留现有配置路由使用的旧模型标识。
cards.extend(vec![
bundled_codex_model_card(BundledCodexModelCardSpec {
model_id: "gpt-5.4",
display_name: "GPT-5.4",
@@ -556,8 +429,8 @@ pub fn bundled_codex_model_cards() -> &'static [Value] {
default_verbosity: "low",
supports_priority_tier: false,
}),
bundled_codex_auto_review_model_card(),
]
]);
cards
})
}
@@ -2183,10 +2056,46 @@ mod tests {
#[test]
fn codex_client_user_agent_matches_originator_and_version() {
let profile = crate::codex_client_profile();
assert_eq!(
profile.user_agent,
format!("{}/{}", profile.originator, profile.codex_version)
);
assert!(profile.user_agent.starts_with(&format!(
"{}/{} (",
profile.originator, profile.codex_version
)));
assert!(profile.user_agent.ends_with(") unknown"));
}
#[test]
fn bundled_latest_cli_models_preserve_capabilities_without_account_data() {
for model in ["gpt-6-astra", "gpt-6.1-sol"] {
let card = bundled_codex_model_cards()
.iter()
.find(|card| card["slug"] == model)
.unwrap();
assert_eq!(card["context_window"], 272_000);
assert_eq!(card["max_context_window"], 872_000);
assert_eq!(card["minimal_client_version"], "0.153.0");
assert_eq!(
card["supported_reasoning_levels"]
.as_array()
.unwrap()
.iter()
.map(|level| level["effort"].as_str().unwrap())
.collect::<Vec<_>>(),
["low", "medium", "high", "xhigh", "max", "ultra"]
);
for private in [
"account_id",
"user_id",
"installation_id",
"cookie",
"authorization",
"comp_hash",
] {
assert!(
card.get(private).is_none(),
"unexpected account field: {private}"
);
}
}
}
#[test]
@@ -2824,7 +2733,7 @@ mod tests {
"codex-auto-review",
None,
);
assert!(!capabilities.use_responses_lite);
assert!(capabilities.use_responses_lite);
assert_eq!(
capabilities.default_reasoning_effort.as_deref(),
Some("medium")
@@ -2832,7 +2741,7 @@ mod tests {
assert_eq!(capabilities.default_reasoning_summary, None);
assert!(capabilities.supports_parallel_tool_calls);
assert_eq!(capabilities.default_verbosity.as_deref(), Some("low"));
assert!(capabilities.supported_service_tiers.is_empty());
assert_eq!(capabilities.supported_service_tiers, vec!["priority"]);
}
#[test]
@@ -0,0 +1,952 @@
[
{
"slug": "gpt-6-astra",
"prefer_websockets": true,
"support_verbosity": true,
"default_verbosity": "low",
"apply_patch_tool_type": "freeform",
"web_search_tool_type": "text_and_image",
"input_modalities": [
"text",
"image"
],
"supports_image_detail_original": true,
"truncation_policy": {
"mode": "tokens",
"limit": 10000
},
"supports_parallel_tool_calls": true,
"tool_mode": "code_mode_only",
"multi_agent_version": "v2",
"multi_agent_reasoning_effort": "xhigh",
"use_responses_lite": true,
"supports_reasoning_effort_updates": true,
"include_skills_usage_instructions": false,
"include_apps_usage_instructions": false,
"include_plugin_usage_instructions": false,
"guardian": null,
"node_repl_auto_review_required": true,
"node_repl_disabled": false,
"requires_sandboxed_review": false,
"auto_review_model_override": null,
"model_specialty": null,
"context_window": 272000,
"max_context_window": 872000,
"auto_compact_token_limit": null,
"default_reasoning_summary": "none",
"display_name": "GPT-6-Astra",
"description": "Frontier intelligence for the most demanding work.",
"default_reasoning_level": "low",
"supported_reasoning_levels": [
{
"effort": "low",
"description": "Fast responses with lighter reasoning"
},
{
"effort": "medium",
"description": "Balances speed and reasoning depth for everyday tasks"
},
{
"effort": "high",
"description": "Greater reasoning depth for complex problems"
},
{
"effort": "xhigh",
"description": "Extra high reasoning depth for complex problems"
},
{
"effort": "max",
"description": "Maximum reasoning depth for the hardest problems"
},
{
"effort": "ultra",
"description": "Maximum reasoning with automatic task delegation"
}
],
"shell_type": "shell_command",
"visibility": "list",
"minimal_client_version": "0.153.0",
"supported_in_api": true,
"availability_nux": null,
"upgrade": null,
"priority": 2,
"experimental_supported_tools": [
"send_user_message_async",
"clock"
],
"supports_search_tool": true,
"supports_experimental_context": false,
"default_service_tier": null,
"service_tiers": [
{
"id": "priority",
"name": "Fast",
"description": "2x speed, increased usage"
}
],
"additional_speed_tiers": [
"fast"
],
"supports_reasoning_summary_parameter": true,
"supports_reasoning_summaries": true
},
{
"slug": "gpt-6.1-sol",
"prefer_websockets": true,
"support_verbosity": true,
"default_verbosity": "low",
"apply_patch_tool_type": "freeform",
"web_search_tool_type": "text_and_image",
"input_modalities": [
"text",
"image"
],
"supports_image_detail_original": true,
"truncation_policy": {
"mode": "tokens",
"limit": 10000
},
"supports_parallel_tool_calls": true,
"tool_mode": "code_mode_only",
"multi_agent_version": "v2",
"multi_agent_reasoning_effort": "xhigh",
"use_responses_lite": true,
"include_skills_usage_instructions": false,
"include_apps_usage_instructions": false,
"include_plugin_usage_instructions": false,
"guardian": null,
"node_repl_auto_review_required": true,
"node_repl_disabled": false,
"requires_sandboxed_review": false,
"auto_review_model_override": null,
"model_specialty": null,
"context_window": 272000,
"max_context_window": 872000,
"auto_compact_token_limit": null,
"default_reasoning_summary": "none",
"display_name": "GPT-6.1-Sol",
"description": "Latest workhorse model for coding and everyday work.",
"default_reasoning_level": "low",
"supported_reasoning_levels": [
{
"effort": "low",
"description": "Fast responses with lighter reasoning"
},
{
"effort": "medium",
"description": "Balances speed and reasoning depth for everyday tasks"
},
{
"effort": "high",
"description": "Greater reasoning depth for complex problems"
},
{
"effort": "xhigh",
"description": "Extra high reasoning depth for complex problems"
},
{
"effort": "max",
"description": "Maximum reasoning depth for the hardest problems"
},
{
"effort": "ultra",
"description": "Maximum reasoning with automatic task delegation"
}
],
"shell_type": "shell_command",
"visibility": "list",
"minimal_client_version": "0.153.0",
"supported_in_api": true,
"availability_nux": {
"message": "Maximize usage with GPT-6.1 Sol. Try it on complex work for near-Astra performance at a lower cost."
},
"upgrade": null,
"priority": 1,
"experimental_supported_tools": [
"send_user_message_async",
"clock"
],
"supports_search_tool": true,
"supports_experimental_context": false,
"default_service_tier": null,
"service_tiers": [
{
"id": "priority",
"name": "Fast",
"description": "2x speed, increased usage"
}
],
"additional_speed_tiers": [
"fast"
],
"supports_reasoning_summary_parameter": true,
"supports_reasoning_summaries": true,
"supports_reasoning_effort_updates": true
},
{
"slug": "gpt-6-sol",
"prefer_websockets": true,
"support_verbosity": true,
"default_verbosity": "low",
"apply_patch_tool_type": "freeform",
"web_search_tool_type": "text_and_image",
"input_modalities": [
"text",
"image"
],
"supports_image_detail_original": true,
"truncation_policy": {
"mode": "tokens",
"limit": 10000
},
"supports_parallel_tool_calls": true,
"tool_mode": "code_mode_only",
"multi_agent_version": "v2",
"multi_agent_reasoning_effort": null,
"use_responses_lite": true,
"include_skills_usage_instructions": false,
"include_apps_usage_instructions": false,
"include_plugin_usage_instructions": false,
"guardian": null,
"node_repl_auto_review_required": true,
"node_repl_disabled": false,
"requires_sandboxed_review": false,
"auto_review_model_override": null,
"model_specialty": null,
"context_window": 272000,
"max_context_window": 872000,
"auto_compact_token_limit": null,
"default_reasoning_summary": "none",
"display_name": "GPT-6-Sol",
"description": "Previous generation workhorse model.",
"default_reasoning_level": "medium",
"supported_reasoning_levels": [
{
"effort": "low",
"description": "Fast responses with lighter reasoning"
},
{
"effort": "medium",
"description": "Balances speed and reasoning depth for everyday tasks"
},
{
"effort": "high",
"description": "Greater reasoning depth for complex problems"
},
{
"effort": "xhigh",
"description": "Extra high reasoning depth for complex problems"
},
{
"effort": "max",
"description": "Maximum reasoning depth for the hardest problems"
},
{
"effort": "ultra",
"description": "Maximum reasoning with automatic task delegation"
}
],
"shell_type": "shell_command",
"visibility": "list",
"minimal_client_version": "0.155.0",
"supported_in_api": true,
"availability_nux": null,
"upgrade": null,
"priority": 3,
"experimental_supported_tools": [
"send_user_message_async",
"clock"
],
"supports_search_tool": true,
"supports_experimental_context": false,
"default_service_tier": "priority",
"service_tiers": [
{
"id": "priority",
"name": "Fast",
"description": "1.5x speed"
}
],
"additional_speed_tiers": [
"fast"
],
"supports_reasoning_summary_parameter": true,
"supports_reasoning_summaries": true
},
{
"slug": "gpt-6-luna",
"prefer_websockets": true,
"support_verbosity": true,
"default_verbosity": "low",
"apply_patch_tool_type": "freeform",
"web_search_tool_type": "text_and_image",
"input_modalities": [
"text",
"image"
],
"supports_image_detail_original": true,
"truncation_policy": {
"mode": "tokens",
"limit": 10000
},
"supports_parallel_tool_calls": true,
"tool_mode": "code_mode_only",
"multi_agent_version": "v2",
"multi_agent_reasoning_effort": null,
"use_responses_lite": true,
"include_skills_usage_instructions": false,
"include_apps_usage_instructions": false,
"include_plugin_usage_instructions": false,
"guardian": null,
"node_repl_auto_review_required": false,
"node_repl_disabled": false,
"requires_sandboxed_review": false,
"auto_review_model_override": null,
"model_specialty": null,
"context_window": 272000,
"max_context_window": 872000,
"auto_compact_token_limit": null,
"default_reasoning_summary": "none",
"display_name": "GPT-6-Luna",
"description": "Fast and affordable model for easier tasks.",
"default_reasoning_level": "medium",
"supported_reasoning_levels": [
{
"effort": "low",
"description": "Fast responses with lighter reasoning"
},
{
"effort": "medium",
"description": "Balances speed and reasoning depth for everyday tasks"
},
{
"effort": "high",
"description": "Greater reasoning depth for complex problems"
},
{
"effort": "xhigh",
"description": "Extra high reasoning depth for complex problems"
},
{
"effort": "max",
"description": "Maximum reasoning depth for the hardest problems"
}
],
"shell_type": "shell_command",
"visibility": "list",
"minimal_client_version": "0.155.0",
"supported_in_api": true,
"availability_nux": null,
"upgrade": null,
"priority": 4,
"experimental_supported_tools": [
"send_user_message_async",
"clock"
],
"supports_search_tool": true,
"supports_experimental_context": false,
"default_service_tier": "priority",
"service_tiers": [
{
"id": "priority",
"name": "Fast",
"description": "1.5x speed"
}
],
"additional_speed_tiers": [
"fast"
],
"supports_reasoning_summary_parameter": true,
"supports_reasoning_summaries": true
},
{
"slug": "gpt-5.6-sol",
"prefer_websockets": true,
"support_verbosity": true,
"default_verbosity": "low",
"apply_patch_tool_type": "freeform",
"web_search_tool_type": "text_and_image",
"input_modalities": [
"text",
"image"
],
"supports_image_detail_original": true,
"truncation_policy": {
"mode": "tokens",
"limit": 10000
},
"supports_parallel_tool_calls": true,
"tool_mode": "code_mode_only",
"multi_agent_version": "v2",
"multi_agent_reasoning_effort": null,
"use_responses_lite": true,
"supports_reasoning_effort_updates": false,
"include_skills_usage_instructions": false,
"include_apps_usage_instructions": true,
"include_plugin_usage_instructions": true,
"guardian": null,
"node_repl_auto_review_required": false,
"node_repl_disabled": false,
"requires_sandboxed_review": false,
"auto_review_model_override": null,
"model_specialty": null,
"context_window": 272000,
"max_context_window": 872000,
"auto_compact_token_limit": null,
"default_reasoning_summary": "none",
"display_name": "GPT-5.6-Sol",
"description": "Older generation workhorse model.",
"default_reasoning_level": "low",
"supported_reasoning_levels": [
{
"effort": "low",
"description": "Fast responses with lighter reasoning"
},
{
"effort": "medium",
"description": "Balances speed and reasoning depth for everyday tasks"
},
{
"effort": "high",
"description": "Greater reasoning depth for complex problems"
},
{
"effort": "xhigh",
"description": "Extra high reasoning depth for complex problems"
},
{
"effort": "max",
"description": "Maximum reasoning depth for the hardest problems"
},
{
"effort": "ultra",
"description": "Maximum reasoning with automatic task delegation"
}
],
"shell_type": "shell_command",
"visibility": "list",
"minimal_client_version": "0.144.0",
"supported_in_api": true,
"availability_nux": null,
"upgrade": {
"model": "gpt-6-sol",
"migration_markdown": "Meet GPT-6 Sol\n\nOur latest Sol is more intelligent and more efficient so your usage limits go further. This model is a great daily driver for complex tasks, especially coding.\n",
"retirement_at": null
},
"priority": 5,
"experimental_supported_tools": [],
"supports_search_tool": true,
"supports_experimental_context": false,
"default_service_tier": null,
"service_tiers": [
{
"id": "priority",
"name": "Fast",
"description": "1.5x speed, increased usage"
}
],
"additional_speed_tiers": [
"fast"
],
"supports_reasoning_summary_parameter": true,
"supports_reasoning_summaries": true
},
{
"slug": "gpt-5.6-terra",
"prefer_websockets": true,
"support_verbosity": true,
"default_verbosity": "low",
"apply_patch_tool_type": "freeform",
"web_search_tool_type": "text_and_image",
"input_modalities": [
"text",
"image"
],
"supports_image_detail_original": true,
"truncation_policy": {
"mode": "tokens",
"limit": 10000
},
"supports_parallel_tool_calls": true,
"tool_mode": "code_mode_only",
"multi_agent_version": "v2",
"multi_agent_reasoning_effort": null,
"use_responses_lite": true,
"supports_reasoning_effort_updates": false,
"include_skills_usage_instructions": false,
"include_apps_usage_instructions": true,
"include_plugin_usage_instructions": true,
"guardian": null,
"node_repl_auto_review_required": false,
"node_repl_disabled": false,
"requires_sandboxed_review": false,
"auto_review_model_override": null,
"model_specialty": null,
"context_window": 272000,
"max_context_window": 872000,
"auto_compact_token_limit": null,
"default_reasoning_summary": "none",
"display_name": "GPT-5.6-Terra",
"description": "Older balanced model for straightforward work.",
"default_reasoning_level": "medium",
"supported_reasoning_levels": [
{
"effort": "low",
"description": "Fast responses with lighter reasoning"
},
{
"effort": "medium",
"description": "Balances speed and reasoning depth for everyday tasks"
},
{
"effort": "high",
"description": "Greater reasoning depth for complex problems"
},
{
"effort": "xhigh",
"description": "Extra high reasoning depth for complex problems"
},
{
"effort": "max",
"description": "Maximum reasoning depth for the hardest problems"
},
{
"effort": "ultra",
"description": "Maximum reasoning with automatic task delegation"
}
],
"shell_type": "shell_command",
"visibility": "list",
"minimal_client_version": "0.144.0",
"supported_in_api": true,
"availability_nux": null,
"upgrade": {
"model": "gpt-6-sol",
"migration_markdown": "Meet GPT-6 Sol\n\nOur latest Sol is more intelligent and more efficient so your usage limits go further. This model is a great daily driver for complex tasks, especially coding.\n",
"retirement_at": null
},
"priority": 8,
"experimental_supported_tools": [],
"supports_search_tool": true,
"supports_experimental_context": false,
"default_service_tier": null,
"service_tiers": [
{
"id": "priority",
"name": "Fast",
"description": "1.5x speed, increased usage"
}
],
"additional_speed_tiers": [
"fast"
],
"supports_reasoning_summary_parameter": true,
"supports_reasoning_summaries": true
},
{
"slug": "gpt-5.6-luna",
"prefer_websockets": true,
"support_verbosity": true,
"default_verbosity": "low",
"apply_patch_tool_type": "freeform",
"web_search_tool_type": "text_and_image",
"input_modalities": [
"text",
"image"
],
"supports_image_detail_original": true,
"truncation_policy": {
"mode": "tokens",
"limit": 10000
},
"supports_parallel_tool_calls": true,
"tool_mode": "code_mode_only",
"multi_agent_version": "v1",
"multi_agent_reasoning_effort": null,
"use_responses_lite": true,
"supports_reasoning_effort_updates": false,
"include_skills_usage_instructions": false,
"include_apps_usage_instructions": true,
"include_plugin_usage_instructions": true,
"guardian": null,
"node_repl_auto_review_required": false,
"node_repl_disabled": false,
"requires_sandboxed_review": false,
"auto_review_model_override": null,
"model_specialty": null,
"context_window": 272000,
"max_context_window": 872000,
"auto_compact_token_limit": null,
"default_reasoning_summary": "none",
"display_name": "GPT-5.6-Luna",
"description": "Older fast and efficient model.",
"default_reasoning_level": "medium",
"supported_reasoning_levels": [
{
"effort": "low",
"description": "Fast responses with lighter reasoning"
},
{
"effort": "medium",
"description": "Balances speed and reasoning depth for everyday tasks"
},
{
"effort": "high",
"description": "Greater reasoning depth for complex problems"
},
{
"effort": "xhigh",
"description": "Extra high reasoning depth for complex problems"
},
{
"effort": "max",
"description": "Maximum reasoning depth for the hardest problems"
}
],
"shell_type": "shell_command",
"visibility": "list",
"minimal_client_version": "0.144.0",
"supported_in_api": true,
"availability_nux": null,
"upgrade": {
"model": "gpt-6-luna",
"migration_markdown": "Meet GPT-6 Luna\n\nOur latest Luna is significantly more efficient, so your usage limits go even further. Reach for it for any job that doesn't require frontier intelligence.\n",
"retirement_at": null
},
"priority": 9,
"experimental_supported_tools": [],
"supports_search_tool": true,
"supports_experimental_context": false,
"default_service_tier": null,
"service_tiers": [
{
"id": "priority",
"name": "Fast",
"description": "1.5x speed, increased usage"
}
],
"additional_speed_tiers": [
"fast"
],
"supports_reasoning_summary_parameter": true,
"supports_reasoning_summaries": true
},
{
"slug": "gpt-daybreak-blue-latest",
"prefer_websockets": true,
"support_verbosity": true,
"default_verbosity": "low",
"apply_patch_tool_type": "freeform",
"web_search_tool_type": "text_and_image",
"input_modalities": [
"text",
"image"
],
"supports_image_detail_original": true,
"truncation_policy": {
"mode": "tokens",
"limit": 10000
},
"supports_parallel_tool_calls": true,
"tool_mode": "code_mode_only",
"multi_agent_version": "v2",
"use_responses_lite": true,
"include_skills_usage_instructions": false,
"include_apps_usage_instructions": true,
"include_plugin_usage_instructions": true,
"node_repl_auto_review_required": false,
"node_repl_disabled": false,
"auto_review_model_override": null,
"model_specialty": "cyber",
"context_window": 272000,
"max_context_window": 872000,
"auto_compact_token_limit": null,
"default_reasoning_summary": "none",
"display_name": "Daybreak Blue",
"description": "Latest frontier agentic coding model for broad defensive cybersecurity work.",
"default_reasoning_level": "low",
"supported_reasoning_levels": [
{
"effort": "low",
"description": "Fast responses with lighter reasoning"
},
{
"effort": "medium",
"description": "Balances speed and reasoning depth for everyday tasks"
},
{
"effort": "high",
"description": "Greater reasoning depth for complex problems"
},
{
"effort": "xhigh",
"description": "Extra high reasoning depth for complex problems"
},
{
"effort": "max",
"description": "Maximum reasoning depth for the hardest problems"
},
{
"effort": "ultra",
"description": "Maximum reasoning with automatic task delegation"
}
],
"shell_type": "shell_command",
"visibility": "hide",
"minimal_client_version": "0.142.2",
"supported_in_api": true,
"availability_nux": null,
"upgrade": null,
"priority": 11,
"experimental_supported_tools": [],
"supports_search_tool": true,
"default_service_tier": null,
"service_tiers": [],
"additional_speed_tiers": [],
"supports_reasoning_summary_parameter": true,
"supports_reasoning_summaries": true
},
{
"slug": "gpt-daybreak-red-latest",
"prefer_websockets": true,
"support_verbosity": true,
"default_verbosity": "high",
"apply_patch_tool_type": "freeform",
"web_search_tool_type": "text_and_image",
"input_modalities": [
"text",
"image"
],
"supports_image_detail_original": true,
"truncation_policy": {
"mode": "tokens",
"limit": 10000
},
"supports_parallel_tool_calls": true,
"tool_mode": "code_mode_only",
"multi_agent_version": "v2",
"use_responses_lite": true,
"include_skills_usage_instructions": false,
"include_apps_usage_instructions": false,
"include_plugin_usage_instructions": false,
"node_repl_auto_review_required": false,
"node_repl_disabled": false,
"auto_review_model_override": null,
"model_specialty": "cyber",
"context_window": 372000,
"max_context_window": 372000,
"auto_compact_token_limit": null,
"default_reasoning_summary": "none",
"display_name": "Daybreak Red",
"description": "Cyber-permissive variant of our latest frontier agentic coding model for advanced, authorized cybersecurity research.",
"default_reasoning_level": "medium",
"supported_reasoning_levels": [
{
"effort": "low",
"description": "Fast responses with lighter reasoning"
},
{
"effort": "medium",
"description": "Balances speed and reasoning depth for everyday tasks"
},
{
"effort": "high",
"description": "Greater reasoning depth for complex problems"
},
{
"effort": "xhigh",
"description": "Extra high reasoning depth for complex problems"
},
{
"effort": "max",
"description": "Maximum reasoning depth for the hardest problems"
},
{
"effort": "ultra",
"description": "Maximum reasoning with automatic task delegation"
}
],
"shell_type": "shell_command",
"visibility": "hide",
"minimal_client_version": "0.142.2",
"supported_in_api": true,
"availability_nux": null,
"upgrade": null,
"priority": 12,
"experimental_supported_tools": [],
"supports_search_tool": true,
"default_service_tier": null,
"service_tiers": [],
"additional_speed_tiers": [],
"supports_reasoning_summary_parameter": true,
"supports_reasoning_summaries": true
},
{
"slug": "gpt-5.5",
"prefer_websockets": true,
"support_verbosity": true,
"default_verbosity": "low",
"apply_patch_tool_type": "freeform",
"web_search_tool_type": "text_and_image",
"input_modalities": [
"text",
"image"
],
"supports_image_detail_original": true,
"truncation_policy": {
"mode": "tokens",
"limit": 10000
},
"supports_parallel_tool_calls": true,
"tool_mode": null,
"multi_agent_version": null,
"multi_agent_reasoning_effort": null,
"use_responses_lite": false,
"supports_reasoning_effort_updates": false,
"include_skills_usage_instructions": true,
"include_apps_usage_instructions": true,
"include_plugin_usage_instructions": true,
"guardian": null,
"node_repl_auto_review_required": false,
"node_repl_disabled": false,
"requires_sandboxed_review": false,
"auto_review_model_override": null,
"model_specialty": null,
"context_window": 272000,
"max_context_window": 272000,
"auto_compact_token_limit": null,
"default_reasoning_summary": "none",
"display_name": "GPT-5.5",
"description": "Legacy coding model.",
"default_reasoning_level": "medium",
"supported_reasoning_levels": [
{
"effort": "low",
"description": "Fast responses with lighter reasoning"
},
{
"effort": "medium",
"description": "Balances speed and reasoning depth for everyday tasks"
},
{
"effort": "high",
"description": "Greater reasoning depth for complex problems"
},
{
"effort": "xhigh",
"description": "Extra high reasoning depth for complex problems"
}
],
"shell_type": "shell_command",
"visibility": "list",
"minimal_client_version": "0.124.0",
"supported_in_api": true,
"availability_nux": null,
"upgrade": {
"model": "gpt-6-sol",
"migration_markdown": "Meet GPT-6 Sol\n\nOur latest Sol is more intelligent and more efficient so your usage limits go further. This model is a great daily driver for complex tasks, especially coding.\n",
"retirement_at": null
},
"priority": 13,
"experimental_supported_tools": [],
"supports_search_tool": true,
"supports_experimental_context": false,
"default_service_tier": null,
"service_tiers": [
{
"id": "priority",
"name": "Fast",
"description": "1.5x speed, increased usage"
}
],
"additional_speed_tiers": [
"fast"
],
"supports_reasoning_summary_parameter": true,
"supports_reasoning_summaries": true
},
{
"slug": "codex-auto-review",
"prefer_websockets": true,
"support_verbosity": true,
"default_verbosity": "low",
"apply_patch_tool_type": "freeform",
"web_search_tool_type": "text_and_image",
"input_modalities": [
"text",
"image"
],
"supports_image_detail_original": true,
"truncation_policy": {
"mode": "tokens",
"limit": 10000
},
"supports_parallel_tool_calls": true,
"tool_mode": "code_mode_only",
"multi_agent_version": "v1",
"multi_agent_reasoning_effort": null,
"use_responses_lite": true,
"supports_reasoning_effort_updates": false,
"include_skills_usage_instructions": false,
"include_apps_usage_instructions": false,
"include_plugin_usage_instructions": false,
"guardian": null,
"node_repl_auto_review_required": false,
"node_repl_disabled": false,
"requires_sandboxed_review": false,
"auto_review_model_override": null,
"model_specialty": null,
"context_window": 272000,
"max_context_window": 872000,
"auto_compact_token_limit": null,
"default_reasoning_summary": "none",
"display_name": "Codex Auto Review",
"description": "Automatic approval review model for Codex.",
"default_reasoning_level": "medium",
"supported_reasoning_levels": [
{
"effort": "low",
"description": "Fast responses with lighter reasoning"
},
{
"effort": "medium",
"description": "Balances speed and reasoning depth for everyday tasks"
},
{
"effort": "high",
"description": "Greater reasoning depth for complex problems"
},
{
"effort": "xhigh",
"description": "Extra high reasoning depth for complex problems"
},
{
"effort": "max",
"description": "Maximum reasoning depth for the hardest problems"
}
],
"shell_type": "shell_command",
"visibility": "hide",
"minimal_client_version": "0.98.0",
"supported_in_api": true,
"availability_nux": null,
"upgrade": null,
"priority": 43,
"experimental_supported_tools": [],
"supports_search_tool": true,
"supports_experimental_context": false,
"default_service_tier": null,
"service_tiers": [
{
"id": "priority",
"name": "Fast",
"description": "1.5x speed, increased usage"
}
],
"additional_speed_tiers": [
"fast"
],
"supports_reasoning_summary_parameter": true,
"supports_reasoning_summaries": true
}
]
@@ -7,6 +7,7 @@ use crate::contracts::{
GEMINI_EMBEDDING_SYNC_SUCCESS_REPORT_KIND, GEMINI_INTERACTIONS_STREAM_PLAN_KIND,
GEMINI_INTERACTIONS_STREAM_SUCCESS_REPORT_KIND, GEMINI_INTERACTIONS_SYNC_PLAN_KIND,
GEMINI_INTERACTIONS_SYNC_SUCCESS_REPORT_KIND, OPENAI_EMBEDDING_SYNC_PLAN_KIND,
OPENAI_MEMORIES_SYNC_PLAN_KIND, OPENAI_MEMORIES_SYNC_SUCCESS_REPORT_KIND,
OPENAI_REALTIME_STREAM_PLAN_KIND, OPENAI_RERANK_SYNC_PLAN_KIND, OPENAI_SEARCH_SYNC_PLAN_KIND,
OPENAI_SEARCH_SYNC_SUCCESS_REPORT_KIND,
};
@@ -29,6 +30,14 @@ pub struct LocalSameFormatProviderSpec {
pub fn resolve_sync_spec(plan_kind: &str) -> Option<LocalSameFormatProviderSpec> {
match plan_kind {
OPENAI_MEMORIES_SYNC_PLAN_KIND => Some(LocalSameFormatProviderSpec {
api_format: "openai:responses",
decision_kind: OPENAI_MEMORIES_SYNC_PLAN_KIND,
report_kind: OPENAI_MEMORIES_SYNC_SUCCESS_REPORT_KIND,
family: LocalSameFormatProviderFamily::Standard,
require_streaming: false,
operation: Some(ApiOperation::OpenAiMemoriesSummarize),
}),
CLAUDE_CHAT_SYNC_PLAN_KIND => Some(LocalSameFormatProviderSpec {
api_format: "claude:messages",
decision_kind: CLAUDE_CHAT_SYNC_PLAN_KIND,
@@ -260,4 +269,16 @@ mod tests {
assert_eq!(spec.report_kind, "openai_search_sync_success");
assert!(!spec.require_streaming);
}
#[test]
fn memories_uses_responses_permissions_and_a_native_sync_operation() {
let spec = resolve_sync_spec("openai_memories_sync").expect("memory spec");
assert_eq!(spec.api_format, "openai:responses");
assert_eq!(
spec.operation,
Some(crate::ApiOperation::OpenAiMemoriesSummarize)
);
assert_eq!(spec.report_kind, "openai_memories_sync_success");
assert!(!spec.require_streaming);
}
}
@@ -11,13 +11,13 @@ use crate::contracts::{
GEMINI_INTERACTIONS_STREAM_PLAN_KIND, GEMINI_INTERACTIONS_SYNC_PLAN_KIND,
GEMINI_VIDEO_CANCEL_SYNC_PLAN_KIND, GEMINI_VIDEO_CREATE_SYNC_PLAN_KIND,
OPENAI_CHAT_STREAM_PLAN_KIND, OPENAI_CHAT_SYNC_PLAN_KIND, OPENAI_EMBEDDING_SYNC_PLAN_KIND,
OPENAI_IMAGE_STREAM_PLAN_KIND, OPENAI_IMAGE_SYNC_PLAN_KIND, OPENAI_REALTIME_STREAM_PLAN_KIND,
OPENAI_RERANK_SYNC_PLAN_KIND, OPENAI_RESPONSES_COMPACT_STREAM_PLAN_KIND,
OPENAI_RESPONSES_COMPACT_SYNC_PLAN_KIND, OPENAI_RESPONSES_STREAM_PLAN_KIND,
OPENAI_RESPONSES_SYNC_PLAN_KIND, OPENAI_SEARCH_SYNC_PLAN_KIND,
OPENAI_VIDEO_CANCEL_SYNC_PLAN_KIND, OPENAI_VIDEO_CONTENT_PLAN_KIND,
OPENAI_VIDEO_CREATE_SYNC_PLAN_KIND, OPENAI_VIDEO_DELETE_SYNC_PLAN_KIND,
OPENAI_VIDEO_REMIX_SYNC_PLAN_KIND,
OPENAI_IMAGE_STREAM_PLAN_KIND, OPENAI_IMAGE_SYNC_PLAN_KIND, OPENAI_MEMORIES_SYNC_PLAN_KIND,
OPENAI_REALTIME_STREAM_PLAN_KIND, OPENAI_RERANK_SYNC_PLAN_KIND,
OPENAI_RESPONSES_COMPACT_STREAM_PLAN_KIND, OPENAI_RESPONSES_COMPACT_SYNC_PLAN_KIND,
OPENAI_RESPONSES_STREAM_PLAN_KIND, OPENAI_RESPONSES_SYNC_PLAN_KIND,
OPENAI_SEARCH_SYNC_PLAN_KIND, OPENAI_VIDEO_CANCEL_SYNC_PLAN_KIND,
OPENAI_VIDEO_CONTENT_PLAN_KIND, OPENAI_VIDEO_CREATE_SYNC_PLAN_KIND,
OPENAI_VIDEO_DELETE_SYNC_PLAN_KIND, OPENAI_VIDEO_REMIX_SYNC_PLAN_KIND,
};
use crate::formats::openai::image::request::is_openai_image_stream_request;
@@ -193,6 +193,14 @@ pub fn resolve_execution_runtime_sync_plan_kind_with_client_surface(
return None;
}
if route_family == Some("openai")
&& route_kind == Some("memories")
&& *method == Method::POST
&& path == "/v1/memories/trace_summarize"
{
return Some(OPENAI_MEMORIES_SYNC_PLAN_KIND);
}
if route_family == Some("openai")
&& route_kind == Some("video")
&& *method == Method::POST
@@ -580,6 +588,7 @@ pub fn supports_sync_execution_decision_kind(plan_kind: &str) -> bool {
matches!(
plan_kind,
OPENAI_CHAT_SYNC_PLAN_KIND
| OPENAI_MEMORIES_SYNC_PLAN_KIND
| OPENAI_EMBEDDING_SYNC_PLAN_KIND
| OPENAI_RERANK_SYNC_PLAN_KIND
| OPENAI_SEARCH_SYNC_PLAN_KIND
@@ -746,6 +746,9 @@ fn maybe_build_standard_same_format_sync_body(
}
let body_json = body_json?;
if report_kind == "openai_memories_sync_finalize" {
return Some(body_json.clone());
}
if is_error_like_sync_body(body_json) {
return None;
}
@@ -1949,6 +1952,7 @@ fn is_openai_responses_finalize_kind(report_kind: &str) -> bool {
fn standard_same_format_api_format(report_kind: &str) -> Option<&'static str> {
match report_kind {
"openai_memories_sync_finalize" => Some("openai:responses"),
"openai_chat_sync_finalize" => Some("openai:chat"),
"claude_chat_sync_finalize" => Some("claude:messages"),
"gemini_chat_sync_finalize" => Some("gemini:generate_content"),
@@ -4163,6 +4167,24 @@ mod tests {
use base64::Engine as _;
use serde_json::json;
#[test]
fn native_memories_response_keeps_output_array_and_future_fields() {
let body = json!({"output":[{"trace_summary":"synthetic", "memory_summary":"memory"}], "future_response_field":{"enabled":true}});
let context = json!({"provider_api_format":"openai:responses","client_api_format":"openai:responses","needs_conversion":false,"requested_model":"gpt-6.1-sol-max"});
assert_eq!(
maybe_build_standard_same_format_sync_body_from_normalized_payload(
"openai_memories_sync_finalize",
200,
Some(&context),
Some(&body),
None
)
.expect("native response")
.expect("body"),
body
);
}
#[test]
fn converts_openai_images_sync_body_to_gemini_image_body() {
let provider_body_json = json!({
+11 -5
View File
@@ -1860,14 +1860,20 @@ mod tests {
assert_eq!(
model_ids,
vec![
"gpt-6-astra",
"gpt-6.1-sol",
"gpt-6-sol",
"gpt-6-luna",
"gpt-5.6-sol",
"gpt-5.6-terra",
"gpt-5.6-luna",
"gpt-daybreak-blue-latest",
"gpt-daybreak-red-latest",
"gpt-5.5",
"codex-auto-review",
"gpt-5.4",
"gpt-5.4-mini",
"gpt-5.2",
"codex-auto-review",
]
);
let sol = models
@@ -1886,7 +1892,7 @@ mod tests {
);
assert_eq!(sol["multi_agent_version"], "v2");
assert_eq!(sol["supports_image_detail_original"], true);
assert_eq!(sol["context_window"], 372_000);
assert_eq!(sol["context_window"], 272_000);
for model_id in ["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"] {
let model = models
@@ -1894,11 +1900,11 @@ mod tests {
.find(|model| model["id"] == model_id)
.expect("GPT-5.6 Codex preset");
assert_eq!(model["shell_type"], "shell_command");
assert_eq!(model["comp_hash"], "3000");
assert!(model.get("comp_hash").is_none());
assert_eq!(model["experimental_supported_tools"], json!([]));
assert_eq!(model["tool_mode"], "code_mode_only");
assert_eq!(model["prefer_websockets"], true);
assert_eq!(model["reasoning_summary_format"], "experimental");
assert!(model.get("reasoning_summary_format").is_none());
assert_eq!(model["truncation_policy"]["limit"], 10_000);
assert_eq!(model["minimal_client_version"], "0.144.0");
assert!(model.get("effective_context_window_percent").is_none());
@@ -1924,7 +1930,7 @@ mod tests {
assert_eq!(auto_review["supported_in_api"], true);
assert_eq!(auto_review["default_reasoning_level"], "medium");
assert_eq!(auto_review["default_reasoning_summary"], "none");
assert_eq!(auto_review["use_responses_lite"], false);
assert_eq!(auto_review["use_responses_lite"], true);
}
#[test]
+15 -5
View File
@@ -129,6 +129,12 @@ pub async fn build_standard_models_fetch_execution_plan_for_client_version(
provider_type == "codex" && api_format.starts_with("openai:");
let is_deepseek_anthropic_models_fetch = api_format.starts_with("claude:")
&& deepseek_anthropic_models_fetch_uses_openai_auth(&transport.endpoint.base_url);
if is_codex_openai_models_fetch {
if let Some(version) = codex_client_version {
aether_ai_formats::CodexClientProfile::cli(version)
.map_err(|error| error.to_string())?;
}
}
let mut headers =
standard_models_fetch_headers(&api_format, &provider_type, codex_client_version);
if is_codex_openai_models_fetch {
@@ -636,10 +642,9 @@ fn standard_models_fetch_headers(
return BTreeMap::from([
(
"user-agent".to_string(),
format!(
"{}/{client_version}",
aether_ai_formats::codex_client_originator()
),
aether_ai_formats::CodexClientProfile::cli(client_version)
.expect("validated Codex catalog client version")
.user_agent,
),
(
"originator".to_string(),
@@ -1059,7 +1064,12 @@ mod tests {
);
assert_eq!(
plan.headers.get("user-agent").map(String::as_str),
Some("codex_cli_rs/0.145.2")
Some(
aether_ai_formats::CodexClientProfile::cli("0.145.2")
.unwrap()
.user_agent
.as_str()
)
);
assert_eq!(
plan.headers.get("originator").map(String::as_str),
@@ -3,6 +3,16 @@ use super::generic::{
};
use crate::provider::ProviderOAuthAdapter;
/// Codex CLI 的公开 OAuth 权限范围,不包含账户专属值。
pub const CODEX_OAUTH_SCOPES: &[&str] = &[
"openid",
"profile",
"email",
"offline_access",
"api.connectors.read",
"api.connectors.invoke",
];
#[derive(Debug, Clone)]
pub struct CodexProviderOAuthAdapter {
inner: GenericProviderOAuthAdapter,
@@ -45,6 +55,7 @@ impl ProviderOAuthAdapter for CodexProviderOAuthAdapter {
query.append_pair("prompt", "login");
query.append_pair("id_token_add_organizations", "true");
query.append_pair("codex_cli_simplified_flow", "true");
query.append_pair("originator", "codex_cli_rs");
}
response.authorize_url = url.to_string();
Ok(response)
@@ -156,6 +167,26 @@ mod tests {
assert!(response
.authorize_url
.contains("codex_cli_simplified_flow=true"));
let url = url::Url::parse(&response.authorize_url).unwrap();
let query = url.query_pairs().collect::<BTreeMap<_, _>>();
assert_eq!(
query.get("scope").map(|value| value.as_ref()),
Some("openid profile email offline_access api.connectors.read api.connectors.invoke")
);
assert_eq!(
query.get("originator").map(|value| value.as_ref()),
Some("codex_cli_rs")
);
assert_eq!(
query
.get("code_challenge_method")
.map(|value| value.as_ref()),
Some("S256")
);
assert_eq!(
query.get("state").map(|value| value.as_ref()),
Some("state-1")
);
}
#[tokio::test]
@@ -92,7 +92,7 @@ pub const GENERIC_PROVIDER_OAUTH_TEMPLATES: &[GenericProviderOAuthTemplate] = &[
client_id: "app_EMoamEEZ73f0CkXaXp7hrann",
client_id_env: None,
client_secret_env: None,
scopes: &["openid", "email", "profile", "offline_access"],
scopes: super::codex::CODEX_OAUTH_SCOPES,
redirect_uri: "http://localhost:1455/auth/callback",
use_pkce: true,
uses_json_payload: false,
@@ -12,7 +12,7 @@ pub use claude_code::{
CLAUDE_CODE_COOKIE_SCOPE, CLAUDE_CODE_OAUTH_SCOPES, CLAUDE_CODE_PROVIDER_TYPE,
CLAUDE_CODE_REDIRECT_URI, CLAUDE_CODE_TOKEN_URL, CLAUDE_CODE_WEB_BASE_URL,
};
pub use codex::CodexProviderOAuthAdapter;
pub use codex::{CodexProviderOAuthAdapter, CODEX_OAUTH_SCOPES};
pub use generic::{
derive_codex_identity_fingerprint, GenericProviderOAuthAdapter, GenericProviderOAuthTemplate,
ANTIGRAVITY_OAUTH_CLIENT_ID_ENV, ANTIGRAVITY_OAUTH_CLIENT_SECRET_ENV,
@@ -107,6 +107,12 @@ pub(crate) fn apply_codex_fingerprint_convergence_policy(
}
let is_responses = aether_ai_formats::is_openai_responses_format(provider_api_format);
if context.api_operation() == Some(aether_ai_formats::ApiOperation::OpenAiMemoriesSummarize) {
return ProviderOutboundRequestPolicyResult::skipped(
policy,
ProviderOutboundRequestPolicyReason::NativeOperationExcluded,
);
}
let is_live = aether_ai_formats::api_format_alias_matches(provider_api_format, "codex:live");
if !is_responses && !is_live {
return ProviderOutboundRequestPolicyResult::skipped(
@@ -567,6 +573,30 @@ mod tests {
}
}
#[test]
fn native_memories_excludes_responses_fingerprint_body_mutations() {
let transport = sample_transport();
let context =
ProviderOutboundRequestContext::new("synthetic-memory-turn", 1_700_000_000_123)
.with_api_operation(aether_ai_formats::ApiOperation::OpenAiMemoriesSummarize);
let mut headers = BTreeMap::new();
let mut body = json!({"model":"gpt-6.1-sol","traces":[],"future":42});
let original = body.clone();
let result = apply_codex_fingerprint_convergence_policy(
&transport,
"openai:responses",
&context,
&mut headers,
&mut body,
);
assert_eq!(
result.reason,
ProviderOutboundRequestPolicyReason::NativeOperationExcluded
);
assert_eq!(body, original);
assert!(headers.is_empty());
}
#[test]
fn provider_config_switch_is_opt_in_and_codex_only() {
assert!(!codex_fingerprint_convergence_enabled("codex", None));
@@ -154,6 +154,7 @@ pub use rules::{
};
pub use same_format_provider::{
build_same_format_provider_headers, build_same_format_provider_request_body,
build_same_format_provider_request_body_for_operation,
build_same_format_provider_request_body_with_compatibility_report,
build_same_format_provider_request_body_with_compatibility_report_and_reasoning_replay_policy,
build_same_format_provider_upstream_url, classify_same_format_provider_request_behavior,
@@ -23,6 +23,7 @@ pub const PROVIDER_OUTBOUND_CONTEXT_MAX_VALUE_BYTES: usize = 256;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ProviderOutboundRequestContext {
api_operation: Option<aether_ai_formats::ApiOperation>,
logical_turn_id: String,
original_turn_id: Option<String>,
original_client_session_id: Option<String>,
@@ -33,6 +34,7 @@ pub struct ProviderOutboundRequestContext {
impl ProviderOutboundRequestContext {
pub fn new(logical_turn_id: impl Into<String>, turn_started_at_unix_ms: u64) -> Self {
Self {
api_operation: None,
logical_turn_id: canonical_required_value(logical_turn_id.into(), "logical_turn_id"),
original_turn_id: None,
original_client_session_id: None,
@@ -68,6 +70,15 @@ impl ProviderOutboundRequestContext {
self.logical_turn_id.as_str()
}
pub fn with_api_operation(mut self, operation: aether_ai_formats::ApiOperation) -> Self {
self.api_operation = Some(operation);
self
}
pub fn api_operation(&self) -> Option<aether_ai_formats::ApiOperation> {
self.api_operation
}
pub fn original_turn_id(&self) -> Option<&str> {
self.original_turn_id.as_deref()
}
@@ -106,6 +117,7 @@ pub enum ProviderOutboundRequestPolicyReason {
AgentIdentityExcluded,
UnsupportedApiFormat,
CompactOperationExcluded,
NativeOperationExcluded,
Disabled,
RequestBodyNotObject,
}
@@ -604,7 +604,7 @@ pub fn provider_type_admin_oauth_template(provider_type: &str) -> Option<Provide
authorize_url: "https://auth.openai.com/oauth/authorize",
token_url: "https://auth.openai.com/oauth/token",
client_id: "app_EMoamEEZ73f0CkXaXp7hrann",
scopes: &["openid", "email", "profile", "offline_access"],
scopes: aether_oauth::provider::providers::CODEX_OAUTH_SCOPES,
redirect_uri: "http://localhost:1455/auth/callback",
use_pkce: true,
}),
@@ -120,6 +120,43 @@ fn build_transport_request_url_inner(
return Some(url);
}
if params.api_operation == Some(ApiOperation::OpenAiMemoriesSummarize) {
if normalized_provider_api_format != "openai:responses" {
return None;
}
if let Some(path) = transport
.endpoint
.custom_path
.as_deref()
.map(str::trim)
.filter(|path| !path.is_empty())
{
// 显式操作模板也适用于原生同步操作。
if path.contains("{operation}") {
let path = expand_custom_path_template(path, build_path_params(params, false))?;
return build_passthrough_path_url(
&transport.endpoint.base_url,
&path,
params.request_query,
GATEWAY_CREDENTIAL_QUERY_KEYS,
);
}
// 仅描述 Responses 的自定义路径无法承接该操作。
return None;
}
// 以 Responses 的同一提供商根路径派生原生端点。
let mut url = Url::parse(&build_openai_responses_url(
&transport.endpoint.base_url,
strip_gateway_credential_query_parameters(params.request_query).as_deref(),
false,
))
.ok()?;
let root = url.path().strip_suffix("/responses")?;
let path = format!("{root}/memories/trace_summarize");
url.set_path(&path);
return Some(url.to_string());
}
let xai_base =
crate::xai::resolved_xai_upstream_base_url(transport, &normalized_provider_api_format);
let request_base_url = xai_base
@@ -558,6 +595,14 @@ pub fn transport_supports_api_operation(
provider_api_format: &str,
operation: Option<ApiOperation>,
) -> bool {
if operation == Some(ApiOperation::OpenAiMemoriesSummarize) {
return aether_ai_formats::normalize_api_format_alias(provider_api_format)
== "openai:responses"
&& !crate::kiro::is_kiro_provider_transport(transport)
&& !crate::grok::is_grok_provider_transport(transport)
&& !is_antigravity_provider_transport(transport)
&& !is_gemini_cli_provider_transport(transport);
}
if operation != Some(ApiOperation::ClaudeCountTokens) {
return true;
}
@@ -1221,6 +1266,73 @@ mod tests {
assert_eq!(url, "https://api.openai.example/v1/responses?tenant=demo");
}
#[test]
fn memories_url_respects_operation_templates_and_rejects_incompatible_paths() {
let params = TransportRequestUrlParams {
provider_api_format: "openai:responses",
mapped_model: Some("gpt-6.1-sol"),
upstream_is_stream: false,
request_query: Some("key=synthetic&tenant=demo"),
kiro_api_region: None,
api_operation: Some(ApiOperation::OpenAiMemoriesSummarize),
};
let transport = sample_transport(
"codex",
"openai:responses",
"https://example.com",
Some("/native/{operation}"),
);
assert_eq!(
build_transport_request_url(&transport, params).as_deref(),
Some("https://example.com/native/trace_summarize?tenant=demo")
);
let incompatible = sample_transport(
"codex",
"openai:responses",
"https://example.com",
Some("/native/responses"),
);
assert!(build_transport_request_url(&incompatible, params).is_none());
for provider in ["kiro", "grok", "antigravity", "gemini_cli"] {
let private =
sample_transport(provider, "openai:responses", "https://example.com", None);
assert!(
build_transport_request_url(&private, params).is_none(),
"{provider}"
);
}
}
#[test]
fn memories_url_uses_the_configured_provider_root_and_removes_gateway_auth() {
for (provider, base, expected) in [
(
"codex",
"https://chatgpt.com/backend-api/codex",
"https://chatgpt.com/backend-api/codex/memories/trace_summarize?tenant=demo",
),
(
"custom",
"https://example.com/v1/responses",
"https://example.com/v1/memories/trace_summarize?tenant=demo",
),
] {
let transport = sample_transport(provider, "openai:responses", base, None);
let result = build_transport_request_url(
&transport,
TransportRequestUrlParams {
provider_api_format: "openai:responses",
mapped_model: Some("gpt-6.1-sol"),
upstream_is_stream: false,
request_query: Some("key=synthetic-secret&tenant=demo"),
kiro_api_region: None,
api_operation: Some(ApiOperation::OpenAiMemoriesSummarize),
},
);
assert_eq!(result.as_deref(), Some(expected));
}
}
#[test]
fn builds_openai_search_url_for_codex_provider_root() {
let transport = sample_transport(
@@ -273,7 +273,10 @@ pub fn classify_same_format_provider_request_behavior_for_operation(
);
let operation_requires_sync = matches!(
api_operation,
Some(aether_ai_formats::ApiOperation::ClaudeCountTokens)
Some(
aether_ai_formats::ApiOperation::ClaudeCountTokens
| aether_ai_formats::ApiOperation::OpenAiMemoriesSummarize
)
);
let upstream_is_stream = !operation_requires_sync
&& aether_ai_formats::resolve_upstream_is_stream_for_provider(
@@ -321,6 +324,7 @@ pub fn build_same_format_provider_request_body(
input,
None,
aether_ai_formats::OpenAiResponsesReasoningReplayPolicy::OpenAiItemIds,
false,
)
}
@@ -337,11 +341,34 @@ pub fn build_same_format_provider_request_body_with_compatibility_report_and_rea
input: SameFormatProviderRequestBodyInput<'_>,
reasoning_replay_policy: aether_ai_formats::OpenAiResponsesReasoningReplayPolicy,
) -> Option<SameFormatProviderRequestBodyOutput> {
build_same_format_provider_request_body_for_operation(input, reasoning_replay_policy, None)
}
pub fn build_same_format_provider_request_body_for_operation(
input: SameFormatProviderRequestBodyInput<'_>,
reasoning_replay_policy: aether_ai_formats::OpenAiResponsesReasoningReplayPolicy,
api_operation: Option<aether_ai_formats::ApiOperation>,
) -> Option<SameFormatProviderRequestBodyOutput> {
let native_memories =
api_operation == Some(aether_ai_formats::ApiOperation::OpenAiMemoriesSummarize);
if native_memories
&& (!aether_ai_formats::api_format_alias_matches(
input.provider_api_format,
"openai:responses",
) || !aether_ai_formats::api_format_alias_matches(
input.client_api_format,
"openai:responses",
) || input.kiro_auth_config.is_some()
|| input.is_claude_code)
{
return None;
}
let mut compatibility_edits = Vec::new();
let body = build_same_format_provider_request_body_inner(
input,
Some(&mut compatibility_edits),
reasoning_replay_policy,
native_memories,
)?;
Some(SameFormatProviderRequestBodyOutput {
body,
@@ -355,7 +382,10 @@ pub fn enforce_same_format_provider_api_operation_body_policy(
) -> bool {
if !matches!(
api_operation,
Some(aether_ai_formats::ApiOperation::ClaudeCountTokens)
Some(
aether_ai_formats::ApiOperation::ClaudeCountTokens
| aether_ai_formats::ApiOperation::OpenAiMemoriesSummarize
)
) {
return false;
}
@@ -367,6 +397,7 @@ fn build_same_format_provider_request_body_inner(
input: SameFormatProviderRequestBodyInput<'_>,
mut compatibility_edits: Option<&mut Vec<SameFormatProviderCompatibilityEdit>>,
reasoning_replay_policy: aether_ai_formats::OpenAiResponsesReasoningReplayPolicy,
native_memories: bool,
) -> Option<Value> {
if let Some(kiro_auth_config) = input.kiro_auth_config {
let body = build_kiro_provider_request_body(
@@ -523,6 +554,11 @@ fn build_same_format_provider_request_body_inner(
"applied configured provider body rules",
);
}
if native_memories {
// 记忆端点使用原生 JSON,不注入 Responses 的 input/store/include/stream,
// 也不通过 Responses 规则归一化 traces。
return Some(provider_request_body);
}
if matches!(input.family, SameFormatProviderFamily::Gemini)
&& aether_ai_formats::api_format_alias_matches(
input.provider_api_format,
@@ -992,6 +1028,39 @@ mod tests {
};
use serde_json::json;
#[test]
fn memories_preserves_native_traces_and_does_not_apply_responses_stream_policy() {
let body = json!({"model": "memory-global", "reasoning": {"effort": "high"},
"traces": [{"id": "synthetic-trace", "items": [{"future": 7}]}],
"future_request": {"opaque": [1, 2, 3]}});
let input = SameFormatProviderRequestBodyInput {
body_json: &body,
mapped_model: "memory-upstream",
client_api_format: "openai:responses",
provider_api_format: "openai:responses",
source_model: Some("memory-global"),
family: SameFormatProviderFamily::Standard,
body_rules: None,
request_headers: None,
upstream_is_stream: false,
force_body_stream_field: true,
kiro_auth_config: None,
is_claude_code: false,
enable_model_directives: false,
};
let result = build_same_format_provider_request_body_for_operation(
input,
aether_ai_formats::OpenAiResponsesReasoningReplayPolicy::OpenAiItemIds,
Some(aether_ai_formats::ApiOperation::OpenAiMemoriesSummarize),
)
.unwrap();
let mut expected = body.clone();
expected["model"] = json!("memory-upstream");
assert_eq!(result.body, expected);
assert!(result.body.get("input").is_none());
assert!(result.body.get("stream").is_none());
}
fn sample_transport(provider_type: &str) -> GatewayProviderTransportSnapshot {
GatewayProviderTransportSnapshot {
provider: GatewayProviderTransportProvider {
@@ -299,6 +299,7 @@ pub fn is_local_ai_sync_report_kind(report_kind: &str) -> bool {
| "claude_chat_sync_error"
| "gemini_chat_sync_error"
| "openai_responses_sync_success"
| "openai_memories_sync_success"
| "openai_responses_compact_sync_success"
| "openai_responses_sync_error"
| "openai_responses_compact_sync_error"
@@ -946,6 +947,7 @@ mod tests {
));
assert!(is_local_ai_sync_report_kind("openai_image_sync_success"));
assert!(is_local_ai_sync_report_kind("openai_search_sync_success"));
assert!(is_local_ai_sync_report_kind("openai_memories_sync_success"));
assert!(is_local_ai_sync_report_kind("openai_image_sync_error"));
assert!(is_local_ai_sync_report_kind(
"openai_embedding_sync_success"