mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-07 18:07:47 +08:00
对齐 Codex CLI 0.159.3 的通用画像、模型能力与原生协议
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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!({
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user