fix(codex): load context defaults from CPA model definitions
- Resolve context limits from core model definitions, aliases, and configuration - Preserve user overrides and display context sources and fallback hints - Refresh defaults from CPA when restoring all model settings - Keep restore-all available without customizations and remove its icon - Allow saving restored defaults even when values remain unchanged - Add regression coverage for context resolution and default restoration
This commit is contained in:
1 parent
ce9944c316
commit
40ba85d6f0
10 files changed
+452
-66
No files matched your search
+10
-5
@@ -65,7 +65,7 @@ export const jaOverrides = {
|
||||
'config.software.terminal.auto': '自動選択',
|
||||
'agents.catalog.button': 'モデル一覧の設定',
|
||||
'agents.catalog.title': 'Codex モデル一覧',
|
||||
'agents.catalog.subtitle': '利用可能なモデルのパラメーターを編集し、生成される Codex モデル一覧に保存します。',
|
||||
'agents.catalog.subtitle': 'コンテキストの既定値は CPA コア API から取得します。モデルのパラメーターを編集し、Codex モデル一覧に保存できます。',
|
||||
'agents.catalog.search': 'モデル名または ID を検索',
|
||||
'agents.catalog.official': '公式テンプレート',
|
||||
'agents.catalog.fallback': '標準テンプレート',
|
||||
@@ -76,6 +76,11 @@ export const jaOverrides = {
|
||||
'agents.catalog.displayName': '表示名',
|
||||
'agents.catalog.description': 'モデルの説明',
|
||||
'agents.catalog.context': 'コンテキストウィンドウ(トークン)',
|
||||
'agents.catalog.contextSource.definition': 'コンテキストの取得元:CPA コア 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': '最大コンテキストウィンドウ(トークン)',
|
||||
'agents.catalog.percent': '有効コンテキスト比率(%)',
|
||||
'agents.catalog.compact': '自動圧縮のしきい値(トークン)',
|
||||
@@ -89,10 +94,10 @@ export const jaOverrides = {
|
||||
'agents.catalog.visible': 'Codex のモデル一覧に表示',
|
||||
'agents.catalog.parallel': '並列ツール呼び出しを許可',
|
||||
'agents.catalog.capabilityHint': 'Fast は常に選択可能です。推論・画像・ツール機能の動作は上流モデルの対応状況によります。',
|
||||
'agents.catalog.resetModel': 'このテンプレートを復元',
|
||||
'agents.catalog.resetAll': 'すべて公式テンプレートに復元',
|
||||
'agents.catalog.resetHint': '専用テンプレートがあるモデルは公式設定に、それ以外はアプリの標準設定に戻ります。保存すると反映されます。',
|
||||
'agents.catalog.restored': '下書きをテンプレートに戻しました。保存すると反映されます。',
|
||||
'agents.catalog.resetModel': 'このモデルの既定値に戻す',
|
||||
'agents.catalog.resetAll': 'すべて既定値に戻す',
|
||||
'agents.catalog.resetHint': 'コンテキストは CPA コア API の値を既定値とし、有効なデータがない場合のみテンプレートの値を使用します。その他の項目はモデルのテンプレートに戻ります。保存後に適用されます。',
|
||||
'agents.catalog.restored': '下書きを既定値に戻しました。保存すると適用されます。',
|
||||
'agents.catalog.saved': '保存しました。管理対象の一覧が同期されます。Codex を再起動して変更を読み込んでください。',
|
||||
'agents.catalog.syncFailed': '設定は保存しましたが、一覧の同期に失敗しました:{error}',
|
||||
'agents.catalog.changed': 'モデル一覧またはテンプレートが更新されました。再読み込みしてから編集してください。',
|
||||
|
||||
+10
-5
@@ -15,7 +15,7 @@ export const en: Record<MessageKey, string> = {
|
||||
'usage.unit.tokens': 'Token',
|
||||
'agents.catalog.button': 'Model list settings',
|
||||
'agents.catalog.title': 'Codex model catalog',
|
||||
'agents.catalog.subtitle': 'Customize parameters for currently available models and save them to the generated Codex catalog.',
|
||||
'agents.catalog.subtitle': 'Default context settings come from the CPA core API. Customize model parameters and save them to the Codex catalog.',
|
||||
'agents.catalog.search': 'Search model names or IDs',
|
||||
'agents.catalog.official': 'Official template',
|
||||
'agents.catalog.fallback': 'Default template',
|
||||
@@ -26,6 +26,11 @@ 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.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)',
|
||||
'agents.catalog.percent': 'Effective context (%)',
|
||||
'agents.catalog.compact': 'Auto-compaction threshold (tokens)',
|
||||
@@ -39,10 +44,10 @@ export const en: Record<MessageKey, string> = {
|
||||
'agents.catalog.visible': 'Show in the Codex model list',
|
||||
'agents.catalog.parallel': 'Allow parallel tool calls',
|
||||
'agents.catalog.capabilityHint': 'Fast is always available. Reasoning, image and tool capabilities depend on the upstream model.',
|
||||
'agents.catalog.resetModel': 'Restore this template',
|
||||
'agents.catalog.resetAll': 'Restore all official templates',
|
||||
'agents.catalog.resetHint': 'Models with dedicated templates return to official settings; other models return to application defaults. Save to apply.',
|
||||
'agents.catalog.restored': 'Templates restored in the draft. Save to apply.',
|
||||
'agents.catalog.resetModel': 'Restore model defaults',
|
||||
'agents.catalog.resetAll': 'Restore all defaults',
|
||||
'agents.catalog.resetHint': 'Context windows default to CPA core API values, with template values used only when valid data is missing. Other parameters revert to the model template. Save to apply.',
|
||||
'agents.catalog.restored': 'Defaults restored in the draft. Save to apply.',
|
||||
'agents.catalog.saved': 'Saved. Managed catalogs are synchronized; restart Codex to load the changes.',
|
||||
'agents.catalog.syncFailed': 'Settings saved, but catalog synchronization failed: {error}',
|
||||
'agents.catalog.changed': 'The model list or templates have changed. Reload before editing again.',
|
||||
|
||||
@@ -14,7 +14,7 @@ export const zhCN = {
|
||||
'usage.unit.tokens': 'Token',
|
||||
'agents.catalog.button': '模型列表设置',
|
||||
'agents.catalog.title': 'Codex 模型列表',
|
||||
'agents.catalog.subtitle': '自定义当前可用模型的参数,保存后用于生成 Codex 模型列表。',
|
||||
'agents.catalog.subtitle': '默认上下文参数读取自 CPA 内核 API;可自定义当前模型参数并保存到 Codex 模型列表。',
|
||||
'agents.catalog.search': '搜索模型名称或 ID',
|
||||
'agents.catalog.official': '官方模板',
|
||||
'agents.catalog.fallback': '默认模板',
|
||||
@@ -25,6 +25,11 @@ export const zhCN = {
|
||||
'agents.catalog.displayName': '显示名称',
|
||||
'agents.catalog.description': '模型说明',
|
||||
'agents.catalog.context': '上下文窗口(tokens)',
|
||||
'agents.catalog.contextSource.definition': '上下文来源:CPA 内核的模型定义 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)',
|
||||
'agents.catalog.percent': '有效上下文比例(%)',
|
||||
'agents.catalog.compact': '自动压缩阈值(tokens)',
|
||||
@@ -38,10 +43,10 @@ export const zhCN = {
|
||||
'agents.catalog.visible': '在 Codex 模型列表中显示',
|
||||
'agents.catalog.parallel': '允许并行工具调用',
|
||||
'agents.catalog.capabilityHint': 'Fast 始终可选;思考等级、图片与工具能力是否生效取决于上游模型。',
|
||||
'agents.catalog.resetModel': '恢复此模型模板',
|
||||
'agents.catalog.resetAll': '全部恢复官方模板',
|
||||
'agents.catalog.resetHint': '有专属模板的模型恢复官方配置,其余恢复软件默认配置。恢复操作保存后生效。',
|
||||
'agents.catalog.restored': '模板已恢复到草稿,点击保存后生效。',
|
||||
'agents.catalog.resetModel': '恢复此模型默认值',
|
||||
'agents.catalog.resetAll': '全部恢复默认值',
|
||||
'agents.catalog.resetHint': '上下文窗口默认使用 CPA 内核 API 返回值,仅在缺少有效数据时使用模板值;其他参数恢复模型模板。保存后生效。',
|
||||
'agents.catalog.restored': '默认值已恢复到草稿,点击保存后生效。',
|
||||
'agents.catalog.saved': '已保存。已托管的模型目录会同步更新,重启 Codex 后加载新配置。',
|
||||
'agents.catalog.syncFailed': '设置已保存,但目录同步失败:{error}',
|
||||
'agents.catalog.changed': '模型列表或模板已更新,请重新加载后再编辑。',
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Check, LoaderCircle, RefreshCw, RotateCcw, Search, X } from 'lucide-rea
|
||||
import { useI18n } from '../i18n';
|
||||
import {
|
||||
cloneCodexModelConfiguration,
|
||||
codexContextSourceHint,
|
||||
codexReasoningEfforts,
|
||||
sameCodexModelConfiguration,
|
||||
toggleCodexReasoningLevel,
|
||||
@@ -36,23 +37,29 @@ export function CodexModelCatalogDialog({ onClose, onSaved }: CodexModelCatalogD
|
||||
const [search, setSearch] = useState('');
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [defaultsRestored, setDefaultsRestored] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const [notice, setNotice] = useState('');
|
||||
const [discardConfirmOpen, setDiscardConfirmOpen] = useState(false);
|
||||
|
||||
const load = useCallback(async () => {
|
||||
const load = useCallback(async (restoreDefaults = false) => {
|
||||
setLoading(true);
|
||||
setError('');
|
||||
setNotice('');
|
||||
try {
|
||||
const next = await invoke<CodexCatalogEditorSnapshot>('get_codex_model_catalog_editor');
|
||||
setSnapshot(next);
|
||||
setModels(cloneModels(next));
|
||||
setModels(cloneModels(next).map((model) => restoreDefaults
|
||||
? { ...model, configuration: cloneCodexModelConfiguration(model.defaults) }
|
||||
: model));
|
||||
setDefaultsRestored(restoreDefaults);
|
||||
setSelectedSlug((current) => next.models.some((model) => model.slug === current)
|
||||
? current
|
||||
: next.models[0]?.slug ?? '');
|
||||
return true;
|
||||
} catch (requestError) {
|
||||
setError(String(requestError));
|
||||
return false;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -63,12 +70,13 @@ export function CodexModelCatalogDialog({ onClose, onSaved }: CodexModelCatalogD
|
||||
}, [load]);
|
||||
|
||||
const dirty = useMemo(() => {
|
||||
if (defaultsRestored) return true;
|
||||
if (!snapshot || snapshot.models.length !== models.length) return false;
|
||||
return models.some((model) => {
|
||||
const saved = snapshot.models.find((candidate) => candidate.slug === model.slug);
|
||||
return !saved || !sameCodexModelConfiguration(model.configuration, saved.configuration);
|
||||
});
|
||||
}, [models, snapshot]);
|
||||
}, [defaultsRestored, models, snapshot]);
|
||||
|
||||
const filteredModels = useMemo(() => {
|
||||
const query = search.trim().toLocaleLowerCase();
|
||||
@@ -130,17 +138,13 @@ export function CodexModelCatalogDialog({ onClose, onSaved }: CodexModelCatalogD
|
||||
setNotice(t('agents.catalog.restored'));
|
||||
};
|
||||
|
||||
const restoreAll = () => {
|
||||
setModels((current) => current.map((model) => ({
|
||||
...model,
|
||||
configuration: cloneCodexModelConfiguration(model.defaults),
|
||||
})));
|
||||
setError('');
|
||||
setNotice(t('agents.catalog.restored'));
|
||||
const restoreAll = async () => {
|
||||
if (loading || saving) return;
|
||||
if (await load(true)) setNotice(t('agents.catalog.restored'));
|
||||
};
|
||||
|
||||
const save = async () => {
|
||||
if (!snapshot || saving) return;
|
||||
if (!snapshot || loading || saving) return;
|
||||
for (const model of models) {
|
||||
const validationKey = validateCodexModelConfiguration(model.configuration);
|
||||
if (validationKey) {
|
||||
@@ -165,6 +169,7 @@ export function CodexModelCatalogDialog({ onClose, onSaved }: CodexModelCatalogD
|
||||
});
|
||||
setSnapshot(result.snapshot);
|
||||
setModels(cloneModels(result.snapshot));
|
||||
setDefaultsRestored(false);
|
||||
setNotice(result.synchronizationError
|
||||
? t('agents.catalog.syncFailed', { error: result.synchronizationError })
|
||||
: t('agents.catalog.saved'));
|
||||
@@ -231,7 +236,7 @@ export function CodexModelCatalogDialog({ onClose, onSaved }: CodexModelCatalogD
|
||||
</aside>
|
||||
|
||||
<main className="codex-catalog-editor">
|
||||
{activeModel ? (
|
||||
{activeModel && !loading ? (
|
||||
<>
|
||||
<div className="codex-catalog-model-heading">
|
||||
<div>
|
||||
@@ -276,6 +281,7 @@ export function CodexModelCatalogDialog({ onClose, onSaved }: CodexModelCatalogD
|
||||
<label className="codex-catalog-switch"><input type="checkbox" checked={activeModel.configuration.visibility === 'list'} onChange={(event) => updateField('visibility', event.currentTarget.checked ? 'list' : 'hide')} /><span>{t('agents.catalog.visible')}</span></label>
|
||||
<label className="codex-catalog-switch"><input type="checkbox" checked={activeModel.configuration.supports_parallel_tool_calls} onChange={(event) => updateField('supports_parallel_tool_calls', event.currentTarget.checked)} /><span>{t('agents.catalog.parallel')}</span></label>
|
||||
</div>
|
||||
<p className="codex-catalog-hint" role="note">{t(codexContextSourceHint(activeModel))}</p>
|
||||
<p className="codex-catalog-hint">{t('agents.catalog.capabilityHint')}</p>
|
||||
</>
|
||||
) : !loading ? <div className="codex-catalog-state">{t('agents.catalog.empty')}</div> : null}
|
||||
@@ -289,11 +295,11 @@ export function CodexModelCatalogDialog({ onClose, onSaved }: CodexModelCatalogD
|
||||
{!error && !notice ? <span>{dirty ? t('agents.catalog.unsaved') : t('agents.catalog.saveHint')}</span> : null}
|
||||
</div>
|
||||
<div>
|
||||
<button type="button" className="secondary-button" onClick={restoreAll} disabled={!models.some((model) => !sameCodexModelConfiguration(model.configuration, model.defaults)) || saving}>
|
||||
<RotateCcw size={15} />{t('agents.catalog.resetAll')}
|
||||
<button type="button" className="secondary-button" onClick={() => void restoreAll()} disabled={loading || saving}>
|
||||
{t('agents.catalog.resetAll')}
|
||||
</button>
|
||||
<button type="button" className="secondary-button" onClick={requestClose} disabled={saving}>{t('common.cancel')}</button>
|
||||
<button type="button" className="primary-button" onClick={() => void save()} disabled={!snapshot || !dirty || saving}>
|
||||
<button type="button" className="primary-button" onClick={() => void save()} disabled={!snapshot || !dirty || loading || saving}>
|
||||
{saving ? <LoaderCircle size={16} className="spin" /> : null}{saving ? t('common.saving') : t('common.save')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -25,6 +25,7 @@ export type CodexModelConfiguration = {
|
||||
export type CodexCatalogEditorModel = {
|
||||
slug: string;
|
||||
hasOfficialTemplate: boolean;
|
||||
contextSource: "definition" | "configuration" | "compatibility" | "template";
|
||||
customized: boolean;
|
||||
configuration: CodexModelConfiguration;
|
||||
defaults: CodexModelConfiguration;
|
||||
@@ -35,6 +36,14 @@ export type CodexCatalogEditorSnapshot = {
|
||||
models: CodexCatalogEditorModel[];
|
||||
};
|
||||
|
||||
export function codexContextSourceHint(model: CodexCatalogEditorModel): MessageKey {
|
||||
if (model.configuration.context_window !== model.defaults.context_window
|
||||
|| model.configuration.max_context_window !== model.defaults.max_context_window) {
|
||||
return "agents.catalog.contextSource.customized";
|
||||
}
|
||||
return `agents.catalog.contextSource.${model.contextSource ?? "template"}`;
|
||||
}
|
||||
|
||||
export type CodexCatalogEditorSaveResult = {
|
||||
snapshot: CodexCatalogEditorSnapshot;
|
||||
synchronizationError: string | null;
|
||||
|
||||
Reference in new issue
Block a user