From 990a26960435c1ff0fbe9d188302dc6ff2fc36fe Mon Sep 17 00:00:00 2001 From: Kyle Mistele Date: Wed, 30 Sep 2026 13:44:57 -0700 Subject: [PATCH] feat: add GPT-6.1 Sol and make it the default Codex model Add gpt-6.1-sol to the baked catalog, match dotted GPT-6 point releases in the 272k Codex compaction window, and switch the Codex default in fold-codex, config docs, and the TUI to gpt-6.1-sol. Co-Authored-By: Claude Opus 5.5 HumanLayer-Session: https://app.dev.codelayer.gg/sessions/01a0f3ff-ca48-7152-bfeb-35b5ab978e78 --- .../fold-agent/src/Catalog/BakedCatalog.ts | 13 +++++++++ packages/fold-agent/src/Config/AgentModels.ts | 2 +- .../fold-agent/src/Config/ConfigSchema.ts | 2 +- .../fold-agent/src/Config/ConfigSchemaJson.ts | 6 ++-- packages/fold-agent/src/Config/FoldInfo.ts | 6 ++-- .../test/Catalog/BakedCatalog.vi.test.ts | 15 ++++++++++ .../test/Config/AgentModels.vi.test.ts | 24 ++++++++++++--- .../test/Config/ConfigSchemaJson.vi.test.ts | 2 +- .../test/Config/ModelSelections.vi.test.ts | 4 +-- .../test/Config/ProviderConfig.vi.test.ts | 2 +- .../fold-agent/test/Mode/Launch.vi.test.ts | 2 +- .../fold-cli/src/tui/ModelSelectionState.ts | 4 +-- .../fold-cli/src/tui/ProviderConfigState.ts | 2 +- .../fold-cli/test/ContextWindow.vi.test.ts | 4 +++ .../test/tui/ModelSelection.vi.test.ts | 4 +-- .../test/tui/ProviderConfigState.vi.test.ts | 2 +- packages/fold-codex/src/CodexModel.ts | 4 +-- .../fold-codex/test/CodexModel.vi.test.ts | 6 ++-- .../src/Compaction/CompactionEngine.ts | 2 +- .../fold-core/test/Api/SwitchModel.vi.test.ts | 29 +++++++++++++++++++ .../Compaction/AutoCompactSession.vi.test.ts | 4 +-- .../Compaction/CompactionEngine.vi.test.ts | 3 +- .../test/Model/ModelFamily.vi.test.ts | 1 + 23 files changed, 111 insertions(+), 32 deletions(-) diff --git a/packages/fold-agent/src/Catalog/BakedCatalog.ts b/packages/fold-agent/src/Catalog/BakedCatalog.ts index 35f9c49..df5638a 100644 --- a/packages/fold-agent/src/Catalog/BakedCatalog.ts +++ b/packages/fold-agent/src/Catalog/BakedCatalog.ts @@ -845,6 +845,19 @@ export const bakedModelCatalog: ReadonlyArray = [ toolCall: true, pricing: { inputPerMTokens: 2, outputPerMTokens: 10, cacheReadPerMTokens: 0.2, cacheWritePerMTokens: 2.5 }, }, + { + providerId: 'openai', + modelId: 'gpt-6.1-sol', + name: 'GPT-6.1 Sol', + contextWindow: 1050000, + maxInputTokens: 922000, + maxOutputTokens: 128000, + reasoning: true, + reasoningEfforts: ['low', 'medium', 'high', 'xhigh', 'max'], + vision: true, + toolCall: true, + pricing: { inputPerMTokens: 2, outputPerMTokens: 10, cacheReadPerMTokens: 0.1, cacheWritePerMTokens: 2.5 }, + }, { providerId: 'openai', modelId: 'gpt-image-1', diff --git a/packages/fold-agent/src/Config/AgentModels.ts b/packages/fold-agent/src/Config/AgentModels.ts index 84eaf87..59d8007 100644 --- a/packages/fold-agent/src/Config/AgentModels.ts +++ b/packages/fold-agent/src/Config/AgentModels.ts @@ -6,7 +6,7 @@ * which carries no key (it uses `~/.fold/auth.json`). * * `orchestrator` falls back to `smart` when unbound (D25). A binding without a model fills the provider - * kind's default (codex → gpt-6-astra, anthropic → claude-opus-4-8); openai-compat has no default and + * kind's default (codex → gpt-6.1-sol, anthropic → claude-opus-4-8); openai-compat has no default and * requires an explicit model. Provider cross-references were validated at decode (ConfigSchema), so * remaining resolution failures are a missing credential env var, a model-less openai-compat binding, * or - when a model catalog is provided - a reasoning level the bound model does not support (D23: diff --git a/packages/fold-agent/src/Config/ConfigSchema.ts b/packages/fold-agent/src/Config/ConfigSchema.ts index e231496..69dca21 100644 --- a/packages/fold-agent/src/Config/ConfigSchema.ts +++ b/packages/fold-agent/src/Config/ConfigSchema.ts @@ -54,7 +54,7 @@ export const RoleBinding = Schema.Struct({ model: Schema.optionalKey( Schema.String.annotate({ description: - 'Provider model id; defaults: codex → gpt-6-astra, opencode → gpt-5.6-sol, xai → grok-4.6, anthropic → claude-opus-4-8; required for openai-compat', + 'Provider model id; defaults: codex → gpt-6.1-sol, opencode → gpt-5.6-sol, xai → grok-4.6, anthropic → claude-opus-4-8; required for openai-compat', }), ), /** Reasoning level for this role's requests. Defaults to `off` when omitted. */ diff --git a/packages/fold-agent/src/Config/ConfigSchemaJson.ts b/packages/fold-agent/src/Config/ConfigSchemaJson.ts index c992273..384170c 100644 --- a/packages/fold-agent/src/Config/ConfigSchemaJson.ts +++ b/packages/fold-agent/src/Config/ConfigSchemaJson.ts @@ -67,14 +67,14 @@ export const starterConfigJsonc = (): string => // Model roles bound to a provider (+ optional model). "smart" and "fast" are required; // "orchestrator" is optional and falls back to "smart". Omitting "model" uses the provider - // defaults: codex → gpt-6-astra, opencode → gpt-5.6-sol, xai → grok-4.6, + // defaults: codex → gpt-6.1-sol, opencode → gpt-5.6-sol, xai → grok-4.6, // anthropic → claude-opus-4-8. "roles": { // Default primary model for new sessions. - "smart": { "provider": "codex", "model": "gpt-6-astra", "reasoning": "medium" }, + "smart": { "provider": "codex", "model": "gpt-6.1-sol", "reasoning": "medium" }, // An explicit model always wins over the provider default. "fast": { "provider": "anthropic", "model": "claude-haiku-4-5-20251001" }, - // No model: uses the codex default, gpt-6-astra. + // No model: uses the codex default, gpt-6.1-sol. "orchestrator": { "provider": "codex", "reasoning": "high" } }, diff --git a/packages/fold-agent/src/Config/FoldInfo.ts b/packages/fold-agent/src/Config/FoldInfo.ts index a5d5c3d..2b24482 100644 --- a/packages/fold-agent/src/Config/FoldInfo.ts +++ b/packages/fold-agent/src/Config/FoldInfo.ts @@ -73,14 +73,14 @@ is optional and falls back to \`smart\`. | \`orchestrator\` | the RLM mode's primary agent | Each binding is \`{ "provider": , "model"?: , "reasoning"?: }\`. Omitting \`model\` -uses the provider kind's default: codex → \`gpt-6-astra\`, opencode → \`gpt-5.6-sol\`, xai → \`grok-4.6\`, and +uses the provider kind's default: codex → \`gpt-6.1-sol\`, opencode → \`gpt-5.6-sol\`, xai → \`grok-4.6\`, and anthropic → \`claude-opus-4-8\` (openai-compat requires an explicit model). \`reasoning\` is one of \`off · minimal · low · medium · high · xhigh · max\` and is validated against the model catalog - a level the model does not support fails the launch naming the supported levels. All roles referenced by a launch resolve eagerly, so every binding needs working credentials. -The starter config defaults \`smart\` to Codex \`gpt-6-astra\` with \`medium\` reasoning, so a new +The starter config defaults \`smart\` to Codex \`gpt-6.1-sol\` with \`medium\` reasoning, so a new default-mode session uses that model unless the user selects a profile or direct model override. ## Profiles - named presets (roles + optional mode) @@ -143,7 +143,7 @@ foldcode --prompt "..." # one-shot run; exit codes 0/130/1 foldcode --profile ultracodex # profile's roles + its pinned mode (rlm) foldcode --mode rlm --rpi # explicit mode + RPI roster foldcode --role fast --reasoning off # primary on the fast role, reasoning off -foldcode --provider codex # kind change drops the stale model -> gpt-6-astra default +foldcode --provider codex # kind change drops the stale model -> gpt-6.1-sol default foldcode --resume latest|sess_* # resume (config re-resolves; drift writes a durable transition) foldcode sessions # list this project's session logs foldcode config init|validate # starter config + schema | validate diff --git a/packages/fold-agent/test/Catalog/BakedCatalog.vi.test.ts b/packages/fold-agent/test/Catalog/BakedCatalog.vi.test.ts index 4435257..ecc6c6e 100644 --- a/packages/fold-agent/test/Catalog/BakedCatalog.vi.test.ts +++ b/packages/fold-agent/test/Catalog/BakedCatalog.vi.test.ts @@ -104,6 +104,21 @@ it('ships the GPT-6 Sol and Luna public limits, efforts, pricing, and Codex prov } }) +it('ships the GPT-6.1 Sol public limits, efforts, pricing, and Codex provider lookup', () => { + const entry = lookupCatalogEntry(bakedModelCatalog, codexModel('gpt-6.1-sol')) + + expect(entry).toMatchObject({ + providerId: 'openai', + modelId: 'gpt-6.1-sol', + contextWindow: 1_050_000, + maxInputTokens: 922_000, + maxOutputTokens: 128_000, + reasoning: true, + reasoningEfforts: ['low', 'medium', 'high', 'xhigh', 'max'], + pricing: { inputPerMTokens: 2, outputPerMTokens: 10, cacheReadPerMTokens: 0.1 }, + }) +}) + it('resolves an openai-compatible gpt-5.6-terra to the baked openai entry', () => { const entry = lookupCatalogEntry(bakedModelCatalog, openAiTerra) diff --git a/packages/fold-agent/test/Config/AgentModels.vi.test.ts b/packages/fold-agent/test/Config/AgentModels.vi.test.ts index eaddd33..3a0266c 100644 --- a/packages/fold-agent/test/Config/AgentModels.vi.test.ts +++ b/packages/fold-agent/test/Config/AgentModels.vi.test.ts @@ -284,6 +284,22 @@ it.effect("GPT-6 Sol and Luna bindings with reasoning 'max' resolve against the }), ) +it.effect("a GPT-6.1 Sol binding with reasoning 'max' resolves against the baked catalog", () => + Effect.gen(function* () { + const config = yield* parseFoldConfig(`{ + "providers": { "codex": { "kind": "codex" } }, + "roles": { + "smart": { "provider": "codex", "model": "gpt-6.1-sol", "reasoning": "max" }, + "fast": { "provider": "codex", "model": "gpt-6-luna", "reasoning": "low" } + } + }`) + const models = agentModelsFromConfig(config, { env: env({}), catalog: bakedModelCatalog }) + + const smart = yield* models.resolve('smart') + expect(smart.activeModel).toMatchObject({ modelId: 'gpt-6.1-sol', requestedReasoningLevel: 'max' }) + }), +) + it.effect("gpt-5.5 with reasoning 'max' fails RoleResolutionError naming its supported levels", () => Effect.gen(function* () { const config = yield* parseFoldConfig(gpt56ConfigText('gpt-5.5')) @@ -312,7 +328,7 @@ const defaultsConfigText = `{ } }` -it.effect('a codex binding without a model resolves to the gpt-6-astra default', () => +it.effect('a codex binding without a model resolves to the gpt-6.1-sol default', () => Effect.gen(function* () { const config = yield* parseFoldConfig(defaultsConfigText) const models = agentModelsFromConfig(config, { @@ -322,8 +338,8 @@ it.effect('a codex binding without a model resolves to the gpt-6-astra default', const model = yield* models.resolve('smart') expect(model.activeModel.providerKind).toBe('codex') - expect(model.activeModel.modelId).toBe('gpt-6-astra') - // Catalog validation ran against the defaulted id: Astra supports 'max'. + expect(model.activeModel.modelId).toBe('gpt-6.1-sol') + // Catalog validation ran against the defaulted id: GPT-6.1 Sol supports 'max'. expect(model.activeModel.requestedReasoningLevel).toBe('max') }), ) @@ -354,7 +370,7 @@ it.effect('an openai-compat binding without a model fails with the required-mode expect(error._tag).toBe('RoleResolutionError') expect(error.role).toBe('orchestrator') expect(error.message).toContain('without a model') - expect(error.message).toContain('codex → gpt-6-astra') + expect(error.message).toContain('codex → gpt-6.1-sol') expect(error.message).toContain('anthropic → claude-opus-4-8') expect(error.message).toContain('required for openai-compat') }), diff --git a/packages/fold-agent/test/Config/ConfigSchemaJson.vi.test.ts b/packages/fold-agent/test/Config/ConfigSchemaJson.vi.test.ts index eccefec..a824171 100644 --- a/packages/fold-agent/test/Config/ConfigSchemaJson.vi.test.ts +++ b/packages/fold-agent/test/Config/ConfigSchemaJson.vi.test.ts @@ -44,7 +44,7 @@ it.effect('the starter config is valid against the schema (round-trips through t expect(config.roles.smart).toMatchObject({ provider: 'codex', - model: 'gpt-6-astra', + model: 'gpt-6.1-sol', reasoning: 'medium', }) expect(config.providers.codex?.kind).toBe('codex') diff --git a/packages/fold-agent/test/Config/ModelSelections.vi.test.ts b/packages/fold-agent/test/Config/ModelSelections.vi.test.ts index ac61736..b046870 100644 --- a/packages/fold-agent/test/Config/ModelSelections.vi.test.ts +++ b/packages/fold-agent/test/Config/ModelSelections.vi.test.ts @@ -76,7 +76,7 @@ it.effect('applies provider-specific role defaults around the directly selected expect(normal.root.activeModel.role).toBe('smart') expect(normal.smart.activeModel.modelId).toBe('chosen') expect(normal.fast.activeModel).toMatchObject({ providerId: 'codex', modelId: 'gpt-5.6-luna' }) - expect(normal.orchestrator.activeModel).toMatchObject({ providerId: 'codex', modelId: 'gpt-6-astra' }) + expect(normal.orchestrator.activeModel).toMatchObject({ providerId: 'codex', modelId: 'gpt-6.1-sol' }) expect(rlm.root.activeModel.modelId).toBe('chosen') expect(rlm.root.activeModel.role).toBe('orchestrator') expect(rlm.smart.activeModel).toMatchObject({ providerId: 'codex', modelId: 'gpt-5.6-terra' }) @@ -153,7 +153,7 @@ it.effect('describes profiles, credentials, and merged model candidates without expect(description.providers.find(({ name }) => name === 'codex')?.models).toEqual([ 'catalog-only', 'default-fast', - 'gpt-6-astra', + 'gpt-6.1-sol', 'profile-fast', 'profile-orchestrator', 'profile-smart', diff --git a/packages/fold-agent/test/Config/ProviderConfig.vi.test.ts b/packages/fold-agent/test/Config/ProviderConfig.vi.test.ts index daf2321..9e211f4 100644 --- a/packages/fold-agent/test/Config/ProviderConfig.vi.test.ts +++ b/packages/fold-agent/test/Config/ProviderConfig.vi.test.ts @@ -155,7 +155,7 @@ it.effect('adds OAuth profiles without an API key and supplies their default mod expect(updated.providers['work-codex']).toEqual({ kind: 'codex', baseUrl: 'https://example.test', - configuredModels: ['gpt-6-astra'], + configuredModels: ['gpt-6.1-sol'], }) }), ).pipe(Effect.provide(NodeFileSystem.layer)), diff --git a/packages/fold-agent/test/Mode/Launch.vi.test.ts b/packages/fold-agent/test/Mode/Launch.vi.test.ts index 7692f4a..408b7b9 100644 --- a/packages/fold-agent/test/Mode/Launch.vi.test.ts +++ b/packages/fold-agent/test/Mode/Launch.vi.test.ts @@ -519,7 +519,7 @@ it.effect('mergeModelSelection drops the stale model when the newly named provid const config = yield* parseFoldConfig(mergeConfigText) // `fold --provider codex` alone: the anthropic model id must NOT ride onto codex - the binding - // comes back model-less so the codex default (gpt-6-astra) applies at resolution. + // comes back model-less so the codex default (gpt-6.1-sol) applies at resolution. const merged = mergeModelSelection(config, config.roles.smart, { provider: 'codex' }) expect(merged).toEqual({ provider: 'codex', reasoning: 'medium' }) expect(merged.model).toBeUndefined() diff --git a/packages/fold-cli/src/tui/ModelSelectionState.ts b/packages/fold-cli/src/tui/ModelSelectionState.ts index f300df0..6857e06 100644 --- a/packages/fold-cli/src/tui/ModelSelectionState.ts +++ b/packages/fold-cli/src/tui/ModelSelectionState.ts @@ -78,8 +78,8 @@ export const modelPickerChoices = ( case 'model': return (configuration.providers.find(({ name }) => name === state.provider)?.models ?? []) .toSorted((left, right) => { - if (left === 'gpt-5.6-sol') return -1 - if (right === 'gpt-5.6-sol') return 1 + if (left === 'gpt-6.1-sol') return -1 + if (right === 'gpt-6.1-sol') return 1 return left.localeCompare(right) }) .map((model) => ({ diff --git a/packages/fold-cli/src/tui/ProviderConfigState.ts b/packages/fold-cli/src/tui/ProviderConfigState.ts index e23ce0f..1738bd7 100644 --- a/packages/fold-cli/src/tui/ProviderConfigState.ts +++ b/packages/fold-cli/src/tui/ProviderConfigState.ts @@ -31,7 +31,7 @@ export const emptyProviderForm = (): ProviderForm => ({ }) const defaults = (kind: ProviderForm['kind']): Pick => { - if (kind === 'codex') return { baseUrl: 'https://chatgpt.com/backend-api/codex', model: 'gpt-5.6-sol' } + if (kind === 'codex') return { baseUrl: 'https://chatgpt.com/backend-api/codex', model: 'gpt-6.1-sol' } if (kind === 'opencode') return { baseUrl: 'https://opencode.ai/zen/v1', model: 'gpt-5.6-sol' } if (kind === 'xai') return { baseUrl: 'https://api.x.ai/v1', model: 'grok-4.6' } if (kind === 'anthropic') return { baseUrl: 'https://api.anthropic.com', model: '' } diff --git a/packages/fold-cli/test/ContextWindow.vi.test.ts b/packages/fold-cli/test/ContextWindow.vi.test.ts index 36aef4b..a43e93c 100644 --- a/packages/fold-cli/test/ContextWindow.vi.test.ts +++ b/packages/fold-cli/test/ContextWindow.vi.test.ts @@ -16,6 +16,10 @@ it('shows the Codex usable Astra window instead of the public API catalog window expect(contextWindowLimitForDisplay(astra, 1_050_000)).toBe(258_400) }) +it('shows the same Codex usable window for GPT-6.1 Sol', () => { + expect(contextWindowLimitForDisplay({ ...astra, modelId: 'gpt-6.1-sol' }, 1_050_000)).toBe(258_400) +}) + it('matches the Codex gauge percentage after excluding its baseline tokens', () => { const limit = contextWindowLimitForDisplay(astra, 1_050_000) diff --git a/packages/fold-cli/test/tui/ModelSelection.vi.test.ts b/packages/fold-cli/test/tui/ModelSelection.vi.test.ts index de068f1..fba02e1 100644 --- a/packages/fold-cli/test/tui/ModelSelection.vi.test.ts +++ b/packages/fold-cli/test/tui/ModelSelection.vi.test.ts @@ -109,14 +109,14 @@ describe('model picker state machine', () => { kind: 'codex' as const, apiKeyEnv: null, credentialPresent: true, - models: ['gpt-5.6-terra', 'gpt-5.6-sol'], + models: ['gpt-5.6-terra', 'gpt-6.1-sol'], }, ], } expect(modelPickerChoices(configured, initialModelPickerState())[0]?.id).toBe('direct') expect(modelPickerChoices(configured, { _tag: 'provider' })[0]?.id).toBe('codex') - expect(modelPickerChoices(configured, { _tag: 'model', provider: 'codex' })[0]?.id).toBe('gpt-5.6-sol') + expect(modelPickerChoices(configured, { _tag: 'model', provider: 'codex' })[0]?.id).toBe('gpt-6.1-sol') }) it('stages profile selection without a mode', () => { diff --git a/packages/fold-cli/test/tui/ProviderConfigState.vi.test.ts b/packages/fold-cli/test/tui/ProviderConfigState.vi.test.ts index 4d7b3b3..e2c1600 100644 --- a/packages/fold-cli/test/tui/ProviderConfigState.vi.test.ts +++ b/packages/fold-cli/test/tui/ProviderConfigState.vi.test.ts @@ -81,7 +81,7 @@ describe('provider config form state', () => { expect(nextProviderKind('xai')).toBe('anthropic') expect(withNextProviderKind({ ...emptyProviderForm(), kind: 'openai-compat' })).toMatchObject({ kind: 'codex', - model: 'gpt-5.6-sol', + model: 'gpt-6.1-sol', baseUrl: 'https://chatgpt.com/backend-api/codex', apiKey: '', }) diff --git a/packages/fold-codex/src/CodexModel.ts b/packages/fold-codex/src/CodexModel.ts index dd714b2..4c404ff 100644 --- a/packages/fold-codex/src/CodexModel.ts +++ b/packages/fold-codex/src/CodexModel.ts @@ -45,7 +45,7 @@ export const CODEX_API_URL = 'https://chatgpt.com/backend-api/codex' export const DEFAULT_REQUEST_RETRY_TIMES = 3 /** The codex model used when {@link CodexModelOptions.model} is omitted. */ -export const DEFAULT_CODEX_MODEL_ID = 'gpt-6-astra' +export const DEFAULT_CODEX_MODEL_ID = 'gpt-6.1-sol' type ResponsesPayload = Omit type ResponseBody = typeof OpenAiSchema.Response.Type @@ -216,7 +216,7 @@ export const decorateCodexClient = (inner: OpenAiClient.Service, options: CodexR /** Options for {@link codexModel}. */ export type CodexModelOptions = { - /** Codex model id, for example `gpt-6-astra`. Defaults to {@link DEFAULT_CODEX_MODEL_ID}. */ + /** Codex model id, for example `gpt-6.1-sol`. Defaults to {@link DEFAULT_CODEX_MODEL_ID}. */ readonly model?: string /** Reasoning level for requests. Defaults to `off`, which leaves the backend default untouched. */ readonly reasoning?: ReasoningLevel diff --git a/packages/fold-codex/test/CodexModel.vi.test.ts b/packages/fold-codex/test/CodexModel.vi.test.ts index dd30bc3..06f3f43 100644 --- a/packages/fold-codex/test/CodexModel.vi.test.ts +++ b/packages/fold-codex/test/CodexModel.vi.test.ts @@ -270,7 +270,7 @@ describe('codexModel defaults', () => { it('omitting the model binds the default codex model id', () => { const model = codexModel({}) - expect(DEFAULT_CODEX_MODEL_ID).toBe('gpt-6-astra') + expect(DEFAULT_CODEX_MODEL_ID).toBe('gpt-6.1-sol') expect(model.activeModel.modelId).toBe(DEFAULT_CODEX_MODEL_ID) expect(model.activeModel.providerKind).toBe('codex') }) @@ -279,8 +279,8 @@ describe('codexModel defaults', () => { expect(codexModel({ model: 'gpt-5.5' }).activeModel.modelId).toBe('gpt-5.5') }) - it('preserves the canonical GPT-6 Sol and Luna ids and max reasoning', () => { - for (const modelId of ['gpt-6-sol', 'gpt-6-luna']) { + it('preserves the canonical GPT-6.1 Sol, GPT-6 Sol, and Luna ids and max reasoning', () => { + for (const modelId of ['gpt-6.1-sol', 'gpt-6-sol', 'gpt-6-luna']) { const model = codexModel({ model: modelId, reasoning: 'max' }) expect(model.activeModel.modelId).toBe(modelId) diff --git a/packages/fold-core/src/Compaction/CompactionEngine.ts b/packages/fold-core/src/Compaction/CompactionEngine.ts index ba01b57..3d9f796 100644 --- a/packages/fold-core/src/Compaction/CompactionEngine.ts +++ b/packages/fold-core/src/Compaction/CompactionEngine.ts @@ -37,7 +37,7 @@ const filePartEstimateChars = 4_800 */ export const defaultModelContextWindows: ReadonlyArray = [ [/claude|fable|mythos/, 200_000], - [/gpt-6-(?:astra|sol|luna)/, 272_000], + [/gpt-6(?:\.\d+)?-(?:astra|sol|luna)/, 272_000], [/gpt-5|codex/, 272_000], [/gpt-4\.1/, 1_000_000], ] diff --git a/packages/fold-core/test/Api/SwitchModel.vi.test.ts b/packages/fold-core/test/Api/SwitchModel.vi.test.ts index 3e900be..ae6677b 100644 --- a/packages/fold-core/test/Api/SwitchModel.vi.test.ts +++ b/packages/fold-core/test/Api/SwitchModel.vi.test.ts @@ -281,6 +281,35 @@ it.effect('switchModel preserves exact GPT-6 Sol and Luna ids in the durable tra }).pipe(Effect.scoped, Effect.provide(NodeFileSystem.layer)), ) +it.effect('switchModel preserves the exact GPT-6.1 Sol id in the durable transition and requests', () => + Effect.gen(function* () { + const astra: ActiveModel = { + ...gptActiveModel, + providerId: 'codex', + providerKind: 'codex', + modelId: 'gpt-6-astra', + requestedReasoningLevel: 'max', + reasoning: { _tag: 'effort', effort: 'max', summary: 'auto' }, + } + const sol61: ActiveModel = { ...astra, modelId: 'gpt-6.1-sol' } + const first = yield* scriptedModel(astra, [textTurn('from astra')]) + const second = yield* scriptedModel(sol61, [textTurn('from gpt-6.1-sol')]) + const session = yield* startSession({ agent: defineAgent({ model: first.model }) }) + + yield* session.send('first turn') + yield* session.switchModel(second.model, { reason: 'switch to the new default' }) + yield* session.send('second turn') + + const entries = yield* session.entries + const modelChanges = entries.filter((entry): entry is ModelChangeLogEntry => + Predicate.isTagged(entry, 'model-change'), + ) + expect(modelChanges).toHaveLength(1) + expect(modelChanges[0]?.model.modelId).toBe('gpt-6.1-sol') + expect((yield* second.scripted.requests)[0]?.openAiConfig?.model).toBe('gpt-6.1-sol') + }).pipe(Effect.scoped, Effect.provide(NodeFileSystem.layer)), +) + it.effect('switchModel rejects duplicate tool names in the replacement toolset as a defect', () => Effect.gen(function* () { const first = yield* scriptedModel(gptActiveModel, [textTurn('one')]) diff --git a/packages/fold-core/test/Compaction/AutoCompactSession.vi.test.ts b/packages/fold-core/test/Compaction/AutoCompactSession.vi.test.ts index bdd7c45..2166c4d 100644 --- a/packages/fold-core/test/Compaction/AutoCompactSession.vi.test.ts +++ b/packages/fold-core/test/Compaction/AutoCompactSession.vi.test.ts @@ -605,9 +605,9 @@ it.effect('a Codex model uses the app context window instead of the public API c }).pipe(Effect.scoped, Effect.provide(NodeFileSystem.layer)), ) -it.effect('GPT-6 Sol and Luna compact against the 272k Codex window instead of the public catalog', () => +it.effect('GPT-6.1 Sol, GPT-6 Sol, and Luna compact against the 272k Codex window instead of the public catalog', () => Effect.gen(function* () { - for (const modelId of ['gpt-6-sol', 'gpt-6-luna']) { + for (const modelId of ['gpt-6.1-sol', 'gpt-6-sol', 'gpt-6-luna']) { const activeModel = { ...codexActiveModel, modelId } const { model, scripted } = yield* scriptedModel(activeModel, [ textTurn(`noted ${'a'.repeat(120)}`, { inputTokens: 224_000 }), diff --git a/packages/fold-core/test/Compaction/CompactionEngine.vi.test.ts b/packages/fold-core/test/Compaction/CompactionEngine.vi.test.ts index 7d95e32..1a7e0b0 100644 --- a/packages/fold-core/test/Compaction/CompactionEngine.vi.test.ts +++ b/packages/fold-core/test/Compaction/CompactionEngine.vi.test.ts @@ -150,6 +150,7 @@ it('defaultContextWindowFor uses the interim table with a conservative fallback' expect(defaultContextWindowFor('gpt-6-astra')).toBe(272_000) expect(defaultContextWindowFor('gpt-6-sol')).toBe(272_000) expect(defaultContextWindowFor('gpt-6-luna')).toBe(272_000) + expect(defaultContextWindowFor('gpt-6.1-sol')).toBe(272_000) expect(defaultContextWindowFor('some-unknown-model')).toBe(128_000) expect(defaultContextWindowFor(null)).toBe(128_000) }) @@ -159,7 +160,7 @@ it('compactionUsableTokens applies the D11 formula, clamped for tiny windows', ( expect(compactionUsableTokens({ contextWindow: 200_000, reserveTokens: 16_384 })).toBe(151_616) // Tiny windows clamp the output budget to window/4 and the reserve to window/8, staying positive. expect(compactionUsableTokens({ contextWindow: 2_000, reserveTokens: 16_384 })).toBe(1_250) - // Fold's raw 272k Codex window compacts at 223,616 for Sol and Luna. + // Fold's raw 272k Codex window compacts at 223,616 for GPT-6.1 Sol, Sol, and Luna. expect(compactionUsableTokens({ contextWindow: 272_000, reserveTokens: 16_384 })).toBe(223_616) }) diff --git a/packages/fold-core/test/Model/ModelFamily.vi.test.ts b/packages/fold-core/test/Model/ModelFamily.vi.test.ts index 0e11de3..2b7c270 100644 --- a/packages/fold-core/test/Model/ModelFamily.vi.test.ts +++ b/packages/fold-core/test/Model/ModelFamily.vi.test.ts @@ -39,6 +39,7 @@ it('classifies openai-compatible models by model id patterns', () => { expect(modelFamilyFor(openAiCompatible('o3-mini'))).toBe('gpt') expect(modelFamilyFor(openAiCompatible('gpt-5.1-codex-max'))).toBe('codex') expect(modelFamilyFor(openAiCompatible('gpt-6-sol'))).toBe('gpt') + expect(modelFamilyFor(openAiCompatible('gpt-6.1-sol'))).toBe('gpt') expect(modelFamilyFor(openAiCompatible('gpt-6-luna'))).toBe('gpt') expect(modelFamilyFor(openAiCompatible('anthropic/claude-opus-4-8'))).toBe('claude') expect(modelFamilyFor(openAiCompatible('llama-3.3-70b'))).toBe('unknown')