@@ -2,12 +2,11 @@ import { copyFileSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFil
22import { homedir } from "node:os" ;
33import { join } from "node:path" ;
44import { atomicWriteFile } from "./config" ;
5+ import { CODEX_CONFIG_PATH , CODEX_MODELS_CACHE_PATH , DEFAULT_CATALOG_PATH , readRootTomlString , resolveCodexConfigPath } from "./codex-paths" ;
56import { DEFAULT_MODEL_CACHE_TTL_MS , getFreshCached , getStaleCached , setCached } from "./model-cache" ;
67import { buildModelsRequest , resolveModelsAuthToken } from "./oauth/index" ;
78import type { OcxConfig , OcxProviderConfig } from "./types" ;
89
9- const CODEX_CONFIG_PATH = join ( homedir ( ) , ".codex" , "config.toml" ) ;
10- const DEFAULT_CATALOG_PATH = join ( homedir ( ) , ".codex" , "opencodex-catalog.json" ) ;
1110const OCX_DIR = join ( homedir ( ) , ".opencodex" ) ;
1211const CATALOG_BACKUP_PATH = join ( OCX_DIR , "catalog-backup.json" ) ;
1312
@@ -40,8 +39,8 @@ export function readCodexCatalogPath(): string {
4039 try {
4140 if ( existsSync ( CODEX_CONFIG_PATH ) ) {
4241 const toml = readFileSync ( CODEX_CONFIG_PATH , "utf-8" ) ;
43- const m = toml . match ( / ^ \s * m o d e l _ c a t a l o g _ j s o n \s * = \s * " ( [ ^ " ] + ) " / m ) ;
44- if ( m ) return m [ 1 ] ;
42+ const path = readRootTomlString ( toml , " model_catalog_json" ) ;
43+ if ( path ) return resolveCodexConfigPath ( path ) ;
4544 }
4645 } catch { /* ignore */ }
4746 return DEFAULT_CATALOG_PATH ;
@@ -55,13 +54,36 @@ function readCatalog(path: string): { models?: RawEntry[]; [k: string]: unknown
5554 } catch { return null ; }
5655}
5756
57+ function normalizeServiceTiers ( entry : RawEntry ) : RawEntry {
58+ if ( entry . service_tier === "priority" ) entry . service_tier = "fast" ;
59+ if ( Array . isArray ( entry . service_tiers ) ) {
60+ entry . service_tiers = entry . service_tiers . map ( tier => {
61+ if ( tier && typeof tier === "object" && "id" in tier && tier . id === "priority" ) {
62+ return { ...tier , id : "fast" } ;
63+ }
64+ return tier ;
65+ } ) ;
66+ }
67+ return entry ;
68+ }
69+
70+ function loadCatalogForSync ( path : string ) : { models ?: RawEntry [ ] ; [ k : string ] : unknown } | null {
71+ const catalog = readCatalog ( path ) ;
72+ if ( catalog ) return catalog ;
73+ return readCatalog ( CODEX_MODELS_CACHE_PATH ) ;
74+ }
75+
76+ function readCurrentCatalogOrCache ( ) : { models ?: RawEntry [ ] ; [ k : string ] : unknown } | null {
77+ return readCatalog ( readCodexCatalogPath ( ) ) ?? readCatalog ( CODEX_MODELS_CACHE_PATH ) ;
78+ }
79+
5880/**
5981 * A full native entry from the on-disk catalog, used as a clone template so injected
6082 * entries carry EVERY field Codex's strict parser requires (e.g. `base_instructions`).
6183 * Returns a deep copy, or null if no catalog/native entry exists.
6284 */
6385export function loadCatalogTemplate ( ) : RawEntry | null {
64- const cat = readCatalog ( readCodexCatalogPath ( ) ) ;
86+ const cat = readCurrentCatalogOrCache ( ) ;
6587 const native = cat ?. models ?. find (
6688 m => typeof m . slug === "string" && ! m . slug . includes ( "/" ) && "base_instructions" in m ,
6789 ) ;
@@ -109,16 +131,16 @@ function deriveEntry(template: RawEntry | null, slug: string, desc: string, prio
109131 e . supported_reasoning_levels = ROUTED_REASONING_LEVELS . map ( l => byEffort . get ( l . effort ) ?? { ...l } ) ;
110132 e . default_reasoning_level = "medium" ;
111133 }
112- return e ;
134+ return normalizeServiceTiers ( e ) ;
113135 }
114136 // Fallback when no template is available (best-effort; strict parser may need more).
115- return {
137+ return normalizeServiceTiers ( {
116138 slug, display_name : slug , description : desc ,
117139 default_reasoning_level : "medium" ,
118140 supported_reasoning_levels : ROUTED_REASONING_LEVELS . map ( l => ( { ...l } ) ) ,
119141 shell_type : "shell_command" , visibility : "list" , supported_in_api : true ,
120142 priority, base_instructions : "You are a helpful coding assistant." ,
121- } ;
143+ } ) ;
122144}
123145
124146/**
@@ -149,7 +171,7 @@ export function buildCatalogEntries(template: RawEntry | null, gptSlugs: string[
149171
150172/** Bare picker-visible native slugs in the live Codex catalog (drives the subagent picker UI). */
151173export function listCatalogNativeSlugs ( ) : string [ ] {
152- const cat = readCatalog ( readCodexCatalogPath ( ) ) ;
174+ const cat = readCurrentCatalogOrCache ( ) ;
153175 return ( cat ?. models ?? [ ] )
154176 . filter ( m => typeof m . slug === "string" && ! ( m . slug as string ) . includes ( "/" ) && m . visibility === "list" )
155177 . map ( m => m . slug as string ) ;
@@ -244,7 +266,7 @@ export function orderForSubagents(goModels: CatalogModel[], featured?: string[])
244266 */
245267export async function syncCatalogModels ( config : OcxConfig ) : Promise < { added : number ; path : string } > {
246268 const catalogPath = readCodexCatalogPath ( ) ;
247- const catalog = readCatalog ( catalogPath ) ;
269+ const catalog = loadCatalogForSync ( catalogPath ) ;
248270 if ( ! catalog ) return { added : 0 , path : catalogPath } ;
249271
250272 const template = ( catalog . models ?? [ ] ) . find (
@@ -272,9 +294,9 @@ export async function syncCatalogModels(config: OcxConfig): Promise<{ added: num
272294 . map ( m => {
273295 const slug = m . slug as string ;
274296 const priority = rank . has ( slug ) ? rank . get ( slug ) ! : ( baseline . get ( slug ) ?? ( m . priority as number ) ) ;
275- return { ...m , priority } ;
297+ return normalizeServiceTiers ( { ...m , priority } ) ;
276298 } ) ;
277- catalog . models = [ ...native , ...goEntries ] ;
299+ catalog . models = [ ...native , ...goEntries ] . map ( m => normalizeServiceTiers ( m ) ) ;
278300
279301 try {
280302 if ( ! existsSync ( OCX_DIR ) ) mkdirSync ( OCX_DIR , { recursive : true } ) ;
0 commit comments