Skip to content

Commit f5855ae

Browse files
committed
feat: add codex context budget controls
1 parent f3541e0 commit f5855ae

6 files changed

Lines changed: 408 additions & 31 deletions

File tree

cli.js

Lines changed: 80 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ const RECENT_CONFIGS_FILE = path.join(CONFIG_DIR, 'recent-configs.json');
9393
const WORKFLOW_DEFINITIONS_FILE = path.join(CONFIG_DIR, 'codexmate-workflows.json');
9494
const WORKFLOW_RUNS_FILE = path.join(CONFIG_DIR, 'codexmate-workflow-runs.jsonl');
9595
const DEFAULT_CLAUDE_MODEL = 'glm-4.7';
96+
const DEFAULT_MODEL_CONTEXT_WINDOW = 190000;
97+
const DEFAULT_MODEL_AUTO_COMPACT_TOKEN_LIMIT = 185000;
9698
const CODEX_BACKUP_NAME = 'codex-config';
9799

98100
const DEFAULT_MODELS = ['gpt-5.3-codex', 'gpt-5.1-codex-max', 'gpt-4-turbo', 'gpt-4'];
@@ -226,6 +228,8 @@ function resolveWebHost(options = {}) {
226228

227229
const EMPTY_CONFIG_FALLBACK_TEMPLATE = `model = "gpt-5.3-codex"
228230
model_reasoning_effort = "high"
231+
model_context_window = ${DEFAULT_MODEL_CONTEXT_WINDOW}
232+
model_auto_compact_token_limit = ${DEFAULT_MODEL_AUTO_COMPACT_TOKEN_LIMIT}
229233
disable_response_storage = true
230234
approval_policy = "never"
231235
sandbox_mode = "danger-full-access"
@@ -3168,6 +3172,8 @@ function buildDefaultConfigContent(initializedAt) {
31683172
31693173
model_provider = "openai"
31703174
model = "${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]
31733179
name = "openai"
@@ -3333,6 +3339,40 @@ 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 escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
3370+
const pattern = new RegExp(`^\\s*${escapedKey}\\s*=\\s*[^\\n]*\\n?`, 'gmi');
3371+
content = content.replace(pattern, '');
3372+
content = content.replace(/^\s*\n*/, '');
3373+
return `${key} = ${normalized}\n${content}`;
3374+
}
3375+
33363376
function getConfigTemplate(params = {}) {
33373377
let content = EMPTY_CONFIG_FALLBACK_TEMPLATE;
33383378
if (fs.existsSync(CONFIG_FILE)) {
@@ -3352,11 +3392,34 @@ function getConfigTemplate(params = {}) {
33523392
if (typeof params.reasoningEffort === 'string') {
33533393
template = applyReasoningEffortToTemplate(template, params.reasoningEffort);
33543394
}
3395+
if (params.modelAutoCompactTokenLimit !== undefined) {
3396+
template = applyPositiveIntegerConfigToTemplate(
3397+
template,
3398+
'model_auto_compact_token_limit',
3399+
params.modelAutoCompactTokenLimit
3400+
);
3401+
}
3402+
if (params.modelContextWindow !== undefined) {
3403+
template = applyPositiveIntegerConfigToTemplate(
3404+
template,
3405+
'model_context_window',
3406+
params.modelContextWindow
3407+
);
3408+
}
33553409
return {
33563410
template
33573411
};
33583412
}
33593413

3414+
function readPositiveIntegerConfigValue(config, key) {
3415+
if (!config || typeof config !== 'object' || !key) {
3416+
return '';
3417+
}
3418+
const raw = config[key];
3419+
const normalized = normalizePositiveIntegerParam(raw);
3420+
return normalized === null ? '' : normalized;
3421+
}
3422+
33603423
function applyConfigTemplate(params = {}) {
33613424
const template = typeof params.template === 'string' ? params.template : '';
33623425
if (!template.trim()) {
@@ -9981,11 +10044,15 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
998110044
const config = statusConfigResult.config;
998210045
const serviceTier = typeof config.service_tier === 'string' ? config.service_tier.trim() : '';
998310046
const modelReasoningEffort = typeof config.model_reasoning_effort === 'string' ? config.model_reasoning_effort.trim() : '';
10047+
const modelContextWindow = readPositiveIntegerConfigValue(config, 'model_context_window');
10048+
const modelAutoCompactTokenLimit = readPositiveIntegerConfigValue(config, 'model_auto_compact_token_limit');
998410049
result = {
998510050
provider: config.model_provider || '未设置',
998610051
model: config.model || '未设置',
998710052
serviceTier,
998810053
modelReasoningEffort,
10054+
modelContextWindow,
10055+
modelAutoCompactTokenLimit,
998910056
configReady: !statusConfigResult.isVirtual,
999010057
configErrorType: statusConfigResult.errorType || '',
999110058
configNotice: statusConfigResult.reason || '',
@@ -11464,11 +11531,15 @@ function buildMcpStatusPayload() {
1146411531
const config = statusConfigResult.config;
1146511532
const serviceTier = typeof config.service_tier === 'string' ? config.service_tier.trim() : '';
1146611533
const modelReasoningEffort = typeof config.model_reasoning_effort === 'string' ? config.model_reasoning_effort.trim() : '';
11534+
const modelContextWindow = readPositiveIntegerConfigValue(config, 'model_context_window');
11535+
const modelAutoCompactTokenLimit = readPositiveIntegerConfigValue(config, 'model_auto_compact_token_limit');
1146711536
return {
1146811537
provider: config.model_provider || '未设置',
1146911538
model: config.model || '未设置',
1147011539
serviceTier,
1147111540
modelReasoningEffort,
11541+
modelContextWindow,
11542+
modelAutoCompactTokenLimit,
1147211543
configReady: !statusConfigResult.isVirtual,
1147311544
configErrorType: statusConfigResult.errorType || '',
1147411545
configNotice: statusConfigResult.reason || '',
@@ -11566,6 +11637,8 @@ const BUILTIN_WORKFLOW_DEFINITIONS = Object.freeze({
1156611637
model: { type: 'string' },
1156711638
serviceTier: { type: 'string' },
1156811639
reasoningEffort: { type: 'string' },
11640+
modelContextWindow: { type: ['string', 'number'] },
11641+
modelAutoCompactTokenLimit: { type: ['string', 'number'] },
1156911642
apply: { type: 'boolean' }
1157011643
},
1157111644
required: ['provider'],
@@ -11580,7 +11653,9 @@ const BUILTIN_WORKFLOW_DEFINITIONS = Object.freeze({
1158011653
provider: '{{input.provider}}',
1158111654
model: '{{input.model}}',
1158211655
serviceTier: '{{input.serviceTier}}',
11583-
reasoningEffort: '{{input.reasoningEffort}}'
11656+
reasoningEffort: '{{input.reasoningEffort}}',
11657+
modelContextWindow: '{{input.modelContextWindow}}',
11658+
modelAutoCompactTokenLimit: '{{input.modelAutoCompactTokenLimit}}'
1158411659
}
1158511660
},
1158611661
{
@@ -12149,15 +12224,17 @@ function createMcpTools(options = {}) {
1214912224

1215012225
pushTool({
1215112226
name: 'codexmate.config.template.get',
12152-
description: 'Get Codex config template with optional provider/model/service tier/reasoning effort.',
12227+
description: 'Get Codex config template with optional provider/model/service tier/reasoning effort/context budget.',
1215312228
readOnly: true,
1215412229
inputSchema: {
1215512230
type: 'object',
1215612231
properties: {
1215712232
provider: { type: 'string' },
1215812233
model: { type: 'string' },
1215912234
serviceTier: { type: 'string' },
12160-
reasoningEffort: { type: 'string' }
12235+
reasoningEffort: { type: 'string' },
12236+
modelContextWindow: { type: ['string', 'number'] },
12237+
modelAutoCompactTokenLimit: { type: ['string', 'number'] }
1216112238
},
1216212239
additionalProperties: false
1216312240
},

tests/e2e/test-config.js

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ module.exports = async function testConfig(ctx) {
2222
assert(typeof apiStatus.configReady === 'boolean', 'api status configReady missing');
2323
assert('modelReasoningEffort' in apiStatus, 'api status modelReasoningEffort missing');
2424
assert('serviceTier' in apiStatus, 'api status serviceTier missing');
25+
assert('modelContextWindow' in apiStatus, 'api status modelContextWindow missing');
26+
assert('modelAutoCompactTokenLimit' in apiStatus, 'api status modelAutoCompactTokenLimit missing');
27+
assert(apiStatus.modelContextWindow === 190000, 'api status modelContextWindow mismatch');
28+
assert(apiStatus.modelAutoCompactTokenLimit === 185000, 'api status modelAutoCompactTokenLimit mismatch');
2529

2630
// ========== List API Tests ==========
2731
const apiList = await api('list');
@@ -83,26 +87,45 @@ module.exports = async function testConfig(ctx) {
8387
assert(typeof templateReasoningXhigh.template === 'string', 'get-config-template(reasoning xhigh) missing template');
8488
assert(/^\s*model_reasoning_effort\s*=\s*"xhigh"\s*$/m.test(templateReasoningXhigh.template), 'get-config-template(reasoning xhigh) missing model_reasoning_effort');
8589

90+
// ========== Get Config Template Tests - Context Budget ==========
91+
const templateContextBudget = await api('get-config-template', {
92+
provider: 'shadow',
93+
model: 'shadow-model',
94+
modelContextWindow: 200000,
95+
modelAutoCompactTokenLimit: 195000
96+
});
97+
assert(typeof templateContextBudget.template === 'string', 'get-config-template(context budget) missing template');
98+
assert(/^\s*model_context_window\s*=\s*200000\s*$/m.test(templateContextBudget.template), 'get-config-template(context budget) missing model_context_window');
99+
assert(/^\s*model_auto_compact_token_limit\s*=\s*195000\s*$/m.test(templateContextBudget.template), 'get-config-template(context budget) missing model_auto_compact_token_limit');
100+
86101
// ========== Get Config Template Tests - Combined ==========
87102
const templateCombined = await api('get-config-template', {
88103
provider: 'shadow',
89104
model: 'shadow-model',
90105
serviceTier: 'fast',
91-
reasoningEffort: 'high'
106+
reasoningEffort: 'high',
107+
modelContextWindow: 190000,
108+
modelAutoCompactTokenLimit: 185000
92109
});
93110
assert(typeof templateCombined.template === 'string', 'get-config-template(combined) missing template');
94111
assert(/^\s*service_tier\s*=\s*"fast"\s*$/m.test(templateCombined.template), 'get-config-template(combined) missing service_tier');
95112
assert(/^\s*model_reasoning_effort\s*=\s*"high"\s*$/m.test(templateCombined.template), 'get-config-template(combined) missing model_reasoning_effort');
113+
assert(/^\s*model_context_window\s*=\s*190000\s*$/m.test(templateCombined.template), 'get-config-template(combined) missing model_context_window');
114+
assert(/^\s*model_auto_compact_token_limit\s*=\s*185000\s*$/m.test(templateCombined.template), 'get-config-template(combined) missing model_auto_compact_token_limit');
96115

97116
const templateCombinedXhigh = await api('get-config-template', {
98117
provider: 'shadow',
99118
model: 'shadow-model',
100119
serviceTier: 'fast',
101-
reasoningEffort: 'xhigh'
120+
reasoningEffort: 'xhigh',
121+
modelContextWindow: 210000,
122+
modelAutoCompactTokenLimit: 200000
102123
});
103124
assert(typeof templateCombinedXhigh.template === 'string', 'get-config-template(combined xhigh) missing template');
104125
assert(/^\s*service_tier\s*=\s*"fast"\s*$/m.test(templateCombinedXhigh.template), 'get-config-template(combined xhigh) missing service_tier');
105126
assert(/^\s*model_reasoning_effort\s*=\s*"xhigh"\s*$/m.test(templateCombinedXhigh.template), 'get-config-template(combined xhigh) missing model_reasoning_effort');
127+
assert(/^\s*model_context_window\s*=\s*210000\s*$/m.test(templateCombinedXhigh.template), 'get-config-template(combined xhigh) missing model_context_window');
128+
assert(/^\s*model_auto_compact_token_limit\s*=\s*200000\s*$/m.test(templateCombinedXhigh.template), 'get-config-template(combined xhigh) missing model_auto_compact_token_limit');
106129

107130
// ========== Export Config Tests ==========
108131
const exportResult = await api('export-config', { includeKeys: true });

0 commit comments

Comments
 (0)