fix(codex): preserve context defaults from the client model API
This commit is contained in:
1 parent
898cf080d3
commit
ca76f344fb
8 files changed
+126
-171
No files matched your search
@@ -135,7 +135,7 @@ pub(crate) async fn refresh_agent_config_statuses(
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) async fn get_agent_models(
|
||||
app: tauri::AppHandle,
|
||||
_app: tauri::AppHandle,
|
||||
gui_config_state: tauri::State<'_, GuiConfigState>,
|
||||
client: String,
|
||||
) -> Result<Vec<AgentModelOption>, String> {
|
||||
@@ -151,11 +151,6 @@ pub(crate) async fn get_agent_models(
|
||||
};
|
||||
let config = gui_config_state.snapshot()?;
|
||||
let prepared = fetch_prepared_agent_models(client, &config).await?;
|
||||
if client == AgentClient::Codex {
|
||||
if let Err(error) = sync_prepared_codex_model_catalog(&app, &config, &prepared) {
|
||||
eprintln!("自动刷新已应用的 Codex 模型目录失败: {error}");
|
||||
}
|
||||
}
|
||||
Ok(prepared.models)
|
||||
}
|
||||
|
||||
@@ -794,53 +789,20 @@ pub(crate) async fn fetch_codex_runtime_models(
|
||||
Err("本地内核不支持 Codex 模型列表接口".to_string())
|
||||
}
|
||||
|
||||
// The client_version response contains synthesized Codex templates; its context
|
||||
// fields are not the raw core model definitions. Resolve defaults through the
|
||||
// management APIs before generating or editing the Codex catalog.
|
||||
// Codex client metadata distinguishes the default context from the maximum.
|
||||
// Do not overwrite either with the generic model-definitions context_length.
|
||||
pub(crate) async fn fetch_codex_catalog_runtime_models(
|
||||
config: &GuiConfigFile,
|
||||
) -> Result<Vec<codex_catalog::CodexRuntimeModel>, String> {
|
||||
let (runtime, definitions, content) = tokio::join!(
|
||||
let (runtime, content) = tokio::join!(
|
||||
fetch_codex_runtime_models(config.port, effective_agent_api_key(config)),
|
||||
fetch_codex_context_definitions(config),
|
||||
fetch_management_config_yaml(config),
|
||||
);
|
||||
let mut runtime = runtime?;
|
||||
let definitions = definitions?;
|
||||
let content = content?;
|
||||
let mut aliases = runtime.iter().map(|model| AgentModelOption {
|
||||
name: model.slug.clone(), alias: None, is_alias: false, context_window: None,
|
||||
}).collect::<Vec<_>>();
|
||||
mark_configured_agent_model_aliases(&mut aliases, &content)?;
|
||||
codex_catalog::merge_context_definitions(&mut runtime, &definitions, &aliases);
|
||||
codex_catalog::apply_configured_context_limits(&mut runtime, &content)?;
|
||||
codex_catalog::apply_configured_context_limits(&mut runtime, &content?)?;
|
||||
Ok(runtime)
|
||||
}
|
||||
|
||||
async fn fetch_codex_context_definitions(
|
||||
config: &GuiConfigFile,
|
||||
) -> Result<Vec<CodexModelDefinition>, String> {
|
||||
// API-key sources may expose models from other channels, so do not filter by
|
||||
// active OAuth credentials. Model IDs still come only from the available list.
|
||||
let channels = ["gemini", "vertex", "aistudio", "antigravity", "claude", "codex", "kimi", "xai"];
|
||||
let results = futures_util::future::join_all(channels.iter().map(|channel|
|
||||
fetch_oauth_channel_model_definitions(config, channel)
|
||||
)).await;
|
||||
let mut definitions = Vec::new();
|
||||
let mut successes = 0;
|
||||
let mut first_error = None;
|
||||
for result in results {
|
||||
match result {
|
||||
Ok(models) => { successes += 1; definitions.extend(models); }
|
||||
Err(error) => { first_error.get_or_insert(error); }
|
||||
}
|
||||
}
|
||||
if successes == 0 {
|
||||
return Err(format!("读取 CPA 模型上下文定义失败: {}", first_error.unwrap_or_default()));
|
||||
}
|
||||
Ok(definitions)
|
||||
}
|
||||
|
||||
pub(crate) async fn fetch_prepared_agent_models(
|
||||
client: AgentClient,
|
||||
config: &GuiConfigFile,
|
||||
@@ -1267,56 +1229,12 @@ pub(crate) async fn update_agent_config(
|
||||
gui_config_state: tauri::State<'_, GuiConfigState>,
|
||||
client: String,
|
||||
model: String,
|
||||
oauth_configuration: bool,
|
||||
claude_code_model_mappings: Option<ClaudeDesktopModelMappings>,
|
||||
claude_desktop_model_mappings: Option<ClaudeDesktopModelMappings>,
|
||||
) -> Result<AgentConfigActionResult, String> {
|
||||
let client = AgentClient::parse(&client)?;
|
||||
let home = app
|
||||
.path()
|
||||
.home_dir()
|
||||
.map_err(|error| format!("无法获取用户目录: {error}"))?;
|
||||
let config = gui_config_state.snapshot()?;
|
||||
let port = config.port;
|
||||
let api_key = effective_agent_api_key(&config);
|
||||
let prepared = fetch_prepared_agent_models(client, &config).await?;
|
||||
let model = resolve_available_agent_model(&prepared.models, &validate_agent_model(&model)?)?;
|
||||
let claude_code_model_mappings = resolve_claude_code_model_mappings(
|
||||
client,
|
||||
&prepared.models,
|
||||
&model,
|
||||
claude_code_model_mappings,
|
||||
)?;
|
||||
let claude_desktop_model_mappings = resolve_claude_desktop_model_mappings(
|
||||
client,
|
||||
&prepared.models,
|
||||
&model,
|
||||
claude_desktop_model_mappings,
|
||||
)?;
|
||||
if let Some(mappings) = claude_desktop_model_mappings.as_ref() {
|
||||
ensure_claude_desktop_model_aliases(&config, mappings, &prepared.models).await?;
|
||||
}
|
||||
let _guard = AGENT_CONFIG_FILE_LOCK
|
||||
.lock()
|
||||
.map_err(|_| "智能体配置文件锁已损坏".to_string())?;
|
||||
let oauth_configuration = if client == AgentClient::Codex {
|
||||
current_codex_oauth_configuration(&home)?
|
||||
} else {
|
||||
false
|
||||
};
|
||||
apply_agent_configuration_with_oauth(
|
||||
client,
|
||||
&home,
|
||||
port,
|
||||
api_key,
|
||||
&model,
|
||||
AgentConfigurationOptions {
|
||||
models: &prepared.models,
|
||||
codex_catalog: prepared.codex_catalog.as_deref(),
|
||||
oauth_configuration,
|
||||
claude_code_model_mappings: claude_code_model_mappings.as_ref(),
|
||||
claude_desktop_model_mappings: claude_desktop_model_mappings.as_ref(),
|
||||
},
|
||||
)
|
||||
apply_agent_config(app, gui_config_state, client, model, oauth_configuration,
|
||||
claude_code_model_mappings, claude_desktop_model_mappings).await
|
||||
}
|
||||
|
||||
pub(crate) fn validate_agent_can_enable(
|
||||
|
||||
@@ -9,7 +9,7 @@ pub(crate) use customizations::{
|
||||
editor_snapshot, load_customizations, save_customizations, CatalogEditorRequest,
|
||||
CatalogEditorSnapshot,
|
||||
};
|
||||
pub(crate) use runtime_context::{apply_configured_context_limits, merge_context_definitions};
|
||||
pub(crate) use runtime_context::apply_configured_context_limits;
|
||||
|
||||
const MODEL_CATALOG_JSON: &str = include_str!("../resources/codex_models/model-catalog.json");
|
||||
const FALLBACK_MODEL_JSON: &str = include_str!("../resources/codex_models/fallback-model.json");
|
||||
@@ -165,7 +165,7 @@ pub(crate) fn parse_runtime_models(payload: &Value) -> Result<Vec<CodexRuntimeMo
|
||||
context_window,
|
||||
max_context_window,
|
||||
context_source: if context_window.or(max_context_window).is_some() {
|
||||
"compatibility"
|
||||
"client"
|
||||
} else {
|
||||
"template"
|
||||
},
|
||||
|
||||
@@ -1,32 +1,5 @@
|
||||
use super::*;
|
||||
|
||||
pub(crate) fn merge_context_definitions(
|
||||
runtime_models: &mut [CodexRuntimeModel],
|
||||
definitions: &[crate::CodexModelDefinition],
|
||||
aliases: &[AgentModelOption],
|
||||
) {
|
||||
for runtime in runtime_models {
|
||||
let source = aliases
|
||||
.iter()
|
||||
.find(|model| model.is_alias && model.name.eq_ignore_ascii_case(&runtime.slug))
|
||||
.and_then(|model| model.alias.as_deref())
|
||||
.unwrap_or(&runtime.slug);
|
||||
// These are core model definitions, not the synthesized Codex client catalog.
|
||||
let context = definitions
|
||||
.iter()
|
||||
.filter(|definition| definition.id.eq_ignore_ascii_case(source))
|
||||
.filter_map(|definition| definition.context_window)
|
||||
.filter(|value| *value > 0)
|
||||
.min();
|
||||
if let Some(context) = context {
|
||||
runtime.context_window = Some(context);
|
||||
runtime.max_context_window =
|
||||
Some(runtime.max_context_window.unwrap_or(context).max(context));
|
||||
runtime.context_source = "definition";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn apply_configured_context_limits(
|
||||
runtime_models: &mut [CodexRuntimeModel],
|
||||
content: &str,
|
||||
@@ -85,32 +58,13 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn raw_core_definitions_replace_synthesized_272k_for_models_and_aliases() {
|
||||
let mut runtime = parse_runtime_models(&serde_json::json!({"models":[
|
||||
fn codex_client_context_defaults_are_preserved_in_editor_and_generated_catalog() {
|
||||
let runtime = parse_runtime_models(&serde_json::json!({"models":[
|
||||
{"slug":"gpt-5.6-sol","context_window":272000,"max_context_window":872000},
|
||||
{"slug":"gemini-3.8-flash-high","context_window":272000,"max_context_window":272000},
|
||||
{"slug":"fast-alias","context_window":272000,"max_context_window":272000},
|
||||
{"slug":"no-definition","context_window":272000,"max_context_window":272000}
|
||||
{"slug":"fast-alias","context_window":128000,"max_context_window":256000},
|
||||
{"slug":"missing-context"}
|
||||
]}))
|
||||
.unwrap();
|
||||
let definitions = crate::parse_codex_model_definitions(&serde_json::json!({"models":[
|
||||
{"id":"gpt-5.6-sol","context_length":921000},
|
||||
{"id":"gemini-3.8-flash-high","context_length":1048576}
|
||||
]}))
|
||||
.unwrap();
|
||||
let aliases = vec![AgentModelOption {
|
||||
name: "fast-alias".to_string(),
|
||||
alias: Some("GPT-5.6-SOL".to_string()),
|
||||
is_alias: true,
|
||||
context_window: None,
|
||||
}];
|
||||
merge_context_definitions(&mut runtime, &definitions, &aliases);
|
||||
assert_eq!(runtime[0].context_window, Some(921_000));
|
||||
assert_eq!(runtime[0].max_context_window, Some(921_000));
|
||||
assert_eq!(runtime[1].context_window, Some(1_048_576));
|
||||
assert_eq!(runtime[2].context_window, Some(921_000));
|
||||
assert_eq!(runtime[2].context_source, "definition");
|
||||
assert_eq!(runtime[3].context_source, "compatibility");
|
||||
let sources = parse_sources(MODEL_CATALOG_JSON).unwrap();
|
||||
let state = CatalogState {
|
||||
sources,
|
||||
@@ -119,19 +73,39 @@ mod tests {
|
||||
};
|
||||
let snapshot = customizations::snapshot_for_state(&runtime, &state).unwrap();
|
||||
let snapshot = serde_json::to_value(snapshot).unwrap();
|
||||
let model = snapshot["models"]
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.find(|model| model["slug"] == "gpt-5.6-sol")
|
||||
.unwrap();
|
||||
assert_eq!(model["configuration"]["context_window"], 921_000);
|
||||
assert_eq!(model["defaults"]["context_window"], 921_000);
|
||||
assert_eq!(model["contextSource"], "definition");
|
||||
let prepared =
|
||||
prepare_catalog_with_customizations(&runtime, &state.sources, &state.customizations)
|
||||
.unwrap();
|
||||
let catalog: Value = serde_json::from_str(&prepared.json).unwrap();
|
||||
for (slug, context, maximum, source) in [
|
||||
("gpt-5.6-sol", 272_000, 872_000, "client"),
|
||||
("fast-alias", 128_000, 256_000, "client"),
|
||||
("missing-context", 272_000, 272_000, "template"),
|
||||
] {
|
||||
let model = snapshot["models"]
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.find(|model| model["slug"] == slug)
|
||||
.unwrap();
|
||||
for field in ["configuration", "defaults"] {
|
||||
assert_eq!(model[field]["context_window"], context);
|
||||
assert_eq!(model[field]["max_context_window"], maximum);
|
||||
}
|
||||
assert_eq!(model["contextSource"], source);
|
||||
let generated = catalog["models"]
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.find(|model| model["slug"] == slug)
|
||||
.unwrap();
|
||||
assert_eq!(generated["context_window"], context);
|
||||
assert_eq!(generated["max_context_window"], maximum);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn core_configured_context_limit_takes_precedence_over_static_definitions() {
|
||||
fn core_configured_context_limit_takes_precedence_over_client_defaults() {
|
||||
let mut runtime = parse_runtime_models(&serde_json::json!({"models":[
|
||||
{"slug":"custom-alias","context_window":921000,"max_context_window":921000},
|
||||
{"slug":"unmodified","context_window":128000,"max_context_window":128000}
|
||||
|
||||
+25
-4
@@ -76,9 +76,8 @@ export const jaOverrides = {
|
||||
'agents.catalog.displayName': '表示名',
|
||||
'agents.catalog.description': 'モデルの説明',
|
||||
'agents.catalog.context': 'コンテキストウィンドウ(トークン)',
|
||||
'agents.catalog.contextSource.definition': 'コンテキストの取得元:CPA コア API のモデル定義。',
|
||||
'agents.catalog.contextSource.client': 'コンテキストの取得元:CPA コアの Codex クライアントモデル一覧 API。',
|
||||
'agents.catalog.contextSource.configuration': 'コンテキストの取得元:CPA コアでこのモデルに設定された max-context-length。',
|
||||
'agents.catalog.contextSource.compatibility': 'CPA にこのモデル固有のコンテキスト定義がありません。現在の値は互換カタログの値であり、汎用テンプレートの既定値である可能性があります。',
|
||||
'agents.catalog.contextSource.template': 'コアに有効なコンテキスト情報がありません。ソフトウェアのテンプレートの代替値を使用しています。',
|
||||
'agents.catalog.contextSource.customized': 'コンテキストの取得元:このモデルのカスタム設定。',
|
||||
'agents.catalog.maximum': '最大コンテキストウィンドウ(トークン)',
|
||||
@@ -810,6 +809,28 @@ export const jaOverrides = {
|
||||
'agents.status.invalid': '設定が無効',
|
||||
'agents.status.applied': '適用済み',
|
||||
'agents.status.unconfigured': '未設定',
|
||||
"agents.history.button": "設定履歴を復元",
|
||||
"agents.history.description": "ソフトウェア管理の項目のみ復元し、他の設定を保持します。復元前に現在の設定を保存し、フォームを更新します。",
|
||||
"agents.history.versions": "履歴",
|
||||
"agents.history.empty": "履歴はありません。設定更新時に自動保存されます。",
|
||||
"agents.history.files": "{count} ファイル",
|
||||
"agents.history.preview": "復元プレビュー",
|
||||
"agents.history.loading": "読み込み中…",
|
||||
"agents.history.select": "履歴を選択して復元する項目を確認してください。",
|
||||
"agents.history.unchanged": "設定は最新です。",
|
||||
"agents.history.updated": "設定を更新し、履歴を保存しました。",
|
||||
"agents.history.restored": "設定を復元しました。他の設定は保持されています。",
|
||||
"agents.history.field": "項目",
|
||||
"agents.history.current": "現在",
|
||||
"agents.history.target": "履歴値",
|
||||
"agents.history.restore": "この履歴を復元",
|
||||
"agents.history.before": "{source}前のバックアップ",
|
||||
"agents.history.source.update": "設定更新",
|
||||
"agents.history.source.default": "既定設定",
|
||||
"agents.history.source.clear": "設定消去",
|
||||
"agents.history.source.restore": "復元",
|
||||
"agents.history.source.sync": "自動同期",
|
||||
"agents.history.source.legacy": "旧バージョンのバックアップ",
|
||||
'agents.title': 'エージェント設定',
|
||||
'agents.embedded.title': 'クライアントとモデルを選んで起動',
|
||||
'agents.embedded.subtitle': 'ない場合は先にインストールしてください',
|
||||
@@ -937,10 +958,10 @@ export const jaOverrides = {
|
||||
'agents.pi.uninstall': 'Pi プラグインをアンインストール',
|
||||
'agents.pi.uninstalling': 'プラグインをアンインストール中',
|
||||
'agents.default.title': 'デフォルト設定にリセット',
|
||||
'agents.default.description': '選択中のモデルを使って {name} の関連設定ファイルをすべて書き直し、その他のユーザー内容を削除します。バックアップは作成されず、元に戻せません。',
|
||||
'agents.default.description': '選択中のモデルを使って {name} の関連設定ファイルをすべて書き直し、その他のユーザー内容を削除します。操作前に履歴を保存します。履歴から復元できるのは管理対象フィールドのみで、削除された独自設定は復元されません。',
|
||||
'agents.default.confirm': 'デフォルト設定を書き込む',
|
||||
'agents.clear.title': 'Codex 設定を消去',
|
||||
'agents.clear.description': 'Codex 設定ディレクトリの auth.json と config.toml を完全に削除し、このアプリが記録した適用状態をリセットします。バックアップは作成されず、元に戻せません。',
|
||||
'agents.clear.description': 'Codex 設定ディレクトリの auth.json と config.toml を完全に削除します。操作前に履歴を保存します。履歴から復元できるのは管理対象フィールドのみで、削除された独自設定は復元されません。',
|
||||
'agents.clear.confirm': '設定を消去',
|
||||
'agents.clear.success': 'Codex 設定を消去しました。',
|
||||
'apiAccess.compatibleName': 'OpenAI 互換 {number}',
|
||||
|
||||
+25
-4
@@ -26,9 +26,8 @@ export const en: Record<MessageKey, string> = {
|
||||
'agents.catalog.displayName': 'Display name',
|
||||
'agents.catalog.description': 'Description',
|
||||
'agents.catalog.context': 'Context window (tokens)',
|
||||
'agents.catalog.contextSource.definition': 'Context source: model definitions from the CPA core API.',
|
||||
'agents.catalog.contextSource.client': 'Context source: the CPA core Codex client model catalog API.',
|
||||
'agents.catalog.contextSource.configuration': 'Context source: max-context-length configured for this model in the CPA core.',
|
||||
'agents.catalog.contextSource.compatibility': 'CPA provides no independent context definition for this model. The current value comes from its compatibility catalog and may be a generic template default.',
|
||||
'agents.catalog.contextSource.template': 'The core provides no valid context data. The current value is a fallback from the application template.',
|
||||
'agents.catalog.contextSource.customized': 'Context source: your custom settings for this model.',
|
||||
'agents.catalog.maximum': 'Maximum context window (tokens)',
|
||||
@@ -810,6 +809,28 @@ export const en: Record<MessageKey, string> = {
|
||||
'agents.status.invalid': 'Invalid Configuration',
|
||||
'agents.status.applied': 'Applied',
|
||||
'agents.status.unconfigured': 'Not Configured',
|
||||
"agents.history.button": "Restore configuration history",
|
||||
"agents.history.description": "Restore only software-managed fields and preserve other settings. Current configuration is backed up first; form drafts refresh after restoring.",
|
||||
"agents.history.versions": "Versions",
|
||||
"agents.history.empty": "No history yet. Updating configuration saves versions automatically.",
|
||||
"agents.history.files": "{count} files",
|
||||
"agents.history.preview": "Restore preview",
|
||||
"agents.history.loading": "Loading…",
|
||||
"agents.history.select": "Select a version to preview the fields to restore.",
|
||||
"agents.history.unchanged": "Configuration is already up to date.",
|
||||
"agents.history.updated": "Configuration updated and history saved.",
|
||||
"agents.history.restored": "Configuration restored. Other custom settings were preserved.",
|
||||
"agents.history.field": "Field",
|
||||
"agents.history.current": "Current",
|
||||
"agents.history.target": "Historical",
|
||||
"agents.history.restore": "Restore this version",
|
||||
"agents.history.before": "Before {source}",
|
||||
"agents.history.source.update": "Update",
|
||||
"agents.history.source.default": "Default configuration",
|
||||
"agents.history.source.clear": "Clear configuration",
|
||||
"agents.history.source.restore": "Restore",
|
||||
"agents.history.source.sync": "Automatic sync",
|
||||
"agents.history.source.legacy": "Legacy backup",
|
||||
'agents.title': 'Agent Configuration',
|
||||
'agents.embedded.title': 'Select a client and model, then start',
|
||||
'agents.embedded.subtitle': 'Install it first if it is not available',
|
||||
@@ -937,10 +958,10 @@ export const en: Record<MessageKey, string> = {
|
||||
'agents.pi.uninstall': 'Uninstall Pi Plugin',
|
||||
'agents.pi.uninstalling': 'Uninstalling plugin',
|
||||
'agents.default.title': 'Reset to Default Configuration',
|
||||
'agents.default.description': 'This rewrites every related {name} configuration file using the selected model and deletes all other user content in those files. No backup is created and the operation cannot be undone.',
|
||||
'agents.default.description': 'This rewrites every related {name} configuration file using the selected model and deletes all other user content in those files. A history snapshot is saved first. History recovery restores only managed fields, not deleted custom settings.',
|
||||
'agents.default.confirm': 'Write Default Configuration',
|
||||
'agents.clear.title': 'Clear Codex Configuration',
|
||||
'agents.clear.description': 'This permanently deletes auth.json and config.toml from the Codex configuration directory and resets the application state tracked by this app. No backup is created and the operation cannot be undone.',
|
||||
'agents.clear.description': 'This permanently deletes auth.json and config.toml from the Codex configuration directory. A history snapshot is saved first. History recovery restores only managed fields, not deleted custom settings.',
|
||||
'agents.clear.confirm': 'Clear Configuration',
|
||||
'agents.clear.success': 'The Codex configuration has been cleared.',
|
||||
'apiAccess.compatibleName': 'OpenAI Compatible {number}',
|
||||
|
||||
@@ -25,9 +25,8 @@ export const zhCN = {
|
||||
'agents.catalog.displayName': '显示名称',
|
||||
'agents.catalog.description': '模型说明',
|
||||
'agents.catalog.context': '上下文窗口(tokens)',
|
||||
'agents.catalog.contextSource.definition': '上下文来源:CPA 内核的模型定义 API。',
|
||||
'agents.catalog.contextSource.client': '上下文来源:CPA 内核的 Codex 客户端模型列表 API。',
|
||||
'agents.catalog.contextSource.configuration': '上下文来源:CPA 内核中为此模型配置的 max-context-length。',
|
||||
'agents.catalog.contextSource.compatibility': 'CPA 未提供此模型的独立上下文定义,当前值来自兼容目录,可能是通用模板默认值。',
|
||||
'agents.catalog.contextSource.template': '内核未提供有效的上下文数据,当前使用软件模板的后备值。',
|
||||
'agents.catalog.contextSource.customized': '上下文来源:你为此模型保存或编辑的自定义配置。',
|
||||
'agents.catalog.maximum': '最大上下文窗口(tokens)',
|
||||
@@ -809,6 +808,28 @@ export const zhCN = {
|
||||
'agents.status.invalid': '配置无效',
|
||||
'agents.status.applied': '已应用',
|
||||
'agents.status.unconfigured': '尚未配置',
|
||||
"agents.history.button": "恢复历史配置",
|
||||
"agents.history.description": "仅恢复软件管理的配置字段,保留其他自定义设置。恢复前会自动备份当前配置;当前表单草稿将按恢复结果刷新。",
|
||||
"agents.history.versions": "历史版本",
|
||||
"agents.history.empty": "暂无配置历史。更新配置后会自动保留版本。",
|
||||
"agents.history.files": "{count} 个文件",
|
||||
"agents.history.preview": "恢复变更预览",
|
||||
"agents.history.loading": "正在读取…",
|
||||
"agents.history.select": "选择一个历史版本,查看将恢复的字段。",
|
||||
"agents.history.unchanged": "配置已是最新,无需更新。",
|
||||
"agents.history.updated": "配置已更新,历史备份已保存。",
|
||||
"agents.history.restored": "已恢复历史配置,其他自定义设置已保留。",
|
||||
"agents.history.field": "配置字段",
|
||||
"agents.history.current": "当前值",
|
||||
"agents.history.target": "历史值",
|
||||
"agents.history.restore": "恢复此版本",
|
||||
"agents.history.before": "{source}前备份",
|
||||
"agents.history.source.update": "更新配置",
|
||||
"agents.history.source.default": "默认配置",
|
||||
"agents.history.source.clear": "清空配置",
|
||||
"agents.history.source.restore": "恢复配置",
|
||||
"agents.history.source.sync": "自动同步",
|
||||
"agents.history.source.legacy": "旧版备份",
|
||||
'agents.title': '智能体配置',
|
||||
'agents.embedded.title': '选择客户端和模型后启动',
|
||||
'agents.embedded.subtitle': '如果没有客户端请自行安装',
|
||||
@@ -936,10 +957,10 @@ export const zhCN = {
|
||||
'agents.pi.uninstall': '卸载 Pi 插件',
|
||||
'agents.pi.uninstalling': '正在卸载插件',
|
||||
'agents.default.title': '重置为默认配置',
|
||||
'agents.default.description': '这会使用当前选中模型重写 {name} 的全部相关配置文件,并删除其中其他用户内容。该操作没有备份且无法恢复。',
|
||||
'agents.default.description': '这会使用当前选中模型重写 {name} 的全部相关配置文件,并删除其中其他用户内容。操作前会保存历史备份;历史恢复仅恢复软件管理的字段,不能恢复被清除的其他自定义内容。',
|
||||
'agents.default.confirm': '确认写入默认配置',
|
||||
'agents.clear.title': '清空 Codex 配置',
|
||||
'agents.clear.description': '这会永久删除 Codex 配置目录中的 auth.json 和 config.toml,并重置软件记录的应用状态。该操作没有备份且无法恢复。',
|
||||
'agents.clear.description': '这会永久删除 Codex 配置目录中的 auth.json 和 config.toml。操作前会保存历史备份;历史恢复仅恢复软件管理的字段,不能恢复被清除的其他自定义内容。',
|
||||
'agents.clear.confirm': '确认清空配置',
|
||||
'agents.clear.success': 'Codex 配置已清空。',
|
||||
'apiAccess.compatibleName': 'OpenAI 兼容 {number}',
|
||||
|
||||
@@ -25,7 +25,7 @@ export type CodexModelConfiguration = {
|
||||
export type CodexCatalogEditorModel = {
|
||||
slug: string;
|
||||
hasOfficialTemplate: boolean;
|
||||
contextSource: "definition" | "configuration" | "compatibility" | "template";
|
||||
contextSource: "client" | "configuration" | "template";
|
||||
customized: boolean;
|
||||
configuration: CodexModelConfiguration;
|
||||
defaults: CodexModelConfiguration;
|
||||
|
||||
@@ -28,18 +28,18 @@ const configuration = (): CodexModelConfiguration => ({
|
||||
});
|
||||
|
||||
describe('Codex 模型列表编辑', () => {
|
||||
test("区分内核模型定义与兼容目录的后备上下文", () => {
|
||||
test("区分 Codex 客户端 API、模板后备与自定义上下文", () => {
|
||||
const model: CodexCatalogEditorModel = {
|
||||
slug: "model-a", hasOfficialTemplate: true, customized: false,
|
||||
contextSource: "definition", configuration: configuration(), defaults: configuration(),
|
||||
contextSource: "client", configuration: configuration(), defaults: configuration(),
|
||||
};
|
||||
expect(codexContextSourceHint(model)).toBe("agents.catalog.contextSource.definition");
|
||||
expect(codexContextSourceHint({ ...model, contextSource: "compatibility" }))
|
||||
.toBe("agents.catalog.contextSource.compatibility");
|
||||
expect(codexContextSourceHint(model)).toBe("agents.catalog.contextSource.client");
|
||||
expect(codexContextSourceHint({ ...model, contextSource: "template" }))
|
||||
.toBe("agents.catalog.contextSource.template");
|
||||
model.configuration.context_window = 64_000;
|
||||
expect(codexContextSourceHint(model)).toBe("agents.catalog.contextSource.customized");
|
||||
model.configuration = cloneCodexModelConfiguration(model.defaults);
|
||||
expect(codexContextSourceHint(model)).toBe("agents.catalog.contextSource.definition");
|
||||
expect(codexContextSourceHint(model)).toBe("agents.catalog.contextSource.client");
|
||||
});
|
||||
|
||||
test('克隆配置时隔离数组字段', () => {
|
||||
|
||||
Reference in new issue
Block a user