Skip to content

Commit 750d97e

Browse files
authored
feat: add openai model info configuration (openai#551)
In reference to [Issue 548](openai#548) - part 1.
1 parent 12bc2dc commit 750d97e

File tree

4 files changed

+301
-3
lines changed

4 files changed

+301
-3
lines changed

codex-cli/src/utils/model-info.ts

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
export type ModelInfo = {
2+
/** The human-readable label for this model */
3+
label: string;
4+
/** The max context window size for this model */
5+
maxContextLength: number;
6+
};
7+
8+
export type SupportedModelId = keyof typeof openAiModelInfo;
9+
export const openAiModelInfo = {
10+
"o1-pro-2025-03-19": {
11+
label: "o1 Pro (2025-03-19)",
12+
maxContextLength: 200000,
13+
},
14+
"o3": {
15+
label: "o3",
16+
maxContextLength: 200000,
17+
},
18+
"o3-2025-04-16": {
19+
label: "o3 (2025-04-16)",
20+
maxContextLength: 200000,
21+
},
22+
"o4-mini": {
23+
label: "o4 Mini",
24+
maxContextLength: 200000,
25+
},
26+
"gpt-4.1-nano": {
27+
label: "GPT-4.1 Nano",
28+
maxContextLength: 1000000,
29+
},
30+
"gpt-4.1-nano-2025-04-14": {
31+
label: "GPT-4.1 Nano (2025-04-14)",
32+
maxContextLength: 1000000,
33+
},
34+
"o4-mini-2025-04-16": {
35+
label: "o4 Mini (2025-04-16)",
36+
maxContextLength: 200000,
37+
},
38+
"gpt-4": {
39+
label: "GPT-4",
40+
maxContextLength: 8192,
41+
},
42+
"o1-preview-2024-09-12": {
43+
label: "o1 Preview (2024-09-12)",
44+
maxContextLength: 128000,
45+
},
46+
"gpt-4.1-mini": {
47+
label: "GPT-4.1 Mini",
48+
maxContextLength: 1000000,
49+
},
50+
"gpt-3.5-turbo-instruct-0914": {
51+
label: "GPT-3.5 Turbo Instruct (0914)",
52+
maxContextLength: 4096,
53+
},
54+
"gpt-4o-mini-search-preview": {
55+
label: "GPT-4o Mini Search Preview",
56+
maxContextLength: 128000,
57+
},
58+
"gpt-4.1-mini-2025-04-14": {
59+
label: "GPT-4.1 Mini (2025-04-14)",
60+
maxContextLength: 1000000,
61+
},
62+
"chatgpt-4o-latest": {
63+
label: "ChatGPT-4o Latest",
64+
maxContextLength: 128000,
65+
},
66+
"gpt-3.5-turbo-1106": {
67+
label: "GPT-3.5 Turbo (1106)",
68+
maxContextLength: 16385,
69+
},
70+
"gpt-4o-search-preview": {
71+
label: "GPT-4o Search Preview",
72+
maxContextLength: 128000,
73+
},
74+
"gpt-4-turbo": {
75+
label: "GPT-4 Turbo",
76+
maxContextLength: 128000,
77+
},
78+
"gpt-4o-realtime-preview-2024-12-17": {
79+
label: "GPT-4o Realtime Preview (2024-12-17)",
80+
maxContextLength: 128000,
81+
},
82+
"gpt-3.5-turbo-instruct": {
83+
label: "GPT-3.5 Turbo Instruct",
84+
maxContextLength: 4096,
85+
},
86+
"gpt-3.5-turbo": {
87+
label: "GPT-3.5 Turbo",
88+
maxContextLength: 16385,
89+
},
90+
"gpt-4-turbo-preview": {
91+
label: "GPT-4 Turbo Preview",
92+
maxContextLength: 128000,
93+
},
94+
"gpt-4o-mini-search-preview-2025-03-11": {
95+
label: "GPT-4o Mini Search Preview (2025-03-11)",
96+
maxContextLength: 128000,
97+
},
98+
"gpt-4-0125-preview": {
99+
label: "GPT-4 (0125) Preview",
100+
maxContextLength: 128000,
101+
},
102+
"gpt-4o-2024-11-20": {
103+
label: "GPT-4o (2024-11-20)",
104+
maxContextLength: 128000,
105+
},
106+
"o3-mini": {
107+
label: "o3 Mini",
108+
maxContextLength: 200000,
109+
},
110+
"gpt-4o-2024-05-13": {
111+
label: "GPT-4o (2024-05-13)",
112+
maxContextLength: 128000,
113+
},
114+
"gpt-4-turbo-2024-04-09": {
115+
label: "GPT-4 Turbo (2024-04-09)",
116+
maxContextLength: 128000,
117+
},
118+
"gpt-3.5-turbo-16k": {
119+
label: "GPT-3.5 Turbo 16k",
120+
maxContextLength: 16385,
121+
},
122+
"o3-mini-2025-01-31": {
123+
label: "o3 Mini (2025-01-31)",
124+
maxContextLength: 200000,
125+
},
126+
"o1-preview": {
127+
label: "o1 Preview",
128+
maxContextLength: 128000,
129+
},
130+
"o1-2024-12-17": {
131+
label: "o1 (2024-12-17)",
132+
maxContextLength: 128000,
133+
},
134+
"gpt-4-0613": {
135+
label: "GPT-4 (0613)",
136+
maxContextLength: 8192,
137+
},
138+
"o1": {
139+
label: "o1",
140+
maxContextLength: 128000,
141+
},
142+
"o1-pro": {
143+
label: "o1 Pro",
144+
maxContextLength: 200000,
145+
},
146+
"gpt-4.5-preview": {
147+
label: "GPT-4.5 Preview",
148+
maxContextLength: 128000,
149+
},
150+
"gpt-4.5-preview-2025-02-27": {
151+
label: "GPT-4.5 Preview (2025-02-27)",
152+
maxContextLength: 128000,
153+
},
154+
"gpt-4o-search-preview-2025-03-11": {
155+
label: "GPT-4o Search Preview (2025-03-11)",
156+
maxContextLength: 128000,
157+
},
158+
"gpt-4o": {
159+
label: "GPT-4o",
160+
maxContextLength: 128000,
161+
},
162+
"gpt-4o-mini": {
163+
label: "GPT-4o Mini",
164+
maxContextLength: 128000,
165+
},
166+
"gpt-4o-2024-08-06": {
167+
label: "GPT-4o (2024-08-06)",
168+
maxContextLength: 128000,
169+
},
170+
"gpt-4.1": {
171+
label: "GPT-4.1",
172+
maxContextLength: 1000000,
173+
},
174+
"gpt-4.1-2025-04-14": {
175+
label: "GPT-4.1 (2025-04-14)",
176+
maxContextLength: 1000000,
177+
},
178+
"gpt-4o-mini-2024-07-18": {
179+
label: "GPT-4o Mini (2024-07-18)",
180+
maxContextLength: 128000,
181+
},
182+
"o1-mini": {
183+
label: "o1 Mini",
184+
maxContextLength: 128000,
185+
},
186+
"gpt-3.5-turbo-0125": {
187+
label: "GPT-3.5 Turbo (0125)",
188+
maxContextLength: 16385,
189+
},
190+
"o1-mini-2024-09-12": {
191+
label: "o1 Mini (2024-09-12)",
192+
maxContextLength: 128000,
193+
},
194+
"gpt-4-1106-preview": {
195+
label: "GPT-4 (1106) Preview",
196+
maxContextLength: 128000,
197+
},
198+
} as const satisfies Record<string, ModelInfo>;

codex-cli/src/utils/model-utils.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { ResponseItem } from "openai/resources/responses/responses.mjs";
22

33
import { approximateTokensUsed } from "./approximate-tokens-used.js";
44
import { getBaseUrl, getApiKey } from "./config";
5+
import { type SupportedModelId, openAiModelInfo } from "./model-info.js";
56
import OpenAI from "openai";
67

78
const MODEL_LIST_TIMEOUT_MS = 2_000; // 2 seconds
@@ -89,10 +90,12 @@ export async function isModelSupportedForResponses(
8990
}
9091

9192
/** Returns the maximum context length (in tokens) for a given model. */
92-
function maxTokensForModel(model: string): number {
93-
// TODO: These numbers are best‑effort guesses and provide a basis for UI percentages. They
94-
// should be provider & model specific instead of being wild guesses.
93+
export function maxTokensForModel(model: string): number {
94+
if (model in openAiModelInfo) {
95+
return openAiModelInfo[model as SupportedModelId].maxContextLength;
96+
}
9597

98+
// fallback to heuristics for models not in the registry
9699
const lower = model.toLowerCase();
97100
if (lower.includes("32k")) {
98101
return 32000;

codex-cli/tests/model-info.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { describe, expect, test } from "vitest";
2+
import { openAiModelInfo } from "../src/utils/model-info";
3+
4+
describe("Model Info", () => {
5+
test("supportedModelInfo contains expected models", () => {
6+
expect(openAiModelInfo).toHaveProperty("gpt-4o");
7+
expect(openAiModelInfo).toHaveProperty("gpt-4.1");
8+
expect(openAiModelInfo).toHaveProperty("o3");
9+
});
10+
11+
test("model info entries have required properties", () => {
12+
Object.entries(openAiModelInfo).forEach(([_, info]) => {
13+
expect(info).toHaveProperty("label");
14+
expect(info).toHaveProperty("maxContextLength");
15+
expect(typeof info.label).toBe("string");
16+
expect(typeof info.maxContextLength).toBe("number");
17+
});
18+
});
19+
});
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { describe, test, expect } from "vitest";
2+
import {
3+
calculateContextPercentRemaining,
4+
maxTokensForModel,
5+
} from "../src/utils/model-utils";
6+
import { openAiModelInfo } from "../src/utils/model-info";
7+
import type { ResponseItem } from "openai/resources/responses/responses.mjs";
8+
9+
describe("Model Utils", () => {
10+
describe("openAiModelInfo", () => {
11+
test("model info entries have required properties", () => {
12+
Object.entries(openAiModelInfo).forEach(([_, info]) => {
13+
expect(info).toHaveProperty("label");
14+
expect(info).toHaveProperty("maxContextLength");
15+
expect(typeof info.label).toBe("string");
16+
expect(typeof info.maxContextLength).toBe("number");
17+
});
18+
});
19+
});
20+
21+
describe("maxTokensForModel", () => {
22+
test("returns correct token limit for known models", () => {
23+
const knownModel = "gpt-4o";
24+
const expectedTokens = openAiModelInfo[knownModel].maxContextLength;
25+
expect(maxTokensForModel(knownModel)).toBe(expectedTokens);
26+
});
27+
28+
test("handles models with size indicators in their names", () => {
29+
expect(maxTokensForModel("some-model-32k")).toBe(32000);
30+
expect(maxTokensForModel("some-model-16k")).toBe(16000);
31+
expect(maxTokensForModel("some-model-8k")).toBe(8000);
32+
expect(maxTokensForModel("some-model-4k")).toBe(4000);
33+
});
34+
35+
test("defaults to 128k for unknown models not in the registry", () => {
36+
expect(maxTokensForModel("completely-unknown-model")).toBe(128000);
37+
});
38+
});
39+
40+
describe("calculateContextPercentRemaining", () => {
41+
test("returns 100% for empty items", () => {
42+
const result = calculateContextPercentRemaining([], "gpt-4o");
43+
expect(result).toBe(100);
44+
});
45+
46+
test("calculates percentage correctly for non-empty items", () => {
47+
const mockItems: Array<ResponseItem> = [
48+
{
49+
id: "test-id",
50+
type: "message",
51+
role: "user",
52+
status: "completed",
53+
content: [
54+
{
55+
type: "input_text",
56+
text: "A".repeat(
57+
openAiModelInfo["gpt-4o"].maxContextLength * 0.25 * 4,
58+
),
59+
},
60+
],
61+
} as ResponseItem,
62+
];
63+
64+
const result = calculateContextPercentRemaining(mockItems, "gpt-4o");
65+
expect(result).toBeCloseTo(75, 0);
66+
});
67+
68+
test("handles models that are not in the registry", () => {
69+
const mockItems: Array<ResponseItem> = [];
70+
71+
const result = calculateContextPercentRemaining(
72+
mockItems,
73+
"unknown-model",
74+
);
75+
expect(result).toBe(100);
76+
});
77+
});
78+
});

0 commit comments

Comments
 (0)