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
13 changes: 13 additions & 0 deletions packages/fold-agent/src/Catalog/BakedCatalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -845,6 +845,19 @@ export const bakedModelCatalog: ReadonlyArray<ModelCatalogEntry> = [
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',
Expand Down
2 changes: 1 addition & 1 deletion packages/fold-agent/src/Config/AgentModels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion packages/fold-agent/src/Config/ConfigSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down
6 changes: 3 additions & 3 deletions packages/fold-agent/src/Config/ConfigSchemaJson.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
},

Expand Down
6 changes: 3 additions & 3 deletions packages/fold-agent/src/Config/FoldInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,14 @@ is optional and falls back to \`smart\`.
| \`orchestrator\` | the RLM mode's primary agent |

Each binding is \`{ "provider": <key>, "model"?: <id>, "reasoning"?: <level> }\`. 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)
Expand Down Expand Up @@ -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
Expand Down
15 changes: 15 additions & 0 deletions packages/fold-agent/test/Catalog/BakedCatalog.vi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
24 changes: 20 additions & 4 deletions packages/fold-agent/test/Config/AgentModels.vi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'))
Expand Down Expand Up @@ -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, {
Expand All @@ -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')
}),
)
Expand Down Expand Up @@ -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')
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
4 changes: 2 additions & 2 deletions packages/fold-agent/test/Config/ModelSelections.vi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' })
Expand Down Expand Up @@ -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',
Expand Down
2 changes: 1 addition & 1 deletion packages/fold-agent/test/Config/ProviderConfig.vi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
Expand Down
2 changes: 1 addition & 1 deletion packages/fold-agent/test/Mode/Launch.vi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
4 changes: 2 additions & 2 deletions packages/fold-cli/src/tui/ModelSelectionState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => ({
Expand Down
2 changes: 1 addition & 1 deletion packages/fold-cli/src/tui/ProviderConfigState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const emptyProviderForm = (): ProviderForm => ({
})

const defaults = (kind: ProviderForm['kind']): Pick<ProviderForm, 'baseUrl' | 'model'> => {
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: '' }
Expand Down
4 changes: 4 additions & 0 deletions packages/fold-cli/test/ContextWindow.vi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
4 changes: 2 additions & 2 deletions packages/fold-cli/test/tui/ModelSelection.vi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/fold-cli/test/tui/ProviderConfigState.vi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: '',
})
Expand Down
4 changes: 2 additions & 2 deletions packages/fold-codex/src/CodexModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof OpenAiSchema.CreateResponse.Encoded, 'stream'>
type ResponseBody = typeof OpenAiSchema.Response.Type
Expand Down Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions packages/fold-codex/test/CodexModel.vi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
})
Expand All @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion packages/fold-core/src/Compaction/CompactionEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const filePartEstimateChars = 4_800
*/
export const defaultModelContextWindows: ReadonlyArray<readonly [RegExp, number]> = [
[/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],
]
Expand Down
29 changes: 29 additions & 0 deletions packages/fold-core/test/Api/SwitchModel.vi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 }),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
Expand All @@ -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)
})

Expand Down
1 change: 1 addition & 0 deletions packages/fold-core/test/Model/ModelFamily.vi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
Loading