@@ -19,11 +19,78 @@ async function ensureDir(dir) {
1919
2020const PROVIDER_ID_REGEX = / ^ [ a - z 0 - 9 ] [ a - z 0 - 9 - _ ] * $ /
2121const MODEL_ID_REGEX = / ^ [ a - z A - Z 0 - 9 ] [ a - z A - Z 0 - 9 \- _ . : / ] * $ /
22+ const REASONING_EFFORT_VALUES = [ 'minimal' , 'low' , 'medium' , 'high' ]
23+ const VERBOSITY_VALUES = [ 'low' , 'medium' , 'high' ]
24+ const REASONING_MODE_VALUES = [ 'budget' , 'effort' , 'level' , 'fixed' , 'mixed' ]
25+ const REASONING_VISIBILITY_VALUES = [ 'hidden' , 'summary' , 'full' , 'mixed' ]
2226const isValidLowercaseProviderId = ( id ) =>
2327 typeof id === 'string' && id === id . toLowerCase ( ) && PROVIDER_ID_REGEX . test ( id )
2428const isValidModelId = ( id ) =>
2529 typeof id === 'string' && MODEL_ID_REGEX . test ( id )
2630
31+ const sanitizeStringArray = ( value ) => {
32+ if ( typeof value === 'string' ) {
33+ const normalized = value . trim ( )
34+ return normalized ? [ normalized ] : undefined
35+ }
36+ if ( ! Array . isArray ( value ) ) return undefined
37+ const values = value . filter ( ( item ) => typeof item === 'string' && item . trim ( ) )
38+ return values . length ? values : undefined
39+ }
40+
41+ const sanitizeReasoningBudget = ( value ) => {
42+ if ( ! value || typeof value !== 'object' ) return undefined
43+ const budget = { }
44+ if ( typeof value . default === 'number' && Number . isFinite ( value . default ) ) budget . default = value . default
45+ if ( typeof value . min === 'number' && Number . isFinite ( value . min ) ) budget . min = value . min
46+ if ( typeof value . max === 'number' && Number . isFinite ( value . max ) ) budget . max = value . max
47+ if ( typeof value . auto === 'number' && Number . isFinite ( value . auto ) ) budget . auto = value . auto
48+ if ( typeof value . off === 'number' && Number . isFinite ( value . off ) ) budget . off = value . off
49+ if ( typeof value . unit === 'string' ) budget . unit = value . unit
50+ return Object . keys ( budget ) . length ? budget : undefined
51+ }
52+
53+ const sanitizeReasoningOptions = ( value , allowedValues ) => {
54+ if ( ! Array . isArray ( value ) ) return undefined
55+ const values = value . filter ( ( item ) => typeof item === 'string' && allowedValues . includes ( item ) )
56+ return values . length ? values : undefined
57+ }
58+
59+ const sanitizeExtraReasoning = ( value ) => {
60+ if ( ! value || typeof value !== 'object' ) return undefined
61+ const reasoning = { }
62+ if ( typeof value . supported === 'boolean' ) reasoning . supported = value . supported
63+ if ( typeof value . default_enabled === 'boolean' ) reasoning . default_enabled = value . default_enabled
64+ if ( typeof value . mode === 'string' && REASONING_MODE_VALUES . includes ( value . mode ) ) {
65+ reasoning . mode = value . mode
66+ }
67+ const budget = sanitizeReasoningBudget ( value . budget )
68+ if ( budget ) reasoning . budget = budget
69+ if ( typeof value . effort === 'string' && REASONING_EFFORT_VALUES . includes ( value . effort ) ) {
70+ reasoning . effort = value . effort
71+ }
72+ const effortOptions = sanitizeReasoningOptions ( value . effort_options , REASONING_EFFORT_VALUES )
73+ if ( effortOptions ) reasoning . effort_options = effortOptions
74+ if ( typeof value . verbosity === 'string' && VERBOSITY_VALUES . includes ( value . verbosity ) ) {
75+ reasoning . verbosity = value . verbosity
76+ }
77+ const verbosityOptions = sanitizeReasoningOptions ( value . verbosity_options , VERBOSITY_VALUES )
78+ if ( verbosityOptions ) reasoning . verbosity_options = verbosityOptions
79+ if ( typeof value . level === 'string' ) reasoning . level = value . level
80+ const levelOptions = sanitizeStringArray ( value . level_options )
81+ if ( levelOptions ) reasoning . level_options = levelOptions
82+ if ( typeof value . interleaved === 'boolean' ) reasoning . interleaved = value . interleaved
83+ if ( typeof value . summaries === 'boolean' ) reasoning . summaries = value . summaries
84+ if ( typeof value . visibility === 'string' && REASONING_VISIBILITY_VALUES . includes ( value . visibility ) ) {
85+ reasoning . visibility = value . visibility
86+ }
87+ const continuation = sanitizeStringArray ( value . continuation )
88+ if ( continuation ) reasoning . continuation = continuation
89+ const notes = sanitizeStringArray ( value . notes )
90+ if ( notes ) reasoning . notes = notes
91+ return Object . keys ( reasoning ) . length ? reasoning : undefined
92+ }
93+
2794function sanitizeAggregateJson ( json ) {
2895 if ( ! json || typeof json !== 'object' ) return null
2996 const providers = json . providers
@@ -88,6 +155,12 @@ function sanitizeAggregateJson(json) {
88155 if ( Object . keys ( rs ) . length ) reasoning = rs
89156 }
90157
158+ let extra_capabilities
159+ const extraReasoning = sanitizeExtraReasoning ( m . extra_capabilities ?. reasoning )
160+ if ( extraReasoning ) {
161+ extra_capabilities = { reasoning : extraReasoning }
162+ }
163+
91164 // search (new schema)
92165 let search
93166 const s = m . search
@@ -127,6 +200,7 @@ function sanitizeAggregateJson(json) {
127200 temperature : typeof m . temperature === 'boolean' ? m . temperature : undefined ,
128201 tool_call : typeof m . tool_call === 'boolean' ? m . tool_call : undefined ,
129202 reasoning,
203+ extra_capabilities,
130204 search,
131205 attachment : typeof m . attachment === 'boolean' ? m . attachment : undefined ,
132206 open_weights : typeof m . open_weights === 'boolean' ? m . open_weights : undefined ,
0 commit comments