fix(zcode): sync model context windows from CPA core

This commit is contained in:
lzt404 committed 2026-08-15 14:58:56 +08:00
1 parent b933a0fa99
commit 607c930d7b
8 files changed
+146 -28

No files matched your search

+4
View File
@@ -640,6 +640,10 @@ pub(crate) async fn fetch_prepared_agent_models(
prepare_codex_agent_models(&runtime_models)
} else {
let mut models = fetch_agent_models(config.port, api_key).await?;
if client == AgentClient::ZCode {
let runtime_models = fetch_codex_runtime_models(config.port, api_key).await?;
codex_catalog::merge_runtime_context_windows(&mut models, &runtime_models);
}
if matches!(client, AgentClient::ClaudeCode | AgentClient::ClaudeDesktop) {
let content = fetch_management_config_yaml(config).await?;
mark_configured_agent_model_aliases(&mut models, &content)?;
+9 -8
View File
@@ -2713,14 +2713,15 @@ pub(crate) fn build_zcode_agent_config(
.into_iter()
.map(|model| {
let display_name = model.alias.as_deref().unwrap_or(&model.name).to_string();
(
model.name,
serde_json::json!({
"name": display_name,
"kinds": ["anthropic"],
"defaultKind": "anthropic"
}),
)
let mut entry = serde_json::Map::new();
entry.insert("name".to_string(), serde_json::json!(display_name));
if let Some(context_window) = model.context_window {
entry.insert(
"limit".to_string(),
serde_json::json!({ "context": context_window }),
);
}
(model.name, serde_json::Value::Object(entry))
})
.collect::<serde_json::Map<_, _>>();
let managed_provider = ensure_json_object_entry(providers, MANAGED_AGENT_PROVIDER_ID);
+28 -16
View File
@@ -2464,18 +2464,38 @@ pub(crate) fn inspect_zcode_agent_config(
.get("provider")
.and_then(|providers| providers.get(MANAGED_AGENT_PROVIDER_ID));
let expected_base = format!("http://127.0.0.1:{port}");
let configured = provider
.and_then(|provider| provider.get("enabled"))
.and_then(serde_json::Value::as_bool)
== Some(true)
let api_format_matches = provider
.and_then(|provider| provider.get("apiFormat"))
.and_then(serde_json::Value::as_str)
.is_none_or(|api_format| api_format == "anthropic-messages");
let prefix = format!("{MANAGED_AGENT_PROVIDER_ID}/");
let model = root
.get("model")
.and_then(serde_json::Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
.and_then(|value| value.strip_prefix(&prefix))
.map(str::to_string);
let selected_model_exists = model.as_deref().is_some_and(|selected_model| {
provider
.and_then(|provider| provider.get("models"))
.and_then(serde_json::Value::as_object)
.is_some_and(|models| {
models
.keys()
.any(|model| model.eq_ignore_ascii_case(selected_model))
})
});
let configured = selected_model_exists
&& api_format_matches
&& provider
.and_then(|provider| provider.get("enabled"))
.and_then(serde_json::Value::as_bool)
== Some(true)
&& provider
.and_then(|provider| provider.get("kind"))
.and_then(serde_json::Value::as_str)
== Some("anthropic")
&& provider
.and_then(|provider| provider.get("apiFormat"))
.and_then(serde_json::Value::as_str)
== Some("anthropic-messages")
&& provider
.and_then(|provider| provider.get("options"))
.and_then(|options| options.get("baseURL"))
@@ -2486,14 +2506,6 @@ pub(crate) fn inspect_zcode_agent_config(
.and_then(|options| options.get("apiKey"))
.and_then(serde_json::Value::as_str)
== Some(api_key);
let prefix = format!("{MANAGED_AGENT_PROVIDER_ID}/");
let model = root
.get("model")
.and_then(serde_json::Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
.map(|value| value.strip_prefix(&prefix).unwrap_or(value))
.map(str::to_string);
Ok((configured, model))
}
+54
View File
@@ -154,6 +154,26 @@ pub(crate) fn parse_runtime_models(payload: &Value) -> Result<Vec<CodexRuntimeMo
Ok(models)
}
pub(crate) fn merge_runtime_context_windows(
models: &mut [AgentModelOption],
runtime_models: &[CodexRuntimeModel],
) {
for model in models {
let Some(runtime_model) = runtime_models
.iter()
.find(|runtime_model| runtime_model.slug.eq_ignore_ascii_case(&model.name))
else {
continue;
};
if let Some(context_window) = runtime_model
.context_window
.or(runtime_model.max_context_window)
{
model.context_window = Some(context_window);
}
}
}
pub(crate) fn prepare_catalog(
runtime_models: &[CodexRuntimeModel],
) -> Result<PreparedCodexCatalog, String> {
@@ -873,6 +893,40 @@ mod tests {
assert_eq!(catalog.models[1].alias.as_deref(), Some("gpt-5.5"));
}
#[test]
fn runtime_context_windows_override_generic_model_metadata() {
let mut models = vec![
AgentModelOption {
name: "GPT-Test".to_string(),
alias: None,
is_alias: false,
context_window: Some(200_000),
},
AgentModelOption {
name: "unmatched".to_string(),
alias: None,
is_alias: false,
context_window: Some(128_000),
},
AgentModelOption {
name: "max-only".to_string(),
alias: None,
is_alias: false,
context_window: None,
},
];
let runtime_models = runtime(serde_json::json!({"models":[
{"id":"gpt-test","context_window":372000},
{"id":"max-only","max_context_window":272000}
]}));
merge_runtime_context_windows(&mut models, &runtime_models);
assert_eq!(models[0].context_window, Some(372_000));
assert_eq!(models[1].context_window, Some(128_000));
assert_eq!(models[2].context_window, Some(272_000));
}
#[test]
fn all_runtime_models_advertise_fast_capabilities() {
let runtime = runtime(serde_json::json!({"models":[
+48 -1
View File
@@ -617,7 +617,9 @@ fn zcode_agent_config_preserves_other_providers_and_uses_anthropic_messages() {
let home = agent_test_home("zcode-agent-config");
let path = home.join(".zcode/v2/config.json");
fs::create_dir_all(path.parent().unwrap()).unwrap();
let models = test_agent_models(&["gpt-test", "deepseek-test"]);
let mut models = test_agent_models(&["gpt-test", "deepseek-test"]);
models[0].context_window = Some(372_000);
models[1].context_window = Some(272_000);
let rendered = build_zcode_agent_config(
Some(
r#"{"locale":"zh-CN","provider":{"other":{"kind":"openai"},"cpa-gui":{"custom":"keep","options":{"timeout":30}}}}"#,
@@ -654,10 +656,55 @@ fn zcode_agent_config_preserves_other_providers_and_uses_anthropic_messages() {
"http://127.0.0.1:8317"
);
assert_eq!(value["model"], "cpa-gui/gpt-test");
assert_eq!(
value["provider"][MANAGED_AGENT_PROVIDER_ID]["models"]["gpt-test"]["limit"]["context"],
372_000
);
assert_eq!(
value["provider"][MANAGED_AGENT_PROVIDER_ID]["models"]["deepseek-test"]["limit"]["context"],
272_000
);
assert_eq!(
inspect_zcode_agent_config(&path, 8317, DEFAULT_API_KEY).unwrap(),
(true, Some("gpt-test".to_string()))
);
let mut normalized = value;
let provider = normalized["provider"][MANAGED_AGENT_PROVIDER_ID]
.as_object_mut()
.unwrap();
provider.remove("apiFormat");
provider.remove("defaultKind");
provider.remove("npm");
fs::write(&path, serde_json::to_string_pretty(&normalized).unwrap()).unwrap();
assert_eq!(
inspect_zcode_agent_config(&path, 8317, DEFAULT_API_KEY).unwrap(),
(true, Some("gpt-test".to_string()))
);
fs::remove_dir_all(home).unwrap();
}
#[test]
fn zcode_inspection_requires_the_default_model_to_belong_to_the_managed_provider() {
let home = agent_test_home("zcode-default-model-inspection");
let path = home.join(".zcode/v2/config.json");
fs::create_dir_all(path.parent().unwrap()).unwrap();
let rendered = build_zcode_agent_config(
None,
"http://127.0.0.1:8317",
DEFAULT_API_KEY,
"gpt-test",
&test_agent_models(&["gpt-test"]),
)
.unwrap();
let mut value: serde_json::Value = serde_json::from_str(&rendered).unwrap();
value["model"] = serde_json::json!("other/gpt-test");
fs::write(&path, serde_json::to_string_pretty(&value).unwrap()).unwrap();
assert_eq!(
inspect_zcode_agent_config(&path, 8317, DEFAULT_API_KEY).unwrap(),
(false, None)
);
fs::remove_dir_all(home).unwrap();
}
+1 -1
View File
@@ -620,7 +620,7 @@ export const jaOverrides = {
'agents.modify.default': 'デフォルト設定',
'agents.modify.clear': '設定を消去',
'agents.modify.invalid': '現在の設定を解析できません。「設定変更を適用」は最小の有効な設定を再構築し、「デフォルト設定」はファイル全体をリセットします。',
'agents.modify.zcodeRestart': '設定の適用または終了後、モデルプロバイダーを再読み込みするため ZCode を完全終了して再起動してください。',
'agents.modify.zcodeRestart': '設定の適用または終了後、ZCode を完全終了して再起動してください。デフォルトモデルとコンテキスト長は新しいセッションで有効になります。',
'agents.pi.installTitle': 'Pi CLIProxyAPI プロバイダー',
'agents.pi.installDescription': 'プラグインをインストールまたは更新し、CPA 認証情報、既定のプロバイダー、既定のモデルを修復します',
'agents.pi.install': 'Pi プラグインをインストール',
+1 -1
View File
@@ -620,7 +620,7 @@ export const en: Record<MessageKey, string> = {
'agents.modify.default': 'Default Configuration',
'agents.modify.clear': 'Clear Configuration',
'agents.modify.invalid': 'The current configuration cannot be parsed. Apply Configuration Changes rebuilds a minimal valid configuration; Default Configuration resets the whole file.',
'agents.modify.zcodeRestart': 'Fully quit and restart ZCode after applying or closing the configuration so it reloads model providers.',
'agents.modify.zcodeRestart': 'Fully quit and restart ZCode after applying or closing the configuration; the default model and context length take effect in new sessions.',
'agents.pi.installTitle': 'Pi CLIProxyAPI provider',
'agents.pi.installDescription': 'Install or update the plugin and repair CPA credentials, the default provider, and the default model.',
'agents.pi.install': 'Install Pi Plugin',
+1 -1
View File
@@ -618,7 +618,7 @@ export const zhCN = {
'agents.modify.default': '默认配置',
'agents.modify.clear': '清空配置',
'agents.modify.invalid': '当前配置无法解析。点击“应用配置修改”会重建最小合法配置;“默认配置”会重置整个文件。',
'agents.modify.zcodeRestart': '应用或关闭配置后,请完全退出并重新启动 ZCode 以加载模型供应商。',
'agents.modify.zcodeRestart': '应用或关闭配置后,请完全退出并重新启动 ZCode;默认模型和上下文长度会在新会话中生效。',
'agents.pi.installTitle': 'Pi CLIProxyAPI provider',
'agents.pi.installDescription': '安装或更新插件,并修复 CPA 凭据、默认 provider 与默认模型',
'agents.pi.install': '安装 Pi 插件',