feat(codex): add customizable model catalog editor
- add per-model context, reasoning, modality, visibility, and parallel tool settings - persist customizations separately and apply them during catalog generation - support restoring individual or all models to official fallback templates - expose full fallback reasoning levels while keeping Fast available - add localized UI, validation, documentation, and regression tests
This commit is contained in:
1 parent
c370c17ff0
commit
673a322c00
13 files changed
+1671
-8
No files matched your search
@@ -0,0 +1,108 @@
|
||||
import type { MessageKey } from '../i18n/resources';
|
||||
|
||||
export const codexReasoningEfforts = ['none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max', 'ultra'] as const;
|
||||
export type CodexReasoningEffort = typeof codexReasoningEfforts[number];
|
||||
|
||||
export type CodexReasoningLevel = {
|
||||
effort: CodexReasoningEffort;
|
||||
description: string;
|
||||
};
|
||||
|
||||
export type CodexModelConfiguration = {
|
||||
display_name: string;
|
||||
description: string | null;
|
||||
context_window: number;
|
||||
max_context_window: number;
|
||||
effective_context_window_percent: number;
|
||||
auto_compact_token_limit: number | null;
|
||||
default_reasoning_level: CodexReasoningEffort | null;
|
||||
supported_reasoning_levels: CodexReasoningLevel[];
|
||||
input_modalities: Array<'text' | 'image'>;
|
||||
visibility: 'list' | 'hide' | 'none';
|
||||
supports_parallel_tool_calls: boolean;
|
||||
};
|
||||
|
||||
export type CodexCatalogEditorModel = {
|
||||
slug: string;
|
||||
hasOfficialTemplate: boolean;
|
||||
customized: boolean;
|
||||
configuration: CodexModelConfiguration;
|
||||
defaults: CodexModelConfiguration;
|
||||
};
|
||||
|
||||
export type CodexCatalogEditorSnapshot = {
|
||||
revision: string;
|
||||
models: CodexCatalogEditorModel[];
|
||||
};
|
||||
|
||||
export type CodexCatalogEditorSaveResult = {
|
||||
snapshot: CodexCatalogEditorSnapshot;
|
||||
synchronizationError: string | null;
|
||||
};
|
||||
|
||||
export function cloneCodexModelConfiguration(configuration: CodexModelConfiguration): CodexModelConfiguration {
|
||||
return {
|
||||
...configuration,
|
||||
supported_reasoning_levels: configuration.supported_reasoning_levels.map((level) => ({ ...level })),
|
||||
input_modalities: [...configuration.input_modalities],
|
||||
};
|
||||
}
|
||||
|
||||
export function sameCodexModelConfiguration(left: CodexModelConfiguration, right: CodexModelConfiguration): boolean {
|
||||
return left.display_name === right.display_name
|
||||
&& left.description === right.description
|
||||
&& Object.is(left.context_window, right.context_window)
|
||||
&& Object.is(left.max_context_window, right.max_context_window)
|
||||
&& Object.is(left.effective_context_window_percent, right.effective_context_window_percent)
|
||||
&& Object.is(left.auto_compact_token_limit, right.auto_compact_token_limit)
|
||||
&& left.default_reasoning_level === right.default_reasoning_level
|
||||
&& left.visibility === right.visibility
|
||||
&& left.supports_parallel_tool_calls === right.supports_parallel_tool_calls
|
||||
&& left.input_modalities.join(',') === right.input_modalities.join(',')
|
||||
&& left.supported_reasoning_levels.length === right.supported_reasoning_levels.length
|
||||
&& left.supported_reasoning_levels.every((level, index) => level.effort === right.supported_reasoning_levels[index].effort
|
||||
&& level.description === right.supported_reasoning_levels[index].description);
|
||||
}
|
||||
|
||||
export function toggleCodexReasoningLevel(
|
||||
configuration: CodexModelConfiguration,
|
||||
effort: CodexReasoningEffort,
|
||||
enabled: boolean,
|
||||
defaults: CodexReasoningLevel[],
|
||||
): CodexModelConfiguration {
|
||||
const levels = new Map(configuration.supported_reasoning_levels.map((level) => [level.effort, level]));
|
||||
if (enabled) {
|
||||
levels.set(effort, levels.get(effort) ?? defaults.find((level) => level.effort === effort)
|
||||
?? { effort, description: `${effort} reasoning effort` });
|
||||
} else {
|
||||
levels.delete(effort);
|
||||
}
|
||||
const supported = codexReasoningEfforts.flatMap((level) => levels.has(level) ? [{ ...levels.get(level)! }] : []);
|
||||
const currentDefault = configuration.default_reasoning_level;
|
||||
const nextDefault = currentDefault && !levels.has(currentDefault)
|
||||
? levels.has('medium') ? 'medium' : supported[0]?.effort ?? null
|
||||
: currentDefault;
|
||||
return { ...configuration, supported_reasoning_levels: supported, default_reasoning_level: nextDefault };
|
||||
}
|
||||
|
||||
export function validateCodexModelConfiguration(configuration: CodexModelConfiguration): MessageKey | null {
|
||||
if (!configuration.display_name.trim() || configuration.display_name.length > 4_000
|
||||
|| (configuration.description?.length ?? 0) > 4_000) return 'agents.catalog.invalidText';
|
||||
if (![configuration.context_window, configuration.max_context_window].every((value) => Number.isSafeInteger(value) && value > 0)) {
|
||||
return 'agents.catalog.invalidContext';
|
||||
}
|
||||
if (configuration.max_context_window < configuration.context_window) return 'agents.catalog.invalidMaximum';
|
||||
const percent = configuration.effective_context_window_percent;
|
||||
if (!Number.isInteger(percent) || percent < 1 || percent > 100) return 'agents.catalog.invalidPercent';
|
||||
const compact = configuration.auto_compact_token_limit;
|
||||
if (compact !== null && (!Number.isSafeInteger(compact) || compact < 1 || compact > configuration.context_window)) {
|
||||
return 'agents.catalog.invalidCompact';
|
||||
}
|
||||
const levels = configuration.supported_reasoning_levels.map((level) => level.effort);
|
||||
if (new Set(levels).size !== levels.length || levels.some((level) => !codexReasoningEfforts.includes(level))
|
||||
|| (configuration.default_reasoning_level !== null && !levels.includes(configuration.default_reasoning_level))) {
|
||||
return 'agents.catalog.invalidReasoning';
|
||||
}
|
||||
if (!configuration.input_modalities.length) return 'agents.catalog.invalidModalities';
|
||||
return null;
|
||||
}
|
||||
Reference in new issue
Block a user