mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-24 12:44:18 +08:00
feat(codex): declare gpt-5.6 context window for Claude Code takeover
- Update Codex OAuth presets to the gpt-5.6 family (haiku -> gpt-5.6-luna) and bump the custom Codex template default model - Inject CLAUDE_CODE_MAX_CONTEXT_TOKENS / CLAUDE_CODE_AUTO_COMPACT_WINDOW (372000, the ChatGPT Codex catalog window with a ~353K effective budget, openai/codex#31860) into effective live settings so Claude Code stops assuming a 200K window and compacts before the upstream rejects the prompt - Gate the injected defaults on every configured model being gpt-5.6*: gpt-5.5's upstream catalog oscillates between 272K and 372K and must not inherit them; explicit user values always win - Strip the injected values on switch-away backfill (mirror-inverse of the injection conditions) so program defaults never harden into per-provider explicit values, and keep both keys out of the shared common-config snippet
This commit is contained in:
@@ -21,6 +21,107 @@ use super::gemini_auth::{
|
|||||||
};
|
};
|
||||||
use super::normalize_claude_models_in_value;
|
use super::normalize_claude_models_in_value;
|
||||||
|
|
||||||
|
/// ChatGPT Codex catalogs gpt-5.6 at a 372K context window with a ~353K
|
||||||
|
/// effective budget (openai/codex#31860), far below the 1.05M API spec.
|
||||||
|
/// Declare the catalog window for both knobs: Claude Code's built-in output
|
||||||
|
/// reserve and compact buffer already keep the actual compact trigger
|
||||||
|
/// (~278K-339K) below the effective budget, so anything lower only wastes
|
||||||
|
/// usable context.
|
||||||
|
const CODEX_OAUTH_CLAUDE_MAX_CONTEXT_TOKENS: &str = "372000";
|
||||||
|
const CODEX_OAUTH_CLAUDE_AUTO_COMPACT_WINDOW: &str = "372000";
|
||||||
|
|
||||||
|
/// Model env keys Claude Code may route requests through. The defaults above
|
||||||
|
/// are calibrated against gpt-5.6's Codex catalog, so every configured model
|
||||||
|
/// must belong to that family before they are injected — gpt-5.5's upstream
|
||||||
|
/// catalog oscillates between 272K and 372K and must not inherit them.
|
||||||
|
const CODEX_OAUTH_MODEL_ENV_KEYS: [&str; 6] = [
|
||||||
|
"ANTHROPIC_MODEL",
|
||||||
|
"ANTHROPIC_DEFAULT_HAIKU_MODEL",
|
||||||
|
"ANTHROPIC_DEFAULT_SONNET_MODEL",
|
||||||
|
"ANTHROPIC_DEFAULT_OPUS_MODEL",
|
||||||
|
"ANTHROPIC_DEFAULT_FABLE_MODEL",
|
||||||
|
"CLAUDE_CODE_SUBAGENT_MODEL",
|
||||||
|
];
|
||||||
|
|
||||||
|
fn provider_env_targets_gpt56(provider_env: Option<&serde_json::Map<String, Value>>) -> bool {
|
||||||
|
let Some(env) = provider_env else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let mut saw_model = false;
|
||||||
|
for key in CODEX_OAUTH_MODEL_ENV_KEYS {
|
||||||
|
let Some(value) = env.get(key) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let Some(model) = value.as_str() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let model = model.trim();
|
||||||
|
if model.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
saw_model = true;
|
||||||
|
if !model.to_ascii_lowercase().starts_with("gpt-5.6") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
saw_model
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Claude Code assigns unknown non-Claude model ids a 200K context window.
|
||||||
|
/// Codex OAuth deliberately exposes GPT ids through Claude Code, so enrich the
|
||||||
|
/// effective live settings for both newly-created and already-saved providers.
|
||||||
|
/// Explicit user values always win; the defaults are only injected when every
|
||||||
|
/// configured model targets gpt-5.6.
|
||||||
|
fn apply_codex_oauth_claude_context_defaults(settings: &mut Value, provider: &Provider) {
|
||||||
|
if !provider.is_codex_oauth() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read provider-owned values before mutably borrowing the effective
|
||||||
|
// settings. This also deliberately prevents a legacy common-config
|
||||||
|
// snippet from overriding model-specific context limits.
|
||||||
|
let provider_env = provider
|
||||||
|
.settings_config
|
||||||
|
.get("env")
|
||||||
|
.and_then(Value::as_object);
|
||||||
|
let Some(root) = settings.as_object_mut() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let env = root.entry("env".to_string()).or_insert_with(|| json!({}));
|
||||||
|
let Some(env) = env.as_object_mut() else {
|
||||||
|
log::warn!(
|
||||||
|
"Cannot apply Codex OAuth Claude context defaults for '{}': env is not an object",
|
||||||
|
provider.id
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let inject_defaults = provider_env_targets_gpt56(provider_env);
|
||||||
|
for (key, default_value) in [
|
||||||
|
(
|
||||||
|
"CLAUDE_CODE_MAX_CONTEXT_TOKENS",
|
||||||
|
CODEX_OAUTH_CLAUDE_MAX_CONTEXT_TOKENS,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"CLAUDE_CODE_AUTO_COMPACT_WINDOW",
|
||||||
|
CODEX_OAUTH_CLAUDE_AUTO_COMPACT_WINDOW,
|
||||||
|
),
|
||||||
|
] {
|
||||||
|
match provider_env.and_then(|provider_env| provider_env.get(key)) {
|
||||||
|
Some(value) => {
|
||||||
|
env.insert(key.to_string(), value.clone());
|
||||||
|
}
|
||||||
|
None if inject_defaults => {
|
||||||
|
env.insert(key.to_string(), Value::String(default_value.to_string()));
|
||||||
|
}
|
||||||
|
// 老模型不注入默认值,同时剥掉遗留共享片段可能带进来的值
|
||||||
|
None => {
|
||||||
|
env.remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn sanitize_claude_settings_for_live(settings: &Value) -> Value {
|
pub(crate) fn sanitize_claude_settings_for_live(settings: &Value) -> Value {
|
||||||
let mut v = settings.clone();
|
let mut v = settings.clone();
|
||||||
if let Some(obj) = v.as_object_mut() {
|
if let Some(obj) = v.as_object_mut() {
|
||||||
@@ -537,6 +638,10 @@ pub(crate) fn build_effective_settings_with_common_config(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if matches!(app_type, AppType::Claude) {
|
||||||
|
apply_codex_oauth_claude_context_defaults(&mut effective_settings, provider);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(effective_settings)
|
Ok(effective_settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -604,11 +709,55 @@ pub(crate) fn strip_common_config_from_live_settings(
|
|||||||
restore_live_settings_for_provider_backfill(app_type, provider, backfill_settings)
|
restore_live_settings_for_provider_backfill(app_type, provider, backfill_settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 与 `apply_codex_oauth_claude_context_defaults` 严格对称:注入产物只活在
|
||||||
|
/// live,切走回填时必须剥掉,否则程序默认值会固化成供应商的"用户显式值",
|
||||||
|
/// 之后调整默认值或更换模型时旧值永远压住新默认。仅当"注入会发生且注入的
|
||||||
|
/// 就是这个值、且存储配置本来没有显式值"时才剥;用户显式存储的值和手改
|
||||||
|
/// live 成其他数字的值都保留。
|
||||||
|
fn strip_injected_codex_oauth_context_defaults(settings: &mut Value, provider: &Provider) {
|
||||||
|
if !provider.is_codex_oauth() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let provider_env = provider
|
||||||
|
.settings_config
|
||||||
|
.get("env")
|
||||||
|
.and_then(Value::as_object);
|
||||||
|
if !provider_env_targets_gpt56(provider_env) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let Some(env) = settings.get_mut("env").and_then(Value::as_object_mut) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
for (key, default_value) in [
|
||||||
|
(
|
||||||
|
"CLAUDE_CODE_MAX_CONTEXT_TOKENS",
|
||||||
|
CODEX_OAUTH_CLAUDE_MAX_CONTEXT_TOKENS,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"CLAUDE_CODE_AUTO_COMPACT_WINDOW",
|
||||||
|
CODEX_OAUTH_CLAUDE_AUTO_COMPACT_WINDOW,
|
||||||
|
),
|
||||||
|
] {
|
||||||
|
let stored_explicit = provider_env.is_some_and(|e| e.contains_key(key));
|
||||||
|
if stored_explicit {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if env.get(key).and_then(Value::as_str) == Some(default_value) {
|
||||||
|
env.remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn restore_live_settings_for_provider_backfill(
|
fn restore_live_settings_for_provider_backfill(
|
||||||
app_type: &AppType,
|
app_type: &AppType,
|
||||||
provider: &Provider,
|
provider: &Provider,
|
||||||
live_settings: Value,
|
live_settings: Value,
|
||||||
) -> Value {
|
) -> Value {
|
||||||
|
if matches!(app_type, AppType::Claude) {
|
||||||
|
let mut settings = live_settings;
|
||||||
|
strip_injected_codex_oauth_context_defaults(&mut settings, provider);
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
if !matches!(app_type, AppType::Codex) {
|
if !matches!(app_type, AppType::Codex) {
|
||||||
return live_settings;
|
return live_settings;
|
||||||
}
|
}
|
||||||
@@ -1711,6 +1860,232 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn codex_oauth_effective_settings_backfill_gpt_context_defaults() {
|
||||||
|
let db = Database::memory().expect("create memory db");
|
||||||
|
let mut provider = Provider::with_id(
|
||||||
|
"codex-oauth".to_string(),
|
||||||
|
"Codex".to_string(),
|
||||||
|
json!({
|
||||||
|
"env": {
|
||||||
|
"ANTHROPIC_MODEL": "gpt-5.6"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
provider.meta = Some(crate::provider::ProviderMeta {
|
||||||
|
provider_type: Some("codex_oauth".to_string()),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
let effective =
|
||||||
|
build_effective_settings_with_common_config(&db, &AppType::Claude, &provider)
|
||||||
|
.expect("build effective settings");
|
||||||
|
assert_eq!(
|
||||||
|
effective["env"]["CLAUDE_CODE_MAX_CONTEXT_TOKENS"],
|
||||||
|
json!("372000")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
effective["env"]["CLAUDE_CODE_AUTO_COMPACT_WINDOW"],
|
||||||
|
json!("372000")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn codex_oauth_context_defaults_preserve_user_overrides() {
|
||||||
|
let db = Database::memory().expect("create memory db");
|
||||||
|
let mut provider = Provider::with_id(
|
||||||
|
"codex-oauth".to_string(),
|
||||||
|
"Codex".to_string(),
|
||||||
|
json!({
|
||||||
|
"env": {
|
||||||
|
"CLAUDE_CODE_MAX_CONTEXT_TOKENS": "500000",
|
||||||
|
"CLAUDE_CODE_AUTO_COMPACT_WINDOW": "350000"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
provider.meta = Some(crate::provider::ProviderMeta {
|
||||||
|
provider_type: Some("codex_oauth".to_string()),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
let effective =
|
||||||
|
build_effective_settings_with_common_config(&db, &AppType::Claude, &provider)
|
||||||
|
.expect("build effective settings");
|
||||||
|
assert_eq!(
|
||||||
|
effective["env"]["CLAUDE_CODE_MAX_CONTEXT_TOKENS"],
|
||||||
|
json!("500000")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
effective["env"]["CLAUDE_CODE_AUTO_COMPACT_WINDOW"],
|
||||||
|
json!("350000")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn codex_oauth_context_defaults_ignore_legacy_common_config_values() {
|
||||||
|
let db = Database::memory().expect("create memory db");
|
||||||
|
db.set_config_snippet(
|
||||||
|
AppType::Claude.as_str(),
|
||||||
|
Some(
|
||||||
|
json!({
|
||||||
|
"env": {
|
||||||
|
"CLAUDE_CODE_MAX_CONTEXT_TOKENS": "262144",
|
||||||
|
"CLAUDE_CODE_AUTO_COMPACT_WINDOW": "262144"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.to_string(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.expect("save legacy common config");
|
||||||
|
let mut provider = Provider::with_id(
|
||||||
|
"codex-oauth".to_string(),
|
||||||
|
"Codex".to_string(),
|
||||||
|
json!({ "env": { "ANTHROPIC_MODEL": "gpt-5.6" } }),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
provider.meta = Some(crate::provider::ProviderMeta {
|
||||||
|
provider_type: Some("codex_oauth".to_string()),
|
||||||
|
common_config_enabled: Some(true),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
let effective =
|
||||||
|
build_effective_settings_with_common_config(&db, &AppType::Claude, &provider)
|
||||||
|
.expect("build effective settings");
|
||||||
|
assert_eq!(
|
||||||
|
effective["env"]["CLAUDE_CODE_MAX_CONTEXT_TOKENS"],
|
||||||
|
json!("372000")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
effective["env"]["CLAUDE_CODE_AUTO_COMPACT_WINDOW"],
|
||||||
|
json!("372000")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn codex_oauth_context_defaults_skip_non_gpt56_models() {
|
||||||
|
let db = Database::memory().expect("create memory db");
|
||||||
|
db.set_config_snippet(
|
||||||
|
AppType::Claude.as_str(),
|
||||||
|
Some(
|
||||||
|
json!({
|
||||||
|
"env": {
|
||||||
|
"CLAUDE_CODE_MAX_CONTEXT_TOKENS": "262144",
|
||||||
|
"CLAUDE_CODE_AUTO_COMPACT_WINDOW": "262144"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.to_string(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.expect("save legacy common config");
|
||||||
|
let mut provider = Provider::with_id(
|
||||||
|
"codex-oauth".to_string(),
|
||||||
|
"Codex".to_string(),
|
||||||
|
json!({
|
||||||
|
"env": {
|
||||||
|
"ANTHROPIC_MODEL": "gpt-5.5",
|
||||||
|
"CLAUDE_CODE_AUTO_COMPACT_WINDOW": "300000"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
provider.meta = Some(crate::provider::ProviderMeta {
|
||||||
|
provider_type: Some("codex_oauth".to_string()),
|
||||||
|
common_config_enabled: Some(true),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
let effective =
|
||||||
|
build_effective_settings_with_common_config(&db, &AppType::Claude, &provider)
|
||||||
|
.expect("build effective settings");
|
||||||
|
// 旧模型不注入 372K 默认值,遗留共享片段带进来的值也要剥掉
|
||||||
|
assert!(effective["env"]
|
||||||
|
.get("CLAUDE_CODE_MAX_CONTEXT_TOKENS")
|
||||||
|
.is_none());
|
||||||
|
// 用户显式写在供应商配置里的值仍然生效
|
||||||
|
assert_eq!(
|
||||||
|
effective["env"]["CLAUDE_CODE_AUTO_COMPACT_WINDOW"],
|
||||||
|
json!("300000")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 往返不动点:注入产物只活在 live,切走回灌后存储配置必须与注入前一致,
|
||||||
|
/// 否则程序默认值固化成"用户显式值",之后调默认值永远压不动。
|
||||||
|
#[test]
|
||||||
|
fn codex_oauth_backfill_strips_injected_context_defaults() {
|
||||||
|
let db = Database::memory().expect("create memory db");
|
||||||
|
let mut provider = Provider::with_id(
|
||||||
|
"codex-oauth".to_string(),
|
||||||
|
"Codex".to_string(),
|
||||||
|
json!({ "env": { "ANTHROPIC_MODEL": "gpt-5.6" } }),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
provider.meta = Some(crate::provider::ProviderMeta {
|
||||||
|
provider_type: Some("codex_oauth".to_string()),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
// 模拟写 live:注入了两个上下文默认值
|
||||||
|
let live = build_effective_settings_with_common_config(&db, &AppType::Claude, &provider)
|
||||||
|
.expect("build effective settings");
|
||||||
|
assert_eq!(
|
||||||
|
live["env"]["CLAUDE_CODE_MAX_CONTEXT_TOKENS"],
|
||||||
|
json!("372000")
|
||||||
|
);
|
||||||
|
|
||||||
|
// 模拟切走回灌:注入产物被剥掉,其余字段原样保留
|
||||||
|
let backfilled =
|
||||||
|
strip_common_config_from_live_settings(&db, &AppType::Claude, &provider, live);
|
||||||
|
assert!(backfilled["env"]
|
||||||
|
.get("CLAUDE_CODE_MAX_CONTEXT_TOKENS")
|
||||||
|
.is_none());
|
||||||
|
assert!(backfilled["env"]
|
||||||
|
.get("CLAUDE_CODE_AUTO_COMPACT_WINDOW")
|
||||||
|
.is_none());
|
||||||
|
assert_eq!(backfilled["env"]["ANTHROPIC_MODEL"], json!("gpt-5.6"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn codex_oauth_backfill_keeps_user_context_values() {
|
||||||
|
let db = Database::memory().expect("create memory db");
|
||||||
|
let mut provider = Provider::with_id(
|
||||||
|
"codex-oauth".to_string(),
|
||||||
|
"Codex".to_string(),
|
||||||
|
json!({
|
||||||
|
"env": {
|
||||||
|
"ANTHROPIC_MODEL": "gpt-5.6",
|
||||||
|
"CLAUDE_CODE_MAX_CONTEXT_TOKENS": "500000"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
provider.meta = Some(crate::provider::ProviderMeta {
|
||||||
|
provider_type: Some("codex_oauth".to_string()),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
// live 里:MAX 是用户显式值;ACW 被用户手改成了非默认数字
|
||||||
|
let live = json!({
|
||||||
|
"env": {
|
||||||
|
"ANTHROPIC_MODEL": "gpt-5.6",
|
||||||
|
"CLAUDE_CODE_MAX_CONTEXT_TOKENS": "500000",
|
||||||
|
"CLAUDE_CODE_AUTO_COMPACT_WINDOW": "300000"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let backfilled =
|
||||||
|
strip_common_config_from_live_settings(&db, &AppType::Claude, &provider, live);
|
||||||
|
assert_eq!(
|
||||||
|
backfilled["env"]["CLAUDE_CODE_MAX_CONTEXT_TOKENS"],
|
||||||
|
json!("500000")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
backfilled["env"]["CLAUDE_CODE_AUTO_COMPACT_WINDOW"],
|
||||||
|
json!("300000")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// C5 回归锁:前端表单的合并/剥离必须走 toml_edit 文档模型。
|
/// C5 回归锁:前端表单的合并/剥离必须走 toml_edit 文档模型。
|
||||||
/// smol-toml 的 parse→merge→stringify 整文档重序列化会丢注释、
|
/// smol-toml 的 parse→merge→stringify 整文档重序列化会丢注释、
|
||||||
/// 按字母序重排键、并为 dotted 表生成多余的空父表头。
|
/// 按字母序重排键、并为 dotted 表生成多余的空父表头。
|
||||||
|
|||||||
@@ -722,6 +722,8 @@ mod tests {
|
|||||||
"ANTHROPIC_BASE_URL": "https://example.com",
|
"ANTHROPIC_BASE_URL": "https://example.com",
|
||||||
"ANTHROPIC_MODEL": "claude-x",
|
"ANTHROPIC_MODEL": "claude-x",
|
||||||
"CLAUDE_CODE_SUBAGENT_MODEL": "gpt-5.4-mini",
|
"CLAUDE_CODE_SUBAGENT_MODEL": "gpt-5.4-mini",
|
||||||
|
"CLAUDE_CODE_MAX_CONTEXT_TOKENS": "400000",
|
||||||
|
"CLAUDE_CODE_AUTO_COMPACT_WINDOW": "400000",
|
||||||
// 可共享、非机密配置(复数 _TOKENS 不应被误剥)
|
// 可共享、非机密配置(复数 _TOKENS 不应被误剥)
|
||||||
"ENABLE_TOOL_SEARCH": "true",
|
"ENABLE_TOOL_SEARCH": "true",
|
||||||
"CLAUDE_CODE_MAX_OUTPUT_TOKENS": "8192"
|
"CLAUDE_CODE_MAX_OUTPUT_TOKENS": "8192"
|
||||||
@@ -767,6 +769,12 @@ mod tests {
|
|||||||
assert!(env
|
assert!(env
|
||||||
.and_then(|e| e.get("CLAUDE_CODE_SUBAGENT_MODEL"))
|
.and_then(|e| e.get("CLAUDE_CODE_SUBAGENT_MODEL"))
|
||||||
.is_none());
|
.is_none());
|
||||||
|
assert!(env
|
||||||
|
.and_then(|e| e.get("CLAUDE_CODE_MAX_CONTEXT_TOKENS"))
|
||||||
|
.is_none());
|
||||||
|
assert!(env
|
||||||
|
.and_then(|e| e.get("CLAUDE_CODE_AUTO_COMPACT_WINDOW"))
|
||||||
|
.is_none());
|
||||||
|
|
||||||
// 可共享的非机密配置必须保留(含复数 _TOKENS 不被误剥)
|
// 可共享的非机密配置必须保留(含复数 _TOKENS 不被误剥)
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -2935,6 +2943,11 @@ impl ProviderService {
|
|||||||
"ANTHROPIC_DEFAULT_FABLE_MODEL",
|
"ANTHROPIC_DEFAULT_FABLE_MODEL",
|
||||||
"ANTHROPIC_DEFAULT_FABLE_MODEL_NAME",
|
"ANTHROPIC_DEFAULT_FABLE_MODEL_NAME",
|
||||||
"CLAUDE_CODE_SUBAGENT_MODEL",
|
"CLAUDE_CODE_SUBAGENT_MODEL",
|
||||||
|
// Context limits follow the actual upstream model. Sharing these
|
||||||
|
// across providers can cap GPT/Kimi to the wrong window and make
|
||||||
|
// Claude Code compact too early or miss the upstream limit.
|
||||||
|
"CLAUDE_CODE_MAX_CONTEXT_TOKENS",
|
||||||
|
"CLAUDE_CODE_AUTO_COMPACT_WINDOW",
|
||||||
"ANTHROPIC_BASE_URL",
|
"ANTHROPIC_BASE_URL",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -394,7 +394,7 @@ export const claudeDesktopProviderPresets: ClaudeDesktopProviderPreset[] = [
|
|||||||
apiFormat: "openai_responses",
|
apiFormat: "openai_responses",
|
||||||
providerType: "codex_oauth",
|
providerType: "codex_oauth",
|
||||||
requiresOAuth: true,
|
requiresOAuth: true,
|
||||||
modelRoutes: brandedRoutes("gpt-5.5", "gpt-5.5", "gpt-5.4-mini"),
|
modelRoutes: brandedRoutes("gpt-5.6", "gpt-5.6", "gpt-5.6-luna"),
|
||||||
icon: "openai",
|
icon: "openai",
|
||||||
iconColor: "#000000",
|
iconColor: "#000000",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1227,10 +1227,32 @@ export const providerPresets: ProviderPreset[] = [
|
|||||||
// base_url 由代理后端强制重写为 chatgpt.com/backend-api/codex
|
// base_url 由代理后端强制重写为 chatgpt.com/backend-api/codex
|
||||||
// 用户无需配置
|
// 用户无需配置
|
||||||
ANTHROPIC_BASE_URL: "https://chatgpt.com/backend-api/codex",
|
ANTHROPIC_BASE_URL: "https://chatgpt.com/backend-api/codex",
|
||||||
ANTHROPIC_MODEL: "gpt-5.5",
|
ANTHROPIC_MODEL: "gpt-5.6",
|
||||||
ANTHROPIC_DEFAULT_HAIKU_MODEL: "gpt-5.4-mini",
|
ANTHROPIC_DEFAULT_HAIKU_MODEL: "gpt-5.6-luna",
|
||||||
ANTHROPIC_DEFAULT_SONNET_MODEL: "gpt-5.5",
|
ANTHROPIC_DEFAULT_SONNET_MODEL: "gpt-5.6",
|
||||||
ANTHROPIC_DEFAULT_OPUS_MODEL: "gpt-5.5",
|
ANTHROPIC_DEFAULT_OPUS_MODEL: "gpt-5.6",
|
||||||
|
// Claude Code falls back to a 200K context window for unrecognized
|
||||||
|
// non-Claude model ids. The ChatGPT Codex backend catalogs gpt-5.6
|
||||||
|
// at a 372K window with a ~353K effective budget (openai/codex#31860),
|
||||||
|
// not the 1.05M API window. Declare 372K for both knobs: Claude Code's
|
||||||
|
// built-in output reserve and compact buffer already keep the compact
|
||||||
|
// trigger below the effective budget.
|
||||||
|
CLAUDE_CODE_MAX_CONTEXT_TOKENS: "${CLAUDE_CODE_MAX_CONTEXT_TOKENS}",
|
||||||
|
CLAUDE_CODE_AUTO_COMPACT_WINDOW: "${CLAUDE_CODE_AUTO_COMPACT_WINDOW}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
templateValues: {
|
||||||
|
CLAUDE_CODE_MAX_CONTEXT_TOKENS: {
|
||||||
|
label: "Max Context Tokens",
|
||||||
|
placeholder: "372000",
|
||||||
|
defaultValue: "372000",
|
||||||
|
editorValue: "372000",
|
||||||
|
},
|
||||||
|
CLAUDE_CODE_AUTO_COMPACT_WINDOW: {
|
||||||
|
label: "Auto Compact Window",
|
||||||
|
placeholder: "372000",
|
||||||
|
defaultValue: "372000",
|
||||||
|
editorValue: "372000",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
category: "third_party",
|
category: "third_party",
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export interface CodexTemplate {
|
|||||||
*/
|
*/
|
||||||
export function getCodexCustomTemplate(): CodexTemplate {
|
export function getCodexCustomTemplate(): CodexTemplate {
|
||||||
const config = `model_provider = "custom"
|
const config = `model_provider = "custom"
|
||||||
model = "gpt-5.5"
|
model = "gpt-5.6"
|
||||||
model_reasoning_effort = "high"
|
model_reasoning_effort = "high"
|
||||||
disable_response_storage = true
|
disable_response_storage = true
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import { providerPresets } from "@/config/claudeProviderPresets";
|
import { providerPresets } from "@/config/claudeProviderPresets";
|
||||||
|
import { applyTemplateValues } from "@/utils/providerConfigUtils";
|
||||||
|
|
||||||
describe("Kimi For Coding Provider Preset", () => {
|
describe("Kimi For Coding Provider Preset", () => {
|
||||||
const kimiForCoding = providerPresets.find(
|
const kimiForCoding = providerPresets.find(
|
||||||
@@ -28,6 +29,47 @@ describe("Kimi For Coding Provider Preset", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("Codex Provider Preset", () => {
|
||||||
|
const codex = providerPresets.find((p) => p.name === "Codex");
|
||||||
|
|
||||||
|
it("should include the Codex preset", () => {
|
||||||
|
expect(codex).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should override Claude Code's 200K fallback for GPT models", () => {
|
||||||
|
const env = (codex!.settingsConfig as any).env;
|
||||||
|
expect(env).toHaveProperty(
|
||||||
|
"CLAUDE_CODE_MAX_CONTEXT_TOKENS",
|
||||||
|
"${CLAUDE_CODE_MAX_CONTEXT_TOKENS}",
|
||||||
|
);
|
||||||
|
expect(env).toHaveProperty(
|
||||||
|
"CLAUDE_CODE_AUTO_COMPACT_WINDOW",
|
||||||
|
"${CLAUDE_CODE_AUTO_COMPACT_WINDOW}",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should expose the Codex-catalog 372K window for both context knobs", () => {
|
||||||
|
const values = codex!.templateValues as any;
|
||||||
|
expect(values?.CLAUDE_CODE_MAX_CONTEXT_TOKENS).toMatchObject({
|
||||||
|
defaultValue: "372000",
|
||||||
|
editorValue: "372000",
|
||||||
|
});
|
||||||
|
expect(values?.CLAUDE_CODE_AUTO_COMPACT_WINDOW).toMatchObject({
|
||||||
|
defaultValue: "372000",
|
||||||
|
editorValue: "372000",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should resolve both context placeholders into Claude Code env values", () => {
|
||||||
|
const config = applyTemplateValues(
|
||||||
|
codex!.settingsConfig,
|
||||||
|
codex!.templateValues,
|
||||||
|
) as any;
|
||||||
|
expect(config.env.CLAUDE_CODE_MAX_CONTEXT_TOKENS).toBe("372000");
|
||||||
|
expect(config.env.CLAUDE_CODE_AUTO_COMPACT_WINDOW).toBe("372000");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("AWS Bedrock Provider Presets", () => {
|
describe("AWS Bedrock Provider Presets", () => {
|
||||||
const bedrockAksk = providerPresets.find(
|
const bedrockAksk = providerPresets.find(
|
||||||
(p) => p.name === "AWS Bedrock (AKSK)",
|
(p) => p.name === "AWS Bedrock (AKSK)",
|
||||||
|
|||||||
Reference in New Issue
Block a user