@@ -57,6 +57,12 @@ function readCatalog(path: string): { models?: RawEntry[]; [k: string]: unknown
5757 } catch { return null ; }
5858}
5959
60+ function findNativeTemplate ( catalog : { models ?: RawEntry [ ] } | null ) : RawEntry | null {
61+ return catalog ?. models ?. find (
62+ m => typeof m . slug === "string" && ! m . slug . includes ( "/" ) && "base_instructions" in m ,
63+ ) ?? null ;
64+ }
65+
6066function normalizeServiceTiers ( entry : RawEntry ) : RawEntry {
6167 // Codex stores the user-facing config spelling as "fast", but the catalog/request
6268 // service tier id is "priority" in current codex-rs. Keep legacy catalogs working.
@@ -83,6 +89,28 @@ function ensureAutoCompactTokenLimit(entry: RawEntry): RawEntry {
8389 return entry ;
8490}
8591
92+ function ensureStrictCatalogFields ( entry : RawEntry ) : RawEntry {
93+ if ( typeof entry . supports_reasoning_summaries !== "boolean" ) entry . supports_reasoning_summaries = true ;
94+ if ( typeof entry . default_reasoning_summary !== "string" ) entry . default_reasoning_summary = "none" ;
95+ if ( typeof entry . support_verbosity !== "boolean" ) entry . support_verbosity = true ;
96+ if ( typeof entry . default_verbosity !== "string" ) entry . default_verbosity = "low" ;
97+ if ( typeof entry . apply_patch_tool_type !== "string" ) entry . apply_patch_tool_type = "freeform" ;
98+ if ( ! entry . truncation_policy || typeof entry . truncation_policy !== "object" || Array . isArray ( entry . truncation_policy ) ) {
99+ entry . truncation_policy = { mode : "tokens" , limit : 10000 } ;
100+ }
101+ if ( typeof entry . supports_parallel_tool_calls !== "boolean" ) entry . supports_parallel_tool_calls = true ;
102+ if ( typeof entry . supports_image_detail_original !== "boolean" ) entry . supports_image_detail_original = false ;
103+ if ( ! Array . isArray ( entry . experimental_supported_tools ) ) entry . experimental_supported_tools = [ ] ;
104+ if ( ! Array . isArray ( entry . input_modalities ) ) entry . input_modalities = [ "text" ] ;
105+ if ( typeof entry . context_window !== "number" || entry . context_window <= 0 ) entry . context_window = 128000 ;
106+ if ( typeof entry . max_context_window !== "number" || entry . max_context_window <= 0 ) {
107+ entry . max_context_window = entry . context_window ;
108+ }
109+ if ( typeof entry . effective_context_window_percent !== "number" ) entry . effective_context_window_percent = 95 ;
110+ if ( typeof entry . comp_hash !== "string" ) entry . comp_hash = "opencodex" ;
111+ return ensureAutoCompactTokenLimit ( entry ) ;
112+ }
113+
86114export function normalizeRoutedCatalogEntry ( entry : RawEntry ) : RawEntry {
87115 delete entry . model_messages ;
88116 delete entry . tool_mode ;
@@ -97,7 +125,7 @@ export function normalizeRoutedCatalogEntry(entry: RawEntry): RawEntry {
97125 // runs through native gpt-5.4-mini, so image search is available and verbalized for text-only models.
98126 entry . web_search_tool_type = "text_and_image" ;
99127 entry . supports_search_tool = true ;
100- return ensureAutoCompactTokenLimit ( entry ) ;
128+ return ensureStrictCatalogFields ( entry ) ;
101129}
102130
103131function applyJawcodeCatalogMetadata ( entry : RawEntry , slug : string ) : void {
@@ -122,8 +150,8 @@ function applyJawcodeCatalogMetadata(entry: RawEntry, slug: string): void {
122150
123151function loadCatalogForSync ( path : string ) : { models ?: RawEntry [ ] ; [ k : string ] : unknown } | null {
124152 const catalog = readCatalog ( path ) ;
125- if ( catalog ) return catalog ;
126- return readCatalog ( CODEX_MODELS_CACHE_PATH ) ;
153+ if ( catalog && findNativeTemplate ( catalog ) ) return catalog ;
154+ return readCatalog ( CATALOG_BACKUP_PATH ) ?? readCatalog ( CODEX_MODELS_CACHE_PATH ) ?? catalog ;
127155}
128156
129157function readCurrentCatalogOrCache ( ) : { models ?: RawEntry [ ] ; [ k : string ] : unknown } | null {
@@ -136,10 +164,9 @@ function readCurrentCatalogOrCache(): { models?: RawEntry[]; [k: string]: unknow
136164 * Returns a deep copy, or null if no catalog/native entry exists.
137165 */
138166export function loadCatalogTemplate ( ) : RawEntry | null {
139- const cat = readCurrentCatalogOrCache ( ) ;
140- const native = cat ?. models ?. find (
141- m => typeof m . slug === "string" && ! m . slug . includes ( "/" ) && "base_instructions" in m ,
142- ) ;
167+ const native = findNativeTemplate ( readCatalog ( readCodexCatalogPath ( ) ) )
168+ ?? findNativeTemplate ( readCatalog ( CATALOG_BACKUP_PATH ) )
169+ ?? findNativeTemplate ( readCatalog ( CODEX_MODELS_CACHE_PATH ) ) ;
143170 return native ? JSON . parse ( JSON . stringify ( native ) ) : null ;
144171}
145172
@@ -186,7 +213,7 @@ function deriveEntry(template: RawEntry | null, slug: string, desc: string, prio
186213 normalizeRoutedCatalogEntry ( e ) ;
187214 applyJawcodeCatalogMetadata ( e , slug ) ;
188215 }
189- return ensureAutoCompactTokenLimit ( normalizeServiceTiers ( e ) ) ;
216+ return ensureStrictCatalogFields ( normalizeServiceTiers ( e ) ) ;
190217 }
191218 // Fallback when no template is available (best-effort; strict parser may need more).
192219 const entry : RawEntry = {
@@ -198,7 +225,7 @@ function deriveEntry(template: RawEntry | null, slug: string, desc: string, prio
198225 ...( slug . includes ( "/" ) ? { web_search_tool_type : "text_and_image" , supports_search_tool : true } : { } ) ,
199226 } ;
200227 applyJawcodeCatalogMetadata ( entry , slug ) ;
201- return ensureAutoCompactTokenLimit ( normalizeServiceTiers ( entry ) ) ;
228+ return ensureStrictCatalogFields ( normalizeServiceTiers ( entry ) ) ;
202229}
203230
204231/**
@@ -351,9 +378,7 @@ export async function syncCatalogModels(config: OcxConfig): Promise<{ added: num
351378 const catalog = loadCatalogForSync ( catalogPath ) ;
352379 if ( ! catalog ) return { added : 0 , path : catalogPath } ;
353380
354- const template = ( catalog . models ?? [ ] ) . find (
355- m => typeof m . slug === "string" && ! m . slug . includes ( "/" ) && "base_instructions" in m ,
356- ) ?? null ;
381+ const template = findNativeTemplate ( catalog ) ;
357382
358383 const goModels = await gatherRoutedModels ( config ) ;
359384 if ( goModels . length === 0 ) return { added : 0 , path : catalogPath } ;
@@ -383,7 +408,7 @@ export async function syncCatalogModels(config: OcxConfig): Promise<{ added: num
383408 // native template can never leak supports_websockets while the flag is off.
384409 const wsEnabled = websocketsEnabled ( config ) ;
385410 catalog . models = [ ...native , ...goEntries ] . map ( m => {
386- const e = ensureAutoCompactTokenLimit ( normalizeServiceTiers ( m ) ) ;
411+ const e = ensureStrictCatalogFields ( normalizeServiceTiers ( m ) ) ;
387412 if ( wsEnabled ) e . supports_websockets = true ;
388413 else delete e . supports_websockets ;
389414 return e ;
0 commit comments