@@ -93,6 +93,8 @@ const RECENT_CONFIGS_FILE = path.join(CONFIG_DIR, 'recent-configs.json');
9393const WORKFLOW_DEFINITIONS_FILE = path . join ( CONFIG_DIR , 'codexmate-workflows.json' ) ;
9494const WORKFLOW_RUNS_FILE = path . join ( CONFIG_DIR , 'codexmate-workflow-runs.jsonl' ) ;
9595const DEFAULT_CLAUDE_MODEL = 'glm-4.7' ;
96+ const DEFAULT_MODEL_CONTEXT_WINDOW = 190000 ;
97+ const DEFAULT_MODEL_AUTO_COMPACT_TOKEN_LIMIT = 185000 ;
9698const CODEX_BACKUP_NAME = 'codex-config' ;
9799
98100const DEFAULT_MODELS = [ 'gpt-5.3-codex' , 'gpt-5.1-codex-max' , 'gpt-4-turbo' , 'gpt-4' ] ;
@@ -226,6 +228,8 @@ function resolveWebHost(options = {}) {
226228
227229const EMPTY_CONFIG_FALLBACK_TEMPLATE = `model = "gpt-5.3-codex"
228230model_reasoning_effort = "high"
231+ model_context_window = ${ DEFAULT_MODEL_CONTEXT_WINDOW }
232+ model_auto_compact_token_limit = ${ DEFAULT_MODEL_AUTO_COMPACT_TOKEN_LIMIT }
229233disable_response_storage = true
230234approval_policy = "never"
231235sandbox_mode = "danger-full-access"
@@ -3168,6 +3172,8 @@ function buildDefaultConfigContent(initializedAt) {
31683172
31693173model_provider = "openai"
31703174model = "${ defaultModel } "
3175+ model_context_window = ${ DEFAULT_MODEL_CONTEXT_WINDOW }
3176+ model_auto_compact_token_limit = ${ DEFAULT_MODEL_AUTO_COMPACT_TOKEN_LIMIT }
31713177
31723178[model_providers.openai]
31733179name = "openai"
@@ -3333,6 +3339,45 @@ function applyReasoningEffortToTemplate(template, reasoningEffort) {
33333339 return content ;
33343340}
33353341
3342+ function normalizePositiveIntegerParam ( value ) {
3343+ if ( value === undefined || value === null ) {
3344+ return null ;
3345+ }
3346+ const text = typeof value === 'number'
3347+ ? String ( value )
3348+ : ( typeof value === 'string' ? value . trim ( ) : String ( value ) . trim ( ) ) ;
3349+ if ( ! text ) {
3350+ return null ;
3351+ }
3352+ if ( ! / ^ \d + $ / . test ( text ) ) {
3353+ return null ;
3354+ }
3355+ const parsed = Number . parseInt ( text , 10 ) ;
3356+ if ( ! Number . isSafeInteger ( parsed ) || parsed <= 0 ) {
3357+ return null ;
3358+ }
3359+ return parsed ;
3360+ }
3361+
3362+ function applyPositiveIntegerConfigToTemplate ( template , key , value ) {
3363+ let content = typeof template === 'string' ? template : '' ;
3364+ const normalized = normalizePositiveIntegerParam ( value ) ;
3365+ if ( ! key || normalized === null ) {
3366+ return content ;
3367+ }
3368+
3369+ const hasBom = content . charCodeAt ( 0 ) === 0xFEFF ;
3370+ const lineEnding = content . includes ( '\r\n' ) ? '\r\n' : '\n' ;
3371+ if ( hasBom ) {
3372+ content = content . slice ( 1 ) ;
3373+ }
3374+ const escapedKey = key . replace ( / [ . * + ? ^ $ { } ( ) | [ \] \\ ] / g, '\\$&' ) ;
3375+ const pattern = new RegExp ( `^\\s*${ escapedKey } \\s*=\\s*[^\\n]*\\n?` , 'gmi' ) ;
3376+ content = content . replace ( pattern , '' ) ;
3377+ content = content . replace ( new RegExp ( `^(?:[\\t ]*${ lineEnding } )+` ) , '' ) ;
3378+ return `${ hasBom ? '\uFEFF' : '' } ${ key } = ${ normalized } ${ lineEnding } ${ content } ` ;
3379+ }
3380+
33363381function getConfigTemplate ( params = { } ) {
33373382 let content = EMPTY_CONFIG_FALLBACK_TEMPLATE ;
33383383 if ( fs . existsSync ( CONFIG_FILE ) ) {
@@ -3343,6 +3388,20 @@ function getConfigTemplate(params = {}) {
33433388 }
33443389 } catch ( e ) { }
33453390 }
3391+ if (
3392+ params . modelAutoCompactTokenLimit !== undefined
3393+ && params . modelAutoCompactTokenLimit !== null
3394+ && normalizePositiveIntegerParam ( params . modelAutoCompactTokenLimit ) === null
3395+ ) {
3396+ return { error : 'modelAutoCompactTokenLimit must be a positive integer' } ;
3397+ }
3398+ if (
3399+ params . modelContextWindow !== undefined
3400+ && params . modelContextWindow !== null
3401+ && normalizePositiveIntegerParam ( params . modelContextWindow ) === null
3402+ ) {
3403+ return { error : 'modelContextWindow must be a positive integer' } ;
3404+ }
33463405 const selectedProvider = typeof params . provider === 'string' ? params . provider . trim ( ) : '' ;
33473406 const selectedModel = typeof params . model === 'string' ? params . model . trim ( ) : '' ;
33483407 let template = normalizeTopLevelConfigWithTemplate ( content , selectedProvider , selectedModel ) ;
@@ -3352,11 +3411,54 @@ function getConfigTemplate(params = {}) {
33523411 if ( typeof params . reasoningEffort === 'string' ) {
33533412 template = applyReasoningEffortToTemplate ( template , params . reasoningEffort ) ;
33543413 }
3414+ if ( ! / ^ \s * m o d e l _ a u t o _ c o m p a c t _ t o k e n _ l i m i t \s * = .* $ / m. test ( template ) ) {
3415+ template = applyPositiveIntegerConfigToTemplate (
3416+ template ,
3417+ 'model_auto_compact_token_limit' ,
3418+ DEFAULT_MODEL_AUTO_COMPACT_TOKEN_LIMIT
3419+ ) ;
3420+ }
3421+ if ( ! / ^ \s * m o d e l _ c o n t e x t _ w i n d o w \s * = .* $ / m. test ( template ) ) {
3422+ template = applyPositiveIntegerConfigToTemplate (
3423+ template ,
3424+ 'model_context_window' ,
3425+ DEFAULT_MODEL_CONTEXT_WINDOW
3426+ ) ;
3427+ }
3428+ if ( params . modelAutoCompactTokenLimit !== undefined ) {
3429+ template = applyPositiveIntegerConfigToTemplate (
3430+ template ,
3431+ 'model_auto_compact_token_limit' ,
3432+ params . modelAutoCompactTokenLimit
3433+ ) ;
3434+ }
3435+ if ( params . modelContextWindow !== undefined ) {
3436+ template = applyPositiveIntegerConfigToTemplate (
3437+ template ,
3438+ 'model_context_window' ,
3439+ params . modelContextWindow
3440+ ) ;
3441+ }
33553442 return {
33563443 template
33573444 } ;
33583445}
33593446
3447+ function readPositiveIntegerConfigValue ( config , key ) {
3448+ const options = arguments [ 2 ] && typeof arguments [ 2 ] === 'object' ? arguments [ 2 ] : { } ;
3449+ const useDefaultsWhenMissing = options . useDefaultsWhenMissing !== false ;
3450+ if ( ! config || typeof config !== 'object' || ! key ) {
3451+ return '' ;
3452+ }
3453+ const raw = config [ key ] ;
3454+ if ( raw === undefined && useDefaultsWhenMissing ) {
3455+ if ( key === 'model_context_window' ) return DEFAULT_MODEL_CONTEXT_WINDOW ;
3456+ if ( key === 'model_auto_compact_token_limit' ) return DEFAULT_MODEL_AUTO_COMPACT_TOKEN_LIMIT ;
3457+ }
3458+ const normalized = normalizePositiveIntegerParam ( raw ) ;
3459+ return normalized === null ? '' : normalized ;
3460+ }
3461+
33603462function applyConfigTemplate ( params = { } ) {
33613463 const template = typeof params . template === 'string' ? params . template : '' ;
33623464 if ( ! template . trim ( ) ) {
@@ -3370,6 +3472,20 @@ function applyConfigTemplate(params = {}) {
33703472 return { error : `模板 TOML 解析失败: ${ e . message } ` } ;
33713473 }
33723474
3475+ if (
3476+ Object . prototype . hasOwnProperty . call ( parsed , 'model_context_window' )
3477+ && normalizePositiveIntegerParam ( parsed . model_context_window ) === null
3478+ ) {
3479+ return { error : '模板中的 model_context_window 必须是正整数' } ;
3480+ }
3481+
3482+ if (
3483+ Object . prototype . hasOwnProperty . call ( parsed , 'model_auto_compact_token_limit' )
3484+ && normalizePositiveIntegerParam ( parsed . model_auto_compact_token_limit ) === null
3485+ ) {
3486+ return { error : '模板中的 model_auto_compact_token_limit 必须是正整数' } ;
3487+ }
3488+
33733489 if ( ! parsed . model_provider || typeof parsed . model_provider !== 'string' ) {
33743490 return { error : '模板缺少 model_provider' } ;
33753491 }
@@ -9976,22 +10092,38 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
997610092 let result ;
997710093
997810094 switch ( action ) {
9979- case 'status' :
10095+ case 'status' : {
998010096 const statusConfigResult = readConfigOrVirtualDefault ( ) ;
998110097 const config = statusConfigResult . config ;
998210098 const serviceTier = typeof config . service_tier === 'string' ? config . service_tier . trim ( ) : '' ;
998310099 const modelReasoningEffort = typeof config . model_reasoning_effort === 'string' ? config . model_reasoning_effort . trim ( ) : '' ;
10100+ const budgetReadOptions = {
10101+ useDefaultsWhenMissing : ! hasConfigLoadError ( statusConfigResult )
10102+ } ;
10103+ const modelContextWindow = readPositiveIntegerConfigValue (
10104+ config ,
10105+ 'model_context_window' ,
10106+ budgetReadOptions
10107+ ) ;
10108+ const modelAutoCompactTokenLimit = readPositiveIntegerConfigValue (
10109+ config ,
10110+ 'model_auto_compact_token_limit' ,
10111+ budgetReadOptions
10112+ ) ;
998410113 result = {
998510114 provider : config . model_provider || '未设置' ,
998610115 model : config . model || '未设置' ,
998710116 serviceTier,
998810117 modelReasoningEffort,
10118+ modelContextWindow,
10119+ modelAutoCompactTokenLimit,
998910120 configReady : ! statusConfigResult . isVirtual ,
999010121 configErrorType : statusConfigResult . errorType || '' ,
999110122 configNotice : statusConfigResult . reason || '' ,
999210123 initNotice : consumeInitNotice ( )
999310124 } ;
999410125 break ;
10126+ }
999510127 case 'install-status' :
999610128 result = buildInstallStatusReport ( ) ;
999710129 break ;
@@ -11464,11 +11596,26 @@ function buildMcpStatusPayload() {
1146411596 const config = statusConfigResult . config ;
1146511597 const serviceTier = typeof config . service_tier === 'string' ? config . service_tier . trim ( ) : '' ;
1146611598 const modelReasoningEffort = typeof config . model_reasoning_effort === 'string' ? config . model_reasoning_effort . trim ( ) : '' ;
11599+ const budgetReadOptions = {
11600+ useDefaultsWhenMissing : ! hasConfigLoadError ( statusConfigResult )
11601+ } ;
11602+ const modelContextWindow = readPositiveIntegerConfigValue (
11603+ config ,
11604+ 'model_context_window' ,
11605+ budgetReadOptions
11606+ ) ;
11607+ const modelAutoCompactTokenLimit = readPositiveIntegerConfigValue (
11608+ config ,
11609+ 'model_auto_compact_token_limit' ,
11610+ budgetReadOptions
11611+ ) ;
1146711612 return {
1146811613 provider : config . model_provider || '未设置' ,
1146911614 model : config . model || '未设置' ,
1147011615 serviceTier,
1147111616 modelReasoningEffort,
11617+ modelContextWindow,
11618+ modelAutoCompactTokenLimit,
1147211619 configReady : ! statusConfigResult . isVirtual ,
1147311620 configErrorType : statusConfigResult . errorType || '' ,
1147411621 configNotice : statusConfigResult . reason || '' ,
@@ -11566,6 +11713,8 @@ const BUILTIN_WORKFLOW_DEFINITIONS = Object.freeze({
1156611713 model : { type : 'string' } ,
1156711714 serviceTier : { type : 'string' } ,
1156811715 reasoningEffort : { type : 'string' } ,
11716+ modelContextWindow : { type : [ 'string' , 'number' ] } ,
11717+ modelAutoCompactTokenLimit : { type : [ 'string' , 'number' ] } ,
1156911718 apply : { type : 'boolean' }
1157011719 } ,
1157111720 required : [ 'provider' ] ,
@@ -11580,7 +11729,9 @@ const BUILTIN_WORKFLOW_DEFINITIONS = Object.freeze({
1158011729 provider : '{{input.provider}}' ,
1158111730 model : '{{input.model}}' ,
1158211731 serviceTier : '{{input.serviceTier}}' ,
11583- reasoningEffort : '{{input.reasoningEffort}}'
11732+ reasoningEffort : '{{input.reasoningEffort}}' ,
11733+ modelContextWindow : '{{input.modelContextWindow}}' ,
11734+ modelAutoCompactTokenLimit : '{{input.modelAutoCompactTokenLimit}}'
1158411735 }
1158511736 } ,
1158611737 {
@@ -12149,15 +12300,17 @@ function createMcpTools(options = {}) {
1214912300
1215012301 pushTool ( {
1215112302 name : 'codexmate.config.template.get' ,
12152- description : 'Get Codex config template with optional provider/model/service tier/reasoning effort.' ,
12303+ description : 'Get Codex config template with optional provider/model/service tier/reasoning effort/context budget .' ,
1215312304 readOnly : true ,
1215412305 inputSchema : {
1215512306 type : 'object' ,
1215612307 properties : {
1215712308 provider : { type : 'string' } ,
1215812309 model : { type : 'string' } ,
1215912310 serviceTier : { type : 'string' } ,
12160- reasoningEffort : { type : 'string' }
12311+ reasoningEffort : { type : 'string' } ,
12312+ modelContextWindow : { type : [ 'string' , 'number' ] } ,
12313+ modelAutoCompactTokenLimit : { type : [ 'string' , 'number' ] }
1216112314 } ,
1216212315 additionalProperties : false
1216312316 } ,
0 commit comments