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
3 changes: 2 additions & 1 deletion src/integrations/generated/integrationArtifacts.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ import modelFireworksMerged from '../models/fireworks-merged.js'
import modelGemini from '../models/gemini.js'
import modelGlm from '../models/glm.js'
import modelGpt from '../models/gpt.js'
import modelGpt6 from '../models/gpt6.js'
import modelKimi from '../models/kimi.js'
import modelLing from '../models/ling.js'
import modelLlama from '../models/llama.js'
Expand All @@ -97,7 +98,7 @@ export const VENDOR_DESCRIPTORS = [vendorAnthropic, vendorBankr, vendorDeepseek,
export const GATEWAY_DESCRIPTORS = [gatewayAimlapi, gatewayApiRoute, gatewayApismart, gatewayAtlasCloud, gatewayAtomicChat, gatewayAzureOpenai, gatewayBedrock, gatewayClinepass, gatewayCloudflare, gatewayCommandcode, gatewayConcentrate, gatewayCustom, gatewayDashscopeCn, gatewayDashscopeIntl, gatewayGithubEnterprise, gatewayGithub, gatewayGitlawbOpengateway, gatewayGroq, gatewayHicap, gatewayKimiCode, gatewayLlmtr, gatewayLmstudio, gatewayMistral, gatewayNvidiaNim, gatewayOllama, gatewayOpencodeGo, gatewayOpencode, gatewayOpenrouter, gatewayTogether, gatewayVertex, gatewayXiaomiMimoToken] as const satisfies readonly GatewayDescriptor[]
export const ANTHROPIC_PROXY_DESCRIPTORS = [anthropicproxyCustom] as const satisfies readonly AnthropicProxyDescriptor[]
export const BRAND_DESCRIPTORS = [brandClaude, brandDeepseek, brandFireworks, brandGemini, brandGlm, brandGpt, brandKimi, brandLing, brandLlama, brandLongcat, brandMacaron, brandMinimax, brandMistral, brandNearai, brandNemotron, brandOpenaiCompatibleAlias, brandQwen, brandTencent, brandXai, brandXiaomiMimo] as const satisfies readonly BrandDescriptor[]
export const MODEL_DESCRIPTOR_GROUPS = [modelClaude, modelDeepseek, modelFireworksMerged, modelGemini, modelGlm, modelGpt, modelKimi, modelLing, modelLlama, modelLongcat, modelMacaron, modelMinimax, modelMistral, modelNearai, modelNemotron, modelOpenaiCompatibleAlias, modelOpencode, modelQwen, modelTencent, modelXai, modelXiaomiMimo] as const satisfies readonly (readonly ModelDescriptor[])[]
export const MODEL_DESCRIPTOR_GROUPS = [modelClaude, modelDeepseek, modelFireworksMerged, modelGemini, modelGlm, modelGpt, modelGpt6, modelKimi, modelLing, modelLlama, modelLongcat, modelMacaron, modelMinimax, modelMistral, modelNearai, modelNemotron, modelOpenaiCompatibleAlias, modelOpencode, modelQwen, modelTencent, modelXai, modelXiaomiMimo] as const satisfies readonly (readonly ModelDescriptor[])[]
export const MODEL_DESCRIPTORS = MODEL_DESCRIPTOR_GROUPS.flat() satisfies readonly ModelDescriptor[]

export { PROVIDER_PRESET_MANIFEST, ORDERED_PROVIDER_PRESETS } from './integrationManifest.generated.js'
Expand Down
43 changes: 43 additions & 0 deletions src/integrations/gpt6Catalog.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { expect, test } from 'bun:test'

import gpt6Models from './models/gpt6.js'
import openaiVendor from './vendors/openai.js'

const GPT6_FLAGSHIPS = ['gpt-6-astra', 'gpt-6.1-sol', 'gpt-6-sol'] as const
const GPT6_IDS = [...GPT6_FLAGSHIPS, 'gpt-6-luna'] as const

test('gpt-6 model descriptors carry the full API spec (no Codex cap published)', () => {
for (const id of GPT6_IDS) {
const descriptor = gpt6Models.find(model => model.id === id)
expect(descriptor).toBeDefined()
// Descriptor and catalog intentionally AGREE here — unlike the 5.6
// family, there is no published Codex-route cap for GPT-6 (the 272k pin
// is verified ONLY for 5.6; see the gpt6.ts descriptor comment). The
// full 1.05M spec ships with a dated comment + first-500 monitoring
// flag instead of an invented cap.
expect(descriptor?.contextWindow).toBe(1_050_000)
expect(descriptor?.maxOutputTokens).toBe(128_000)
}
})

test('openai vendor gpt-6 catalog entries carry limits and reasoning metadata', () => {
for (const id of GPT6_IDS) {
const entry = openaiVendor.catalog?.models?.find(model => model.id === id)
expect(entry).toBeDefined()
expect(entry?.contextWindow).toBe(1_050_000)
expect(entry?.maxOutputTokens).toBe(128_000)
expect(entry?.reasoning?.wireFormat).toBe('reasoning_effort')
expect(entry?.reasoning?.levels).toContain('xhigh')
}
})

test('openai vendor gpt-6 flagships expose the Ultra tier as max; luna tops out at xhigh', () => {
for (const id of GPT6_FLAGSHIPS) {
const entry = openaiVendor.catalog?.models?.find(model => model.id === id)
expect(entry?.reasoning?.levels).toContain('max')
expect(entry?.reasoning?.defaultLevel).toBe('high')
}
const luna = openaiVendor.catalog?.models?.find(model => model.id === 'gpt-6-luna')
expect(luna?.reasoning?.levels).not.toContain('max')
expect(luna?.reasoning?.defaultLevel).toBe('medium')
})
56 changes: 56 additions & 0 deletions src/integrations/models/gpt6.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { defineModel } from '../define.js'

const gpt6Capabilities = {
supportsVision: true,
supportsStreaming: true,
supportsFunctionCalling: true,
supportsJsonMode: true,
supportsReasoning: false,
supportsPreciseTokenCount: true,
}

function gpt6Model(
id: string,
label: string,
contextWindow: number,
maxOutputTokens: number,
) {
return defineModel({
id,
label,
brandId: 'gpt',
vendorId: 'openai',
classification: ['chat', 'vision', 'coding'],
defaultModel: id,
capabilities: gpt6Capabilities,
contextWindow,
maxOutputTokens,
})
}

export default [
// GPT-6 family (October 2026). Live API spec, two agreeing sources
// (2026-10-13): OpenRouter (`openai/gpt-6-astra`: `context_length`
// 1,050,000, `top_provider.max_completion_tokens` 128,000) and models.dev
// (`limit.context` 1,050,000, `limit.input` 922,000, `limit.output`
// 128,000). The same 1,050,000 / 128,000 spec is verified for gpt-6.1-sol,
// gpt-6-sol, and gpt-6-luna (both sources).
//
// Unlike the 5.6 family, there is NO published Codex-route cap for these
// models: OpenAI's Codex bundled metadata pin (openai/codex#33972) covers
// the 5.6 family only, and no Astra/Sol/Luna Codex number (or pricing-tier
// boundary rule) could be found as of 2026-10-13 — in-session research came
// back empty (no web tooling available; zero Astra references in-tree).
// So the descriptors below carry the full API spec instead of a
// Codex-route pin. Do NOT copy the 5.6 272k cap here: that number is
// verified ONLY for the 5.6 family. FIRST-500 MONITORING: watch early
// production Codex traffic for 500 "input exceeds the context window"
// failures (issue #1118 precedent); if the Codex backend enforces a lower
// effective cap, pin these descriptors to it with provenance.
// The 1.05M API spec lives in the openai-vendor catalog entries, same as
// the 5.6 family. Last cross-checked 2026-10-13.
gpt6Model('gpt-6-astra', 'GPT-6 Astra', 1_050_000, 128_000),
gpt6Model('gpt-6.1-sol', 'GPT-6.1 Sol', 1_050_000, 128_000),
gpt6Model('gpt-6-sol', 'GPT-6 Sol', 1_050_000, 128_000),
gpt6Model('gpt-6-luna', 'GPT-6 Luna', 1_050_000, 128_000),
]
15 changes: 15 additions & 0 deletions src/integrations/vendors/openai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,21 @@ export default defineVendor({
{ id: 'gpt-5.6-terra', apiName: 'gpt-5.6-terra', label: 'GPT-5.6 Terra', modelDescriptorId: 'gpt-5.6-terra', contextWindow: 1_050_000, maxOutputTokens: 128_000, capabilities: { supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'medium', 'high', 'xhigh'], defaultLevel: 'high', wireFormat: 'reasoning_effort' } },
{ id: 'gpt-5.6-luna', apiName: 'gpt-5.6-luna', label: 'GPT-5.6 Luna', modelDescriptorId: 'gpt-5.6-luna', contextWindow: 1_050_000, maxOutputTokens: 128_000, capabilities: { supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'medium', 'high', 'xhigh'], defaultLevel: 'high', wireFormat: 'reasoning_effort' } },
{ id: 'gpt-5.5', apiName: 'gpt-5.5', label: 'GPT-5.5', modelDescriptorId: 'gpt-5.5', contextWindow: 272_000, maxOutputTokens: 128_000, capabilities: { supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'medium', 'high', 'xhigh'], defaultLevel: 'high', wireFormat: 'reasoning_effort' } },
// GPT-6 family (October 2026): same /v1/responses + nested
// reasoning.effort wire shape as the 5.6 family (modelRequiresResponsesApi
// routes them there; verified 5.6 behavior, inherited pending live
// GPT-6 wire confirmation). Effort ladder per models.dev
// `reasoning_options` (low, medium, high, xhigh, max) plus the
// operator-confirmed native-Codex Ultra tier on Sol/Astra model types
// only (2026-10-13): flagships (astra/sol/6.1-sol) expose `max`, Luna
// tops out at `xhigh`. The Ultra→max wire mapping is the working
// hypothesis, not a live-verified row — see the gpt6.ts descriptor
// comment. Defaults inherit the family pattern (high flagship,
// medium luna) pending Codex-default evidence.
{ id: 'gpt-6-astra', apiName: 'gpt-6-astra', label: 'GPT-6 Astra', modelDescriptorId: 'gpt-6-astra', contextWindow: 1_050_000, maxOutputTokens: 128_000, capabilities: { supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'medium', 'high', 'xhigh', 'max'], defaultLevel: 'high', wireFormat: 'reasoning_effort' } },
{ id: 'gpt-6.1-sol', apiName: 'gpt-6.1-sol', label: 'GPT-6.1 Sol', modelDescriptorId: 'gpt-6.1-sol', contextWindow: 1_050_000, maxOutputTokens: 128_000, capabilities: { supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'medium', 'high', 'xhigh', 'max'], defaultLevel: 'high', wireFormat: 'reasoning_effort' } },
{ id: 'gpt-6-sol', apiName: 'gpt-6-sol', label: 'GPT-6 Sol', modelDescriptorId: 'gpt-6-sol', contextWindow: 1_050_000, maxOutputTokens: 128_000, capabilities: { supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'medium', 'high', 'xhigh', 'max'], defaultLevel: 'high', wireFormat: 'reasoning_effort' } },
{ id: 'gpt-6-luna', apiName: 'gpt-6-luna', label: 'GPT-6 Luna', modelDescriptorId: 'gpt-6-luna', contextWindow: 1_050_000, maxOutputTokens: 128_000, capabilities: { supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'medium', 'high', 'xhigh'], defaultLevel: 'medium', wireFormat: 'reasoning_effort' } },
{ id: 'gpt-5.4', apiName: 'gpt-5.4', label: 'GPT-5.4', modelDescriptorId: 'gpt-5.4', contextWindow: 1_050_000, maxOutputTokens: 128_000, capabilities: { supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'medium', 'high', 'xhigh'], defaultLevel: 'high', wireFormat: 'reasoning_effort' } },
{ id: 'gpt-5-mini', apiName: 'gpt-5-mini', label: 'GPT-5 Mini' },
{ id: 'gpt-4o', apiName: 'gpt-4o', label: 'GPT-4o' },
Expand Down
24 changes: 24 additions & 0 deletions src/services/api/providerConfig.local.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,13 @@ test('modelRequiresResponsesApi matches gpt-5.4/5.5/5.6 (excl. mini/nano) only',
'gpt-5.6-terra',
'gpt-5.6-luna',
'GPT-5.6-SOL',
// GPT-6 family inherits the 5.6 responses routing
'gpt-6',
'gpt-6-astra',
'gpt-6.1-sol',
'gpt-6-sol',
'gpt-6-luna',
'GPT-6-ASTRA',
// patch releases of a verified family stay routed
'gpt-5.4.1',
]) {
Expand All @@ -402,6 +409,9 @@ test('modelRequiresResponsesApi matches gpt-5.4/5.5/5.6 (excl. mini/nano) only',
'gpt-5.7',
'gpt-5.8-preview',
'gpt-5.9',
// gpt-60-style ids are not the gpt-6 family
'gpt-60',
'gpt-6-mini',
'o3',
'claude-opus-4-8',
]) {
Expand Down Expand Up @@ -434,6 +444,20 @@ test('auto-routes gpt-5.6 to responses on the default OpenAI base', () => {
})
})

test('auto-routes gpt-6-astra to responses on the default OpenAI base', () => {
process.env.CLAUDE_CODE_USE_OPENAI = '1'
process.env.OPENAI_BASE_URL = 'https://api.openai.com/v1'
process.env.OPENAI_MODEL = 'gpt-6-astra'
delete process.env.OPENAI_API_FORMAT

expect(resolveProviderRequest()).toMatchObject({
transport: 'responses',
resolvedModel: 'gpt-6-astra',
baseUrl: 'https://api.openai.com/v1',
reasoning: { effort: 'high' },
})
})

test('auto-routes gpt-5.6 to responses on regional OpenAI subdomains', () => {
process.env.CLAUDE_CODE_USE_OPENAI = '1'
process.env.OPENAI_BASE_URL = 'https://eu.api.openai.com/v1'
Expand Down
10 changes: 10 additions & 0 deletions src/services/api/providerConfig.protoAlias.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,16 @@ describe('providerConfig — Codex alias lookup is prototype-safe', () => {
expect(getReasoningEffortForModel('gpt-5.3-codex-spark')).toBeUndefined()
})

test('getReasoningEffortForModel resolves GPT-6 family defaults', () => {
expect(getReasoningEffortForModel('gpt-6')).toBe('high')
expect(getReasoningEffortForModel('gpt-6-astra')).toBe('high')
expect(getReasoningEffortForModel('gpt-6.1-sol')).toBe('high')
expect(getReasoningEffortForModel('gpt-6-sol')).toBe('high')
expect(getReasoningEffortForModel('gpt-6-luna')).toBe('medium')
expect(supportsCodexReasoningEffort('gpt-6-astra')).toBe(true)
expect(supportsCodexReasoningEffort('gpt-6-luna')).toBe(true)
})

// The descriptor parsing path (parseModelDescriptor, via resolveProviderRequest)
// is guarded too — a proto-name model must resolve to itself, not to the
// inherited Object constructor's (nonexistent) `.model`, which produced an
Expand Down
43 changes: 40 additions & 3 deletions src/services/api/providerConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,33 @@ const CODEX_ALIAS_MODELS: Record<
model: 'gpt-5.6-luna',
reasoningEffort: 'medium',
},
// GPT-6 family (October 2026). Served on the Codex backend (native picker:
// GPT-6.1 Sol, Astra, Sol, Luna) and direct OpenAI /v1/responses. Bare
// `gpt-6` follows the Codex CLI convention of resolving to the flagship
// tier (Sol). Defaults inherit the family pattern (high flagship, medium
// luna) pending Codex-default evidence — see the gpt6.ts descriptor
// comment. gpt-5.5 stays pinned: it retires from Codex sign-in 2026-10-14
// and codexplan already resolves to gpt-5.6-sol above.
'gpt-6': {
model: 'gpt-6-sol',
reasoningEffort: 'high',
},
'gpt-6-astra': {
model: 'gpt-6-astra',
reasoningEffort: 'high',
},
'gpt-6.1-sol': {
model: 'gpt-6.1-sol',
reasoningEffort: 'high',
},
'gpt-6-sol': {
model: 'gpt-6-sol',
reasoningEffort: 'high',
},
'gpt-6-luna': {
model: 'gpt-6-luna',
reasoningEffort: 'medium',
},
'gpt-5.5': {
model: 'gpt-5.5',
reasoningEffort: 'high',
Expand Down Expand Up @@ -487,23 +514,33 @@ function shouldUseGithubResponsesApi(model: string): boolean {
// gpt-5-mini, gpt-4.x, o-series, and claude-* stay on chat/completions.
export function modelRequiresResponsesApi(model: string): boolean {
const normalized = model.trim().toLowerCase().split('?', 1)[0] ?? ''
return /^gpt-5\.[4-6](?!\d)/.test(normalized) &&
const isGpt56Responses = /^gpt-5\.[4-6](?!\d)/.test(normalized)
// GPT-6 family inherits the 5.6 responses routing: same reasoning-model
// shape (function tools + reasoning_effort rejected on chat/completions).
// Verified 5.6 behavior, inherited pending live GPT-6 wire confirmation —
// see the gpt6.ts descriptor comment.
const isGpt6Responses = /^gpt-6(?:[.-]|$)/.test(normalized)
return (isGpt56Responses || isGpt6Responses) &&
!GPT5_MINI_NANO_RE.test(normalized)
}

// The gpt-5 family boundary: gpt-5, gpt-5-*, gpt-5.x — without matching
// gpt-50-style ids. Shared by supportsCodexReasoningEffort and the Codex
// profile model gate so the family shape lives in one place.
const GPT5_FAMILY_RE = /^gpt-5(?:[.-]|$)/
// GPT-6 family (October 2026): same Codex-backend eligibility and effort
// support as gpt-5. Separate pattern so the 5.x boundary stays legible.
const GPT6_FAMILY_RE = /^gpt-6(?:[.-]|$)/
const GPT5_MINI_NANO_RE = /(?:^|[-.])(?:mini|nano)(?:[-.]|$)/

// gpt-5 family models the ChatGPT Codex backend can serve. The -mini/-nano
// tiers are API-only (never exposed through the Codex transport), so a
// stale gpt-5-mini pick saved under a direct-OpenAI profile must fall back
// to the Codex profile's default rather than be sent to the backend and 400.
// Covers gpt-6 the same way (native Codex picker serves the GPT-6 family).
export function isCodexEligibleGpt5Model(model: string): boolean {
const base = model.trim().toLowerCase().split('?', 1)[0] ?? ''
return GPT5_FAMILY_RE.test(base) && !GPT5_MINI_NANO_RE.test(base)
return (GPT5_FAMILY_RE.test(base) || GPT6_FAMILY_RE.test(base)) && !GPT5_MINI_NANO_RE.test(base)
}

// The responses auto-route only fires for the OpenAI first-party surface
Expand Down Expand Up @@ -1543,5 +1580,5 @@ export function supportsCodexReasoningEffort(model: string): boolean {
return true
}

return GPT5_FAMILY_RE.test(base)
return GPT5_FAMILY_RE.test(base) || GPT6_FAMILY_RE.test(base)
}
33 changes: 33 additions & 0 deletions src/utils/context.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,39 @@ test('gpt-5.6 family keeps the full window on the direct-OpenAI route', () => {
}
})

test('gpt-6 family ships the full API spec on the Codex route (no Codex cap published)', () => {
process.env.CLAUDE_CODE_USE_OPENAI = '1'
process.env.OPENAI_BASE_URL = 'https://chatgpt.com/backend-api/codex'
delete process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS
delete process.env.OPENAI_MODEL

// Unlike the 5.6 family (pinned to the verified ~272k Codex effective
// input boundary), GPT-6 has no published Codex-route cap — the 272k pin
// is verified ONLY for 5.6 (openai/codex#33972). The gpt6.ts descriptors
// carry the full 1.05M spec with a dated comment + first-500 monitoring
// flag; if the Codex backend enforces a lower cap, pin them then.
for (const model of ['gpt-6-astra', 'gpt-6.1-sol', 'gpt-6-sol', 'gpt-6-luna']) {
expect(getContextWindowForModel(model)).toBe(1_050_000)
expect(getModelMaxOutputTokens(model)).toEqual({
default: 128_000,
upperLimit: 128_000,
})
}
})

test('gpt-6 family keeps the full window on the direct-OpenAI route', () => {
process.env.CLAUDE_CODE_USE_OPENAI = '1'
delete process.env.OPENAI_BASE_URL
delete process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS
delete process.env.OPENAI_MODEL

// The openai-route catalog entries preserve the true 1.05M window,
// same as the 5.6 family.
for (const model of ['gpt-6-astra', 'gpt-6.1-sol', 'gpt-6-sol', 'gpt-6-luna']) {
expect(getContextWindowForModel(model)).toBe(1_050_000)
}
})

test('gpt-5.4 family uses provider-specific context and output caps', () => {
process.env.CLAUDE_CODE_USE_OPENAI = '1'
delete process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS
Expand Down
Loading
Loading