Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions packages/core/src/services/compactionInputSlimming.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,69 @@ describe('compactionInputSlimming', () => {
expect(t.screenshotTriggerThreshold).toBe(99);
});

it('rejects fractional count-like env values', () => {
const cases = [
{
envKey: 'QWEN_COMPACT_MAX_RECENT_FILES',
value: '1.5',
settings: { maxRecentFilesToRetain: 4 },
get: (t: ReturnType<typeof resolveCompactionTuning>) =>
t.maxRecentFiles,
expected: 4,
},
{
envKey: 'QWEN_COMPACT_MAX_RECENT_IMAGES',
value: '2.5',
settings: { maxRecentImagesToRetain: 5 },
get: (t: ReturnType<typeof resolveCompactionTuning>) =>
t.maxRecentImages,
expected: 5,
},
{
envKey: 'QWEN_COMPACT_SCREENSHOT_THRESHOLD',
value: '9007199254740990.5',
settings: { screenshotTriggerThreshold: 6 },
get: (t: ReturnType<typeof resolveCompactionTuning>) =>
t.screenshotTriggerThreshold,
expected: 6,
},
] as const;

for (const c of cases) {
for (const k of COMPACTION_ENV_KEYS) delete process.env[k];
process.env[c.envKey] = c.value;
expect(c.get(resolveCompactionTuning(c.settings))).toBe(c.expected);
}
});

it('rejects fractional count-like settings values', () => {
const t = resolveCompactionTuning({
maxRecentFilesToRetain: 1.5,
maxRecentImagesToRetain: 2.5,
screenshotTriggerThreshold: 3.5,
});
expect(t.maxRecentFiles).toBe(DEFAULT_MAX_RECENT_FILES);
expect(t.maxRecentImages).toBe(DEFAULT_MAX_RECENT_IMAGES);
expect(t.screenshotTriggerThreshold).toBe(
DEFAULT_SCREENSHOT_TRIGGER_THRESHOLD,
);
});

it('rejects unsafe integer count-like values', () => {
process.env['QWEN_COMPACT_MAX_RECENT_FILES'] = String(
Number.MAX_SAFE_INTEGER + 1,
);
const envFallback = resolveCompactionTuning({
maxRecentFilesToRetain: 4,
});
expect(envFallback.maxRecentFiles).toBe(4);

const settingsFallback = resolveCompactionTuning({
maxRecentImagesToRetain: Number.MAX_SAFE_INTEGER + 1,
});
expect(settingsFallback.maxRecentImages).toBe(DEFAULT_MAX_RECENT_IMAGES);
});

it('parses the boolean env both ways and ignores typos', () => {
process.env['QWEN_COMPACT_SCREENSHOT_TRIGGER'] = 'false';
expect(resolveCompactionTuning(undefined).enableScreenshotTrigger).toBe(
Expand Down
37 changes: 24 additions & 13 deletions packages/core/src/services/compactionInputSlimming.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,19 +80,29 @@ function resolveNumber(
envValue: string | undefined,
settingsValue: number | undefined,
defaultValue: number,
{ minInclusive }: { minInclusive: number },
{
integer = false,
minInclusive,
}: { integer?: boolean; minInclusive: number },
): number {
const isValid = (value: number) =>
Number.isFinite(value) &&
(!integer || Number.isSafeInteger(value)) &&
value >= minInclusive;

if (envValue !== undefined && envValue !== '') {
const parsed = Number(envValue);
if (Number.isFinite(parsed) && parsed >= minInclusive) {
const trimmed = envValue.trim();
if (integer && !/^\d+$/.test(trimmed)) {
return settingsValue !== undefined && isValid(settingsValue)
? settingsValue
: defaultValue;
}
const parsed = Number(trimmed);
if (isValid(parsed)) {
return parsed;
}
}
if (
settingsValue !== undefined &&
Number.isFinite(settingsValue) &&
settingsValue >= minInclusive
) {
if (settingsValue !== undefined && isValid(settingsValue)) {
return settingsValue;
}
return defaultValue;
Expand All @@ -116,8 +126,9 @@ export interface ResolvedCompactionTuning {

/**
* Resolves the post-compact retention + screenshot-trigger knobs in
* priority order env > settings > default, reusing the same validation
* rules as `resolveSlimmingConfig`.
* priority order env > settings > default. Count-like fields require
* integer values because downstream collectors compare against integer
* lengths.
*
* The screenshot trigger counts only images nested in
* `functionResponse.parts` (tool results). Compaction replaces those with
Expand All @@ -134,13 +145,13 @@ export function resolveCompactionTuning(
process.env['QWEN_COMPACT_MAX_RECENT_FILES'],
settings?.maxRecentFilesToRetain,
DEFAULT_MAX_RECENT_FILES,
{ minInclusive: 0 },
{ integer: true, minInclusive: 0 },
),
maxRecentImages: resolveNumber(
process.env['QWEN_COMPACT_MAX_RECENT_IMAGES'],
settings?.maxRecentImagesToRetain,
DEFAULT_MAX_RECENT_IMAGES,
{ minInclusive: 0 },
{ integer: true, minInclusive: 0 },
),
enableScreenshotTrigger: resolveBoolean(
process.env['QWEN_COMPACT_SCREENSHOT_TRIGGER'],
Expand All @@ -151,7 +162,7 @@ export function resolveCompactionTuning(
process.env['QWEN_COMPACT_SCREENSHOT_THRESHOLD'],
settings?.screenshotTriggerThreshold,
DEFAULT_SCREENSHOT_TRIGGER_THRESHOLD,
{ minInclusive: 1 },
{ integer: true, minInclusive: 1 },
),
};
}
Expand Down
Loading