mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-07-02 02:22:55 +08:00
190 lines
5.3 KiB
TypeScript
190 lines
5.3 KiB
TypeScript
import { readFile } from 'node:fs/promises';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
|
|
import TOML from '@iarna/toml';
|
|
|
|
import type { IProviderModels } from '@/shared/interfaces.js';
|
|
import type {
|
|
ProviderChangeActiveModelInput,
|
|
ProviderCurrentActiveModel,
|
|
ProviderModelOption,
|
|
ProviderModelsDefinition,
|
|
ProviderSessionActiveModelChange,
|
|
} from '@/shared/types.js';
|
|
import {
|
|
buildDefaultProviderCurrentActiveModel,
|
|
readObjectRecord,
|
|
readOptionalString,
|
|
writeProviderSessionActiveModelChange,
|
|
} from '@/shared/utils.js';
|
|
|
|
export const CODEX_FALLBACK_MODELS: ProviderModelsDefinition = {
|
|
OPTIONS: [
|
|
{
|
|
value: 'gpt-5.5',
|
|
label: 'gpt-5.5',
|
|
effort: {
|
|
default: 'medium',
|
|
values: [{ value: 'low' }, { value: 'medium' }, { value: 'high' }, { value: 'xhigh' }],
|
|
},
|
|
},
|
|
{
|
|
value: 'gpt-5.4',
|
|
label: 'gpt-5.4',
|
|
effort: {
|
|
default: 'medium',
|
|
values: [{ value: 'low' }, { value: 'medium' }, { value: 'high' }, { value: 'xhigh' }],
|
|
},
|
|
},
|
|
{
|
|
value: 'gpt-5.4-mini',
|
|
label: 'gpt-5.4-mini',
|
|
effort: {
|
|
default: 'medium',
|
|
values: [{ value: 'low' }, { value: 'medium' }, { value: 'high' }, { value: 'xhigh' }],
|
|
},
|
|
},
|
|
{
|
|
value: 'gpt-5.3-codex',
|
|
label: 'gpt-5.3-codex',
|
|
effort: {
|
|
default: 'medium',
|
|
values: [{ value: 'low' }, { value: 'medium' }, { value: 'high' }, { value: 'xhigh' }],
|
|
},
|
|
},
|
|
{
|
|
value: 'gpt-5.2',
|
|
label: 'gpt-5.2',
|
|
effort: {
|
|
default: 'medium',
|
|
values: [{ value: 'low' }, { value: 'medium' }, { value: 'high' }, { value: 'xhigh' }],
|
|
},
|
|
},
|
|
],
|
|
DEFAULT: 'gpt-5.4',
|
|
};
|
|
|
|
type CodexCachedModel = {
|
|
slug?: string;
|
|
display_name?: string;
|
|
description?: string;
|
|
priority?: number;
|
|
visibility?: string;
|
|
supported_in_api?: boolean;
|
|
default_reasoning_level?: string;
|
|
supported_reasoning_levels?: Array<{
|
|
effort?: string;
|
|
description?: string;
|
|
}>;
|
|
};
|
|
|
|
const CODEX_MODELS_CACHE_PATH = path.join(os.homedir(), '.codex', 'models_cache.json');
|
|
const CODEX_CONFIG_PATH = path.join(os.homedir(), '.codex', 'config.toml');
|
|
|
|
const isCodexCachedModel = (value: unknown): value is CodexCachedModel => {
|
|
const record = readObjectRecord(value);
|
|
return Boolean(record && readOptionalString(record.slug));
|
|
};
|
|
|
|
const readCodexPriority = (value: unknown): number => (
|
|
typeof value === 'number' && Number.isFinite(value) ? value : Number.MAX_SAFE_INTEGER
|
|
);
|
|
|
|
const mapCodexModel = (model: CodexCachedModel): ProviderModelOption => {
|
|
const effortValues = Array.isArray(model.supported_reasoning_levels)
|
|
? model.supported_reasoning_levels
|
|
.map((level) => {
|
|
const value = readOptionalString(level?.effort);
|
|
if (!value) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
value,
|
|
description: readOptionalString(level?.description),
|
|
};
|
|
})
|
|
.filter((level): level is NonNullable<typeof level> => Boolean(level))
|
|
: [];
|
|
|
|
return {
|
|
value: model.slug as string,
|
|
label: readOptionalString(model.display_name) ?? (model.slug as string),
|
|
description: readOptionalString(model.description),
|
|
effort: effortValues.length > 0
|
|
? {
|
|
default: readOptionalString(model.default_reasoning_level) ?? undefined,
|
|
values: effortValues,
|
|
}
|
|
: undefined,
|
|
};
|
|
};
|
|
|
|
const buildCodexModelsDefinition = (models: CodexCachedModel[]): ProviderModelsDefinition => {
|
|
const sortedModels = [...models]
|
|
.filter((model) => model.visibility === 'list' && model.supported_in_api !== false)
|
|
.sort((left, right) => readCodexPriority(left.priority) - readCodexPriority(right.priority));
|
|
|
|
const options: ProviderModelOption[] = [];
|
|
const seenValues = new Set<string>();
|
|
|
|
for (const model of sortedModels) {
|
|
const mappedModel = mapCodexModel(model);
|
|
if (seenValues.has(mappedModel.value)) {
|
|
continue;
|
|
}
|
|
|
|
seenValues.add(mappedModel.value);
|
|
options.push(mappedModel);
|
|
}
|
|
|
|
if (options.length === 0) {
|
|
return CODEX_FALLBACK_MODELS;
|
|
}
|
|
|
|
return {
|
|
OPTIONS: options,
|
|
DEFAULT: options[0]?.value ?? CODEX_FALLBACK_MODELS.DEFAULT,
|
|
};
|
|
};
|
|
|
|
export class CodexProviderModels implements IProviderModels {
|
|
async getSupportedModels(): Promise<ProviderModelsDefinition> {
|
|
try {
|
|
const raw = await readFile(CODEX_MODELS_CACHE_PATH, 'utf8');
|
|
const parsed = readObjectRecord(JSON.parse(raw));
|
|
const models = Array.isArray(parsed?.models)
|
|
? parsed.models.filter(isCodexCachedModel)
|
|
: [];
|
|
|
|
return buildCodexModelsDefinition(models);
|
|
} catch {
|
|
return CODEX_FALLBACK_MODELS;
|
|
}
|
|
}
|
|
|
|
async getCurrentActiveModel(): Promise<ProviderCurrentActiveModel> {
|
|
try {
|
|
const raw = await readFile(CODEX_CONFIG_PATH, 'utf8');
|
|
const parsed = readObjectRecord(TOML.parse(raw));
|
|
const model = readOptionalString(parsed?.model);
|
|
if (!model) {
|
|
return buildDefaultProviderCurrentActiveModel(await this.getSupportedModels());
|
|
}
|
|
|
|
return {
|
|
model,
|
|
};
|
|
} catch {
|
|
return buildDefaultProviderCurrentActiveModel(await this.getSupportedModels());
|
|
}
|
|
}
|
|
|
|
async changeActiveModel(
|
|
input: ProviderChangeActiveModelInput,
|
|
): Promise<ProviderSessionActiveModelChange> {
|
|
return writeProviderSessionActiveModelChange('codex', input);
|
|
}
|
|
}
|