diff --git a/apps/cli/src/commands/run.ts b/apps/cli/src/commands/run.ts index 157baeab..d6ee35d7 100644 --- a/apps/cli/src/commands/run.ts +++ b/apps/cli/src/commands/run.ts @@ -28,6 +28,7 @@ type RunOptions = { runLog?: boolean; toolsAllow?: string[]; toolsDeny?: string[]; + contextWindow?: number; help?: boolean; }; @@ -49,6 +50,7 @@ ${cyan("Options:")} ${yellow("--session")} ID Session ID for persistence ${yellow("--debug")} Enable debug logging ${yellow("--run-log")} Enable structured run logging (run-log.jsonl) + ${yellow("--context-window")} N Override context window token count ${yellow("--help")}, -h Show this help ${cyan("Tools Configuration:")} @@ -141,6 +143,11 @@ function parseArgs(argv: string[]): { opts: RunOptions; prompt: string } { opts.toolsDeny = value?.split(",").map((s) => s.trim()) ?? []; continue; } + if (arg === "--context-window") { + const value = args.shift(); + opts.contextWindow = value ? parseInt(value, 10) : undefined; + continue; + } if (arg === "--") { promptParts.push(...args); break; @@ -213,6 +220,7 @@ export async function runCommand(args: string[]): Promise { debug: opts.debug, enableRunLog, tools: toolsConfig, + contextWindowTokens: opts.contextWindow, }); const sessionDir = join(DATA_DIR, "sessions", agent.sessionId); diff --git a/packages/core/src/agent/context-window/guard.test.ts b/packages/core/src/agent/context-window/guard.test.ts index 7bc7ed42..ecd6e71a 100644 --- a/packages/core/src/agent/context-window/guard.test.ts +++ b/packages/core/src/agent/context-window/guard.test.ts @@ -24,15 +24,15 @@ describe("guard", () => { }); describe("resolveContextWindowInfo", () => { - it("should prioritize model context window", () => { + it("should prioritize config over model (explicit override wins)", () => { const result = resolveContextWindowInfo({ modelContextWindow: 100_000, configContextTokens: 50_000, defaultTokens: 200_000, }); - expect(result.tokens).toBe(100_000); - expect(result.source).toBe("model"); + expect(result.tokens).toBe(50_000); + expect(result.source).toBe("config"); }); it("should fall back to config when model is undefined", () => { @@ -105,13 +105,23 @@ describe("guard", () => { expect(result.source).toBe("config"); }); - it("should floor decimal values", () => { + it("should floor decimal values from model", () => { const result = resolveContextWindowInfo({ modelContextWindow: 100_000.9, }); expect(result.tokens).toBe(100_000); }); + + it("should use model when config is not provided", () => { + const result = resolveContextWindowInfo({ + modelContextWindow: 100_000, + defaultTokens: 200_000, + }); + + expect(result.tokens).toBe(100_000); + expect(result.source).toBe("model"); + }); }); describe("evaluateContextWindowGuard", () => { diff --git a/packages/core/src/agent/context-window/guard.ts b/packages/core/src/agent/context-window/guard.ts index 1fdfddfe..959b84ae 100644 --- a/packages/core/src/agent/context-window/guard.ts +++ b/packages/core/src/agent/context-window/guard.ts @@ -27,28 +27,29 @@ function normalizePositiveInt(value: unknown): number | null { /** * Resolve context window information * - * Priority: model > config > default + * Priority: config > model > default + * (Explicit config override always wins — allows capping context for testing/cost control) */ export function resolveContextWindowInfo(params: { /** Model's contextWindow property */ modelContextWindow?: number | undefined; - /** Context tokens specified in config */ + /** Context tokens specified in config (explicit override, highest priority) */ configContextTokens?: number | undefined; /** Default value */ defaultTokens?: number | undefined; }): ContextWindowInfo { - // 1. Try getting from model - const fromModel = normalizePositiveInt(params.modelContextWindow); - if (fromModel) { - return { tokens: fromModel, source: "model" }; - } - - // 2. Try getting from config + // 1. Explicit config override always wins const fromConfig = normalizePositiveInt(params.configContextTokens); if (fromConfig) { return { tokens: fromConfig, source: "config" }; } + // 2. Try getting from model + const fromModel = normalizePositiveInt(params.modelContextWindow); + if (fromModel) { + return { tokens: fromModel, source: "model" }; + } + // 3. Use default value return { tokens: Math.floor(params.defaultTokens ?? DEFAULT_CONTEXT_TOKENS),