diff --git a/apps/server/src/provider/ClaudeModelCatalog.test.ts b/apps/server/src/provider/ClaudeModelCatalog.test.ts index b370c8e24d3d..71e5c19eb9bf 100644 --- a/apps/server/src/provider/ClaudeModelCatalog.test.ts +++ b/apps/server/src/provider/ClaudeModelCatalog.test.ts @@ -1,5 +1,6 @@ import { assert, describe, it } from "@effect/vitest"; import { ProviderInstanceId } from "@t3tools/contracts"; +import { createModelSelection } from "@t3tools/shared/model"; import { hasValidClaudeManifestAdapters } from "./ClaudeModelManifest.ts"; import type { ModelManifestData } from "./ModelManifest.ts"; @@ -7,6 +8,7 @@ import { formatClaudeVersionUpgradeMessage, normalizeClaudeCatalogEffort, resolveClaudeCatalogApiModelId, + resolveClaudeCatalogContextWindowEnv, resolveClaudeModelCatalog, resolveClaudeModelsForVersion, resolveClaudeModelSlug, @@ -38,7 +40,10 @@ const manifest = (): ModelManifestData => ({ id: "contextWindow", label: "Context Window", type: "select", - options: [{ id: "large", label: "Large", isDefault: true }], + options: [ + { id: "small", label: "Small" }, + { id: "large", label: "Large", isDefault: true }, + ], }, ], }, @@ -46,6 +51,7 @@ const manifest = (): ModelManifestData => ({ claudeCode: { effortMap: { extreme: "high" }, modelSuffixes: { contextWindow: { large: "[large]" } }, + contextWindowTokens: { small: 200_000, large: 1_000_000 }, }, }, }, @@ -114,6 +120,58 @@ describe("Claude model catalog", () => { ); }); + it("states the Claude Code 1M context window opt-out per selection", () => { + const catalog = resolveClaudeModelCatalog(manifest()); + const instanceId = ProviderInstanceId.make("claudeAgent"); + assert.deepStrictEqual( + resolveClaudeCatalogContextWindowEnv( + catalog, + createModelSelection(instanceId, "synthetic", [{ id: "contextWindow", value: "small" }]), + ), + { CLAUDE_CODE_DISABLE_1M_CONTEXT: "1" }, + ); + assert.deepStrictEqual( + resolveClaudeCatalogContextWindowEnv(catalog, { instanceId, model: "synthetic" }), + { CLAUDE_CODE_DISABLE_1M_CONTEXT: "0" }, + ); + assert.strictEqual(resolveClaudeCatalogContextWindowEnv(catalog, undefined), undefined); + }); + + it("states the 1M context window opt-out for models with a fixed window", () => { + const base = manifest(); + const claudeAgent = base.providers!.claudeAgent!; + const input: ModelManifestData = { + ...base, + providers: { + ...base.providers, + claudeAgent: { + ...claudeAgent, + profiles: { + ...claudeAgent.profiles, + fixed: { adapter: { claudeCode: { fixedContextWindowTokens: 1_000_000 } } }, + }, + models: [ + ...claudeAgent.models, + { + slug: "claude-synthetic-fixed", + name: "Claude Synthetic Fixed", + status: "current", + profile: "fixed", + }, + ], + }, + }, + }; + const catalog = resolveClaudeModelCatalog(input); + assert.deepStrictEqual( + resolveClaudeCatalogContextWindowEnv(catalog, { + instanceId: ProviderInstanceId.make("claudeAgent"), + model: "claude-synthetic-fixed", + }), + { CLAUDE_CODE_DISABLE_1M_CONTEXT: "0" }, + ); + }); + it("rejects malformed adapter mappings", () => { const base = manifest(); const malformed: ModelManifestData = { diff --git a/apps/server/src/provider/ClaudeModelCatalog.ts b/apps/server/src/provider/ClaudeModelCatalog.ts index bd554f042f0b..c5fcb2d1ce9d 100644 --- a/apps/server/src/provider/ClaudeModelCatalog.ts +++ b/apps/server/src/provider/ClaudeModelCatalog.ts @@ -240,3 +240,23 @@ export function resolveClaudeCatalogContextWindowTokens( const contextWindow = resolveClaudeCatalogContextWindow(catalog, modelSelection); return contextWindow ? entry.runtime.contextWindowTokens?.[contextWindow] : undefined; } + +/** + * Claude Code auto-enables the 1M-token context window for every model with a + * native 1M window, so a bare model slug does not select 200k — the CLI only + * holds a session at 200k when `CLAUDE_CODE_DISABLE_1M_CONTEXT` is set, and a + * user or project settings file may already set it (live-test finding). State + * the window the catalog resolved in both directions, so the session runs the + * window T3 displays — for a fixed-window model as much as for a picked option. + * Managed policy settings still outrank this; the CLI's own usage report then + * corrects the meter. Models without catalog token data are left to the + * user's configuration. + */ +export function resolveClaudeCatalogContextWindowEnv( + catalog: ClaudeModelCatalog, + modelSelection: ModelSelection | undefined, +): Record | undefined { + const tokens = resolveClaudeCatalogContextWindowTokens(catalog, modelSelection); + if (tokens === undefined) return undefined; + return { CLAUDE_CODE_DISABLE_1M_CONTEXT: tokens <= 200_000 ? "1" : "0" }; +} diff --git a/apps/server/src/provider/Layers/ClaudeAdapter.test.ts b/apps/server/src/provider/Layers/ClaudeAdapter.test.ts index 3b5c0f8586a7..4ec1ba32e4fe 100644 --- a/apps/server/src/provider/Layers/ClaudeAdapter.test.ts +++ b/apps/server/src/provider/Layers/ClaudeAdapter.test.ts @@ -63,6 +63,7 @@ class FakeClaudeQuery implements AsyncIterable { private failure: unknown | undefined; public readonly setModelCalls: Array = []; + public readonly applyFlagSettingsCalls: Array> = []; public readonly setPermissionModeCalls: Array = []; public readonly setMaxThinkingTokensCalls: Array = []; public closeCalls = 0; @@ -106,6 +107,10 @@ class FakeClaudeQuery implements AsyncIterable { this.setModelCalls.push(model); }; + readonly applyFlagSettings = async (settings: Record): Promise => { + this.applyFlagSettingsCalls.push(settings); + }; + readonly setPermissionMode = async (mode: PermissionMode): Promise => { this.setPermissionModeCalls.push(mode); }; @@ -555,7 +560,9 @@ describe("ClaudeAdapterLive", () => { }); const createInput = harness.getLastCreateQueryInput(); - assert.equal(createInput?.options.settings, undefined); + assert.deepEqual(createInput?.options.settings, { + env: { CLAUDE_CODE_DISABLE_1M_CONTEXT: "0" }, + }); }).pipe( Effect.provideService(Random.Random, makeDeterministicRandomService()), Effect.provide(harness.layer), @@ -580,6 +587,7 @@ describe("ClaudeAdapterLive", () => { const createInput = harness.getLastCreateQueryInput(); assert.deepEqual(createInput?.options.settings, { fastMode: true, + env: { CLAUDE_CODE_DISABLE_1M_CONTEXT: "0" }, }); }).pipe( Effect.provideService(Random.Random, makeDeterministicRandomService()), @@ -603,7 +611,132 @@ describe("ClaudeAdapterLive", () => { }); const createInput = harness.getLastCreateQueryInput(); - assert.equal(createInput?.options.settings, undefined); + assert.deepEqual(createInput?.options.settings, { + env: { CLAUDE_CODE_DISABLE_1M_CONTEXT: "0" }, + }); + }).pipe( + Effect.provideService(Random.Random, makeDeterministicRandomService()), + Effect.provide(harness.layer), + ); + }); + + it.effect("opts out of the 1M context window when the 200k window is selected", () => { + const harness = makeHarness(); + return Effect.gen(function* () { + const adapter = yield* ClaudeAdapter; + yield* adapter.startSession({ + threadId: THREAD_ID, + provider: ProviderDriverKind.make("claudeAgent"), + modelSelection: createModelSelection( + ProviderInstanceId.make("claudeAgent"), + SYNTHETIC_CLAUDE_CAPABLE_MODEL, + [{ id: "contextWindow", value: "standard" }], + ), + runtimeMode: "full-access", + }); + + const createInput = harness.getLastCreateQueryInput(); + assert.equal(createInput?.options.model, SYNTHETIC_CLAUDE_CAPABLE_MODEL); + assert.deepEqual(createInput?.options.settings, { + env: { CLAUDE_CODE_DISABLE_1M_CONTEXT: "1" }, + }); + }).pipe( + Effect.provideService(Random.Random, makeDeterministicRandomService()), + Effect.provide(harness.layer), + ); + }); + + it.effect( + "updates cached usage after a mid-thread context-window switch so streaming usage is not clamped to the old window", + () => { + const harness = makeHarness(); + return Effect.gen(function* () { + const adapter = yield* ClaudeAdapter; + + const runtimeEventsFiber = yield* Stream.takeUntil( + adapter.streamEvents, + (event) => event.type === "thread.token-usage.updated", + ).pipe(Stream.runCollect, Effect.forkChild); + + yield* adapter.startSession({ + threadId: THREAD_ID, + provider: ProviderDriverKind.make("claudeAgent"), + modelSelection: createModelSelection( + ProviderInstanceId.make("claudeAgent"), + SYNTHETIC_CLAUDE_CAPABLE_MODEL, + [{ id: "contextWindow", value: "standard" }], + ), + runtimeMode: "full-access", + }); + yield* adapter.sendTurn({ + threadId: THREAD_ID, + input: "hello", + modelSelection: createModelSelection( + ProviderInstanceId.make("claudeAgent"), + SYNTHETIC_CLAUDE_CAPABLE_MODEL, + [{ id: "contextWindow", value: "expanded" }], + ), + attachments: [], + }); + + harness.query.emit({ + type: "stream_event", + session_id: "sdk-session-window-switch", + uuid: "stream-window-switch", + parent_tool_use_id: null, + event: { + type: "message_delta", + usage: { + input_tokens: 500_000, + output_tokens: 100, + }, + }, + } as unknown as SDKMessage); + + const runtimeEvents = Array.from(yield* Fiber.join(runtimeEventsFiber)); + const usageEvent = runtimeEvents.find( + (event) => event.type === "thread.token-usage.updated", + ); + assert.equal(usageEvent?.type, "thread.token-usage.updated"); + if (usageEvent?.type === "thread.token-usage.updated") { + // Before the fix this clamped to the session's starting 200k + // window (usedTokens: 200000, maxTokens: 200000) because the + // model switch never refreshed the cache streaming usage reads. + assert.deepEqual(usageEvent.payload.usage, { + usedTokens: 500_100, + lastUsedTokens: 500_100, + inputTokens: 500_000, + outputTokens: 100, + maxTokens: 1_000_000, + }); + } + }).pipe( + Effect.provideService(Random.Random, makeDeterministicRandomService()), + Effect.provide(harness.layer), + ); + }, + ); + + it.effect("opts back into the 1M context window when the expanded window is selected", () => { + const harness = makeHarness(); + return Effect.gen(function* () { + const adapter = yield* ClaudeAdapter; + yield* adapter.startSession({ + threadId: THREAD_ID, + provider: ProviderDriverKind.make("claudeAgent"), + modelSelection: createModelSelection( + ProviderInstanceId.make("claudeAgent"), + SYNTHETIC_CLAUDE_CAPABLE_MODEL, + [{ id: "contextWindow", value: "expanded" }], + ), + runtimeMode: "full-access", + }); + + const createInput = harness.getLastCreateQueryInput(); + assert.equal(createInput?.options.model, `${SYNTHETIC_CLAUDE_CAPABLE_MODEL}[expanded]`); + assert.deepEqual(createInput?.options.settings, { + env: { CLAUDE_CODE_DISABLE_1M_CONTEXT: "0" }, + }); }).pipe( Effect.provideService(Random.Random, makeDeterministicRandomService()), Effect.provide(harness.layer), @@ -691,16 +824,75 @@ describe("ClaudeAdapterLive", () => { `${SYNTHETIC_CLAUDE_CAPABLE_MODEL}[expanded]`, SYNTHETIC_CLAUDE_COLLIDING_ALIAS, ]); + assert.deepEqual(customHarness.query.applyFlagSettingsCalls, [ + { env: { CLAUDE_CODE_DISABLE_1M_CONTEXT: "0" } }, + { env: null }, + ]); assert.equal(customPrompt, "keep this prompt literal"); const builtInOptions = yield* start(builtInHarness, SYNTHETIC_CLAUDE_CAPABLE_MODEL); assert.equal(builtInOptions.model, `${SYNTHETIC_CLAUDE_CAPABLE_MODEL}[expanded]`); assert.equal(builtInOptions.effort, "max"); - assert.deepEqual(builtInOptions.settings, { fastMode: true }); + assert.deepEqual(builtInOptions.settings, { + fastMode: true, + env: { CLAUDE_CODE_DISABLE_1M_CONTEXT: "0" }, + }); }); }, ); + it.effect("leaves the 1M context window alone for models without the option", () => { + const harness = makeHarness(); + return Effect.gen(function* () { + const adapter = yield* ClaudeAdapter; + yield* adapter.startSession({ + threadId: THREAD_ID, + provider: ProviderDriverKind.make("claudeAgent"), + modelSelection: createModelSelection( + ProviderInstanceId.make("claudeAgent"), + SYNTHETIC_CLAUDE_THINKING_MODEL, + [], + ), + runtimeMode: "full-access", + }); + + const createInput = harness.getLastCreateQueryInput(); + assert.equal(createInput?.options.settings, undefined); + }).pipe( + Effect.provideService(Random.Random, makeDeterministicRandomService()), + Effect.provide(harness.layer), + ); + }); + + it.effect("merges the 1M opt-out with other SDK settings", () => { + const harness = makeHarness(); + return Effect.gen(function* () { + const adapter = yield* ClaudeAdapter; + yield* adapter.startSession({ + threadId: THREAD_ID, + provider: ProviderDriverKind.make("claudeAgent"), + modelSelection: createModelSelection( + ProviderInstanceId.make("claudeAgent"), + SYNTHETIC_CLAUDE_CAPABLE_MODEL, + [ + { id: "fastMode", value: true }, + { id: "contextWindow", value: "standard" }, + ], + ), + runtimeMode: "full-access", + }); + + const createInput = harness.getLastCreateQueryInput(); + assert.deepEqual(createInput?.options.settings, { + fastMode: true, + env: { CLAUDE_CODE_DISABLE_1M_CONTEXT: "1" }, + }); + }).pipe( + Effect.provideService(Random.Random, makeDeterministicRandomService()), + Effect.provide(harness.layer), + ); + }); + it.effect("treats ultrathink as a prompt keyword instead of a session effort", () => { const harness = makeHarness(); return Effect.gen(function* () { @@ -4659,6 +4851,7 @@ describe("ClaudeAdapterLive", () => { }); assert.deepEqual(harness.query.setModelCalls, []); + assert.deepEqual(harness.query.applyFlagSettingsCalls, []); }).pipe( Effect.provideService(Random.Random, makeDeterministicRandomService()), Effect.provide(harness.layer), @@ -4702,6 +4895,10 @@ describe("ClaudeAdapterLive", () => { `${SYNTHETIC_CLAUDE_CAPABLE_MODEL}[expanded]`, SYNTHETIC_CLAUDE_CAPABLE_MODEL, ]); + assert.deepEqual(harness.query.applyFlagSettingsCalls, [ + { env: { CLAUDE_CODE_DISABLE_1M_CONTEXT: "0" } }, + { env: { CLAUDE_CODE_DISABLE_1M_CONTEXT: "1" } }, + ]); }).pipe( Effect.provideService(Random.Random, makeDeterministicRandomService()), Effect.provide(harness.layer), diff --git a/apps/server/src/provider/Layers/ClaudeAdapter.ts b/apps/server/src/provider/Layers/ClaudeAdapter.ts index 62763f947c7d..41ae79f6243c 100644 --- a/apps/server/src/provider/Layers/ClaudeAdapter.ts +++ b/apps/server/src/provider/Layers/ClaudeAdapter.ts @@ -13,6 +13,7 @@ import { type PermissionMode, type PermissionResult, type PermissionUpdate, + type Query as ClaudeSdkQuery, type SDKMessage, type SDKResultMessage, type SettingSource, @@ -89,6 +90,7 @@ import { isClaudeCatalogUltracodeEffort, normalizeClaudeCatalogEffort, resolveClaudeCatalogApiModelId, + resolveClaudeCatalogContextWindowEnv, resolveClaudeCatalogContextWindowTokens, resolveClaudeCatalogEffort, resolveClaudeModelSlug, @@ -287,6 +289,7 @@ interface ClaudeSessionContext { readonly startedAt: string; readonly basePermissionMode: PermissionMode | undefined; currentApiModelId: string | undefined; + currentContextWindowEnv: Record | undefined; /** Effective effort for the session's turns; subagents without an explicit * effort override inherit this. */ currentEffort: string | undefined; @@ -327,6 +330,7 @@ interface ClaudeSessionContext { interface ClaudeQueryRuntime extends AsyncIterable { readonly setModel: (model?: string) => Promise; + readonly applyFlagSettings: ClaudeSdkQuery["applyFlagSettings"]; readonly setPermissionMode: (mode: PermissionMode) => Promise; readonly setMaxThinkingTokens: (maxThinkingTokens: number | null) => Promise; readonly close: () => void; @@ -479,6 +483,15 @@ function maxClaudeContextWindowFromModelUsage( return maxContextWindow; } +function sameContextWindowEnv( + left: Record | undefined, + right: Record | undefined, +): boolean { + if (left === undefined || right === undefined) return left === right; + const keys = Object.keys(left); + return keys.length === Object.keys(right).length && keys.every((key) => left[key] === right[key]); +} + function selectedClaudeContextWindow( catalog: ClaudeModelCatalog, modelSelection: ModelSelection | undefined, @@ -4306,6 +4319,7 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* ( const apiModelId = modelSelection ? resolveClaudeCatalogApiModelId(modelCatalog, modelSelection) : undefined; + const contextWindowEnv = resolveClaudeCatalogContextWindowEnv(modelCatalog, modelSelection); const initialContextWindow = selectedClaudeContextWindow(modelCatalog, modelSelection); const rawEffort = getModelSelectionStringOptionValue(modelSelection, "effort"); const effort = @@ -4341,6 +4355,12 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* ( ...(claudeSettings.autoCompactWindow ? { autoCompactWindow: Number(claudeSettings.autoCompactWindow) } : {}), + // The context-window selection must be stated to the CLI (see + // `resolveClaudeCatalogContextWindowEnv`). Set it via flag settings + // rather than the process env: a user or project settings file's + // `env` block overrides the spawned process env, while flag settings + // outrank both (live-test finding; managed policy settings still win). + ...(contextWindowEnv ? { env: contextWindowEnv } : {}), }; const mcpSession = McpProviderSession.readMcpProviderSession(input.threadId); // The attachments dir grant lets the agent Read/copy pasted images at @@ -4460,6 +4480,7 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* ( startedAt, basePermissionMode: permissionMode, currentApiModelId: apiModelId, + currentContextWindowEnv: contextWindowEnv, currentEffort: effectiveEffort ?? undefined, resumeSessionId: sessionId, pendingApprovals, @@ -4580,6 +4601,39 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* ( if (modelSelection?.model) { const apiModelId = resolveClaudeCatalogApiModelId(modelCatalog, modelSelection); + const contextWindowEnv = resolveClaudeCatalogContextWindowEnv(modelCatalog, modelSelection); + const contextWindowTokens = selectedClaudeContextWindow(modelCatalog, modelSelection); + if (!sameContextWindowEnv(context.currentContextWindowEnv, contextWindowEnv)) { + // The context-window opt-out lives in the flag-settings layer, not in + // the model id, and it outranks an explicit `[1m]` suffix (live-test + // finding) — so it is restated whenever the resolved value changes, + // ahead of the model switch. `null` clears the key, leaving a model + // without catalog token data in the same state as a freshly started + // session. Successive calls replace the whole `env` object, so this + // helper must stay its only writer. A failure is logged instead of + // failing the turn: an older CLI that does not know the control + // request would otherwise lose the turn over a wrong context window, + // and the unchanged tracked value retries on the next turn. + const applied = yield* Effect.tryPromise({ + try: () => context.query.applyFlagSettings({ env: contextWindowEnv ?? null }), + catch: (cause) => toRequestError(input.threadId, "turn/applyFlagSettings", cause), + }).pipe( + Effect.as(true), + Effect.catch((cause) => + Effect.logWarning("Failed to apply Claude context-window flag settings.", { + cause, + }).pipe(Effect.as(false)), + ), + ); + if (applied) { + context.currentContextWindowEnv = contextWindowEnv; + // Streaming usage snapshots read this cache until the turn's + // result message reports the model's real window (completeTurn); + // without updating it here, usage on the new window is clamped to + // the old one for the rest of this turn. + context.lastKnownContextWindow = contextWindowTokens ?? context.lastKnownContextWindow; + } + } if (context.currentApiModelId !== apiModelId) { yield* Effect.tryPromise({ try: () => context.query.setModel(apiModelId), diff --git a/apps/server/src/textGeneration/ClaudeTextGeneration.test.ts b/apps/server/src/textGeneration/ClaudeTextGeneration.test.ts index 8dcaa3720295..39298312aadf 100644 --- a/apps/server/src/textGeneration/ClaudeTextGeneration.test.ts +++ b/apps/server/src/textGeneration/ClaudeTextGeneration.test.ts @@ -320,7 +320,7 @@ it.layer(ClaudeTextGenerationTestLayer)("ClaudeTextGeneration", (it) => { body: "Body", }, }), - argsMustContain: `--model ${SYNTHETIC_CLAUDE_CAPABLE_MODEL}[expanded] --effort max --settings {"fastMode":true} --dangerously-skip-permissions`, + argsMustContain: `--model ${SYNTHETIC_CLAUDE_CAPABLE_MODEL}[expanded] --effort max --settings {"fastMode":true,"env":{"CLAUDE_CODE_DISABLE_1M_CONTEXT":"0"}} --dangerously-skip-permissions`, claudeConfig: { customModels: [SYNTHETIC_CLAUDE_COLLIDING_ALIAS] }, }, (textGeneration) => @@ -349,6 +349,38 @@ it.layer(ClaudeTextGenerationTestLayer)("ClaudeTextGeneration", (it) => { ), ); + it.effect("opts out of the 1M context window for 200k selections", () => + withFakeClaudeEnv( + { + output: JSON.stringify({ + structured_output: { + subject: "Add important change", + body: "", + }, + }), + argsMustContain: `--model ${SYNTHETIC_CLAUDE_CAPABLE_MODEL} --effort high --settings {"env":{"CLAUDE_CODE_DISABLE_1M_CONTEXT":"1"}}`, + }, + (textGeneration) => + Effect.gen(function* () { + const generated = yield* textGeneration.generateCommitMessage({ + cwd: process.cwd(), + branch: "feature/claude-effect", + stagedSummary: "M README.md", + stagedPatch: "diff --git a/README.md b/README.md", + modelSelection: { + ...createModelSelection( + ProviderInstanceId.make("claudeAgent"), + SYNTHETIC_CLAUDE_CAPABLE_MODEL, + [{ id: "contextWindow", value: "standard" }], + ), + }, + }); + + expect(generated.subject).toBe("Add important change"); + }), + ), + ); + it.effect("generates thread titles through the Claude provider", () => withFakeClaudeEnv( { diff --git a/apps/server/src/textGeneration/ClaudeTextGeneration.ts b/apps/server/src/textGeneration/ClaudeTextGeneration.ts index e5b13b70f1b7..001d8465d98b 100644 --- a/apps/server/src/textGeneration/ClaudeTextGeneration.ts +++ b/apps/server/src/textGeneration/ClaudeTextGeneration.ts @@ -43,6 +43,7 @@ import { isClaudeCatalogUltracodeEffort, normalizeClaudeCatalogEffort, resolveClaudeCatalogApiModelId, + resolveClaudeCatalogContextWindowEnv, resolveClaudeCatalogEffort, resolveClaudeModelSlug, scopeClaudeModelCatalog, @@ -163,10 +164,12 @@ export const makeClaudeTextGeneration = Effect.fn("makeClaudeTextGeneration")(fu thinkingDescriptor?.type === "boolean" ? thinkingDescriptor.currentValue : undefined; const fastMode = fastModeDescriptor?.type === "boolean" ? fastModeDescriptor.currentValue : undefined; + const contextWindowEnv = resolveClaudeCatalogContextWindowEnv(catalog, resolvedModelSelection); const settings = { ...(typeof thinking === "boolean" ? { alwaysThinkingEnabled: thinking } : {}), ...(fastMode ? { fastMode: true } : {}), ...(ultracode ? { ultracode: true } : {}), + ...(contextWindowEnv ? { env: contextWindowEnv } : {}), }; const settingsJson = Object.keys(settings).length > 0