File size: 4,804 Bytes
1f21206
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
export const OPENAI_DEFAULT_MAIN_MODEL = 'gpt-5.3-codex'
export const OPENAI_DEFAULT_SONNET_MODEL = 'gpt-5.4'
export const OPENAI_DEFAULT_HAIKU_MODEL = 'gpt-5.4-mini'
export const OPENAI_CODEX_EFFECTIVE_CONTEXT_PERCENT = 95
export const OPENAI_CODEX_STANDARD_CONTEXT_WINDOW = 272_000
export const OPENAI_CODEX_LARGE_CONTEXT_WINDOW = 1_000_000
export const OPENAI_CODEX_SPARK_CONTEXT_WINDOW = 128_000
export const OPENAI_CODEX_STANDARD_EFFECTIVE_CONTEXT_WINDOW = Math.floor(
  (OPENAI_CODEX_STANDARD_CONTEXT_WINDOW * OPENAI_CODEX_EFFECTIVE_CONTEXT_PERCENT) /
    100,
)
export const OPENAI_CODEX_LARGE_EFFECTIVE_CONTEXT_WINDOW = Math.floor(
  (OPENAI_CODEX_LARGE_CONTEXT_WINDOW * OPENAI_CODEX_EFFECTIVE_CONTEXT_PERCENT) /
    100,
)
export const OPENAI_CODEX_SPARK_EFFECTIVE_CONTEXT_WINDOW = Math.floor(
  (OPENAI_CODEX_SPARK_CONTEXT_WINDOW * OPENAI_CODEX_EFFECTIVE_CONTEXT_PERCENT) /
    100,
)

export type OpenAIModelCatalogEntry = {
  value: string
  label: string
  description: string
  descriptionForModel?: string
}

export const OPENAI_CODEX_MODEL_CATALOG: OpenAIModelCatalogEntry[] = [
  {
    value: OPENAI_DEFAULT_MAIN_MODEL,
    label: 'GPT-5.3 Codex',
    description: 'Best for coding and agentic work',
    descriptionForModel: 'GPT-5.3 Codex - best for coding and agentic work',
  },
  {
    value: OPENAI_DEFAULT_SONNET_MODEL,
    label: 'GPT-5.4',
    description: 'Strong general-purpose model',
    descriptionForModel: 'GPT-5.4 - strong general-purpose model',
  },
  {
    value: 'gpt-5.5',
    label: 'GPT-5.5',
    description: 'Latest general-purpose model',
    descriptionForModel: 'GPT-5.5 - latest general-purpose model',
  },
  {
    value: OPENAI_DEFAULT_HAIKU_MODEL,
    label: 'GPT-5.4 Mini',
    description: 'Fastest for quick tasks',
    descriptionForModel: 'GPT-5.4 Mini - fastest for quick tasks',
  },
]

export function isOpenAIResponsesModel(model: string): boolean {
  const normalized = model.trim().toLowerCase()
  return normalized.startsWith('gpt-') || /^o\d/.test(normalized)
}

export function resolveOpenAICodexModel(model: string): string {
  if (process.env.OPENAI_CODEX_MODEL?.trim()) {
    return process.env.OPENAI_CODEX_MODEL.trim()
  }

  const normalized = model.trim().toLowerCase()
  if (isOpenAIResponsesModel(normalized)) {
    return model
  }

  if (normalized.includes('haiku')) {
    return (
      process.env.OPENAI_CODEX_HAIKU_MODEL?.trim() ||
      OPENAI_DEFAULT_HAIKU_MODEL
    )
  }

  if (normalized.includes('sonnet')) {
    return (
      process.env.OPENAI_CODEX_SONNET_MODEL?.trim() ||
      OPENAI_DEFAULT_SONNET_MODEL
    )
  }

  if (normalized.includes('opus')) {
    return (
      process.env.OPENAI_CODEX_OPUS_MODEL?.trim() || OPENAI_DEFAULT_MAIN_MODEL
    )
  }

  return OPENAI_DEFAULT_MAIN_MODEL
}

export function getOpenAIModelDisplayName(model: string): string | null {
  switch (model.trim().toLowerCase()) {
    case 'gpt-5.3-codex':
      return 'GPT-5.3 Codex'
    case 'gpt-5.3-codex-spark':
      return 'GPT-5.3 Codex Spark'
    case 'gpt-5.5':
      return 'GPT-5.5'
    case 'gpt-5.4':
      return 'GPT-5.4'
    case 'gpt-5.4-mini':
      return 'GPT-5.4 Mini'
    case 'gpt-5.2':
      return 'GPT-5.2'
    case 'gpt-5.2-codex':
      return 'GPT-5.2 Codex'
    case 'gpt-5.1-codex':
      return 'GPT-5.1 Codex'
    case 'gpt-5.1-codex-max':
      return 'GPT-5.1 Codex Max'
    case 'gpt-5.1-codex-mini':
      return 'GPT-5.1 Codex Mini'
    default:
      return null
  }
}

export function getOpenAICodexContextWindowForModel(
  model: string,
): number | null {
  const normalized = model.trim().toLowerCase()

  // Codex OAuth follows the Codex app model catalog, not the public API model
  // context limits. The catalog applies effective_context_window_percent=95,
  // and the runtime /context display reports this effective window.
  if (
    normalized === 'gpt-5.4' ||
    normalized === 'gpt-5.4-pro'
  ) {
    return OPENAI_CODEX_LARGE_EFFECTIVE_CONTEXT_WINDOW
  }

  if (normalized === 'gpt-5.3-codex-spark') {
    return OPENAI_CODEX_SPARK_EFFECTIVE_CONTEXT_WINDOW
  }

  if (
    normalized === 'gpt-5.5' ||
    normalized === 'gpt-5.5-pro' ||
    normalized === 'gpt-5.4-mini' ||
    normalized === 'gpt-5.4-nano' ||
    normalized === 'gpt-5.3-codex' ||
    normalized === 'gpt-5.2' ||
    normalized === 'gpt-5.2-codex' ||
    normalized === 'gpt-5.1' ||
    normalized === 'gpt-5.1-codex' ||
    normalized === 'gpt-5.1-codex-max' ||
    normalized === 'gpt-5.1-codex-mini' ||
    normalized === 'gpt-5-codex' ||
    normalized === 'gpt-5' ||
    normalized === 'gpt-5-mini' ||
    normalized === 'gpt-5-nano'
  ) {
    return OPENAI_CODEX_STANDARD_EFFECTIVE_CONTEXT_WINDOW
  }

  return null
}

export const getOpenAIContextWindowForModel =
  getOpenAICodexContextWindowForModel