Skip to content

Commit 14cc938

Browse files
committed
Read max_completion_tokens list through the OpenAI preset entry
openAISourceQuirks now reads the api path entry's maxCompletionTokensModels field instead of the static const, so the entry is the single source of truth. A new union test pins flagged plus explicit-exempt against the preset model list so an undecided model fails loudly.
1 parent a89f8c3 commit 14cc938

3 files changed

Lines changed: 32 additions & 7 deletions

File tree

‎packages/first-class-providers/src/providers.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ export const OPENAI_API_BASE_URL = "https://api.openai.com/v1";
2424
* Preset models whose first-party endpoint rejects `max_tokens` and requires
2525
* `max_completion_tokens`. Explicit per-model list: adding a model here
2626
* declares its own requirement, never inferred from name prefixes. gpt-4.1
27-
* is non-reasoning and stays on `max_tokens`.
27+
* is non-reasoning and stays on `max_tokens`. This const is only the api
28+
* path entry's initial value — runtime reads the entry's
29+
* `maxCompletionTokensModels` field, so that field is the source of truth.
2830
*/
2931
export const OPENAI_API_MAX_COMPLETION_TOKENS_MODELS: readonly string[] = [
3032
"gpt-6-astra",

‎src/config/inference-sources.test.ts‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,21 @@ describe("OpenAI reasoning max_completion_tokens quirk (CL-7785)", () => {
129129
}
130130
});
131131

132+
test("every preset model has an explicit quirk decision", () => {
133+
const flagged = new Set(openaiApi?.maxCompletionTokensModels ?? []);
134+
// Explicit max_tokens decision: non-reasoning preset models stay on
135+
// max_tokens. Adding a preset model requires a decision here AND in the
136+
// preset's maxCompletionTokensModels — the union below fails loudly
137+
// otherwise instead of silently sending max_tokens.
138+
const explicitMaxTokensModels = new Set(["gpt-4.1"]);
139+
expect([...flagged, ...explicitMaxTokensModels].sort()).toEqual(
140+
[...new Set(presetModels)].sort(),
141+
);
142+
expect([...flagged].filter((m) => explicitMaxTokensModels.has(m))).toEqual(
143+
[],
144+
);
145+
});
146+
132147
test("reasoning preset models emit max_completion_tokens, never max_tokens", () => {
133148
for (const model of openaiApi?.maxCompletionTokensModels ?? []) {
134149
const body = wireBody(model, presetBaseURL);

‎src/config/inference-sources.ts‎

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
} from "./index.js";
1212
import {
1313
OPENAI_API_BASE_URL,
14-
OPENAI_API_MAX_COMPLETION_TOKENS_MODELS,
14+
firstClassProviderById,
1515
} from "../../packages/first-class-providers/src/index.js";
1616
import { normalizeOpenAICompatibleBaseURL, type Settings } from "./settings.js";
1717
import {
@@ -42,10 +42,18 @@ function catalogEntry(
4242

4343
// First-party OpenAI reasoning models reject `max_tokens` and require
4444
// `max_completion_tokens`. The requirement is declared per model on the
45-
// first-class OpenAI API-key preset — never inferred from name prefixes —
46-
// and the quirk attaches to the source actually in use: it follows the
47-
// first-party endpoint, so relays serving the same model names through the
48-
// same adapter keep `max_tokens`.
45+
// first-class OpenAI API-key path's `maxCompletionTokensModels` field — never
46+
// inferred from name prefixes — and read here through that entry, so the
47+
// entry stays the single source of truth. The quirk attaches to the source
48+
// actually in use: it follows the first-party endpoint, so relays serving
49+
// the same model names through the same adapter keep `max_tokens`.
50+
function openAIAPIPathMaxCompletionTokensModels(): readonly string[] {
51+
return (
52+
firstClassProviderById("openai")?.paths?.find((p) => p.id === "api")
53+
?.maxCompletionTokensModels ?? []
54+
);
55+
}
56+
4957
function openAISourceQuirks(
5058
baseURL: string,
5159
model: string,
@@ -54,7 +62,7 @@ function openAISourceQuirks(
5462
if (normalized !== normalizeOpenAICompatibleBaseURL(OPENAI_API_BASE_URL)) {
5563
return undefined;
5664
}
57-
if (!OPENAI_API_MAX_COMPLETION_TOKENS_MODELS.includes(model)) {
65+
if (!openAIAPIPathMaxCompletionTokensModels().includes(model)) {
5866
return undefined;
5967
}
6068
return { maxTokensField: "max_completion_tokens" };

0 commit comments

Comments
 (0)