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
2 changes: 1 addition & 1 deletion src/codex/catalog/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -548,7 +548,7 @@ function hasNativeCatalogRowShape(entry: RawEntry): boolean {
return typeof entry.base_instructions === "string"
&& entry.base_instructions.length > 0
&& (typeof entry.comp_hash === "string" || entry.comp_hash === null)
&& entry.shell_type === "shell_command"
&& (entry.shell_type === "unified_exec" || entry.shell_type === "shell_command")
&& Array.isArray(levels)
&& levels.length > 0
&& levels.every(level => typeof level === "object" && level !== null
Expand Down
7 changes: 7 additions & 0 deletions src/codex/catalog/parsing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,13 @@ export function ensureStrictCatalogFields(
entry: RawEntry,
options: { preserveExactInputModalities?: boolean; isRouted?: boolean } = {},
): RawEntry {
if (entry.shell_type === "default" || entry.shell_type === "local" || entry.shell_type === "shell_command") {
entry.shell_type = "unified_exec";
Comment on lines +434 to +435

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep the emitted shell type compatible with older Codex clients

When a user runs Codex 0.150 or earlier, rewriting every legacy shell_type to the newly introduced unified_exec value makes the generated catalog incompatible with that client's closed enum, so a subsequent ocx sync can cause Codex to reject the catalog rather than merely disable the affected model. Since Codex 0.151 still accepts shell_command as an alias, preserve that wire value or select the spelling based on the detected client version instead of unconditionally canonicalizing all rows.

AGENTS.md reference: src/AGENTS.md:L10-L10

Useful? React with 👍 / 👎.

}
if (typeof entry.node_repl_disabled !== "boolean") entry.node_repl_disabled = false;
if (typeof entry.node_repl_auto_review_required !== "boolean") entry.node_repl_auto_review_required = false;
if (typeof entry.include_plugin_usage_instructions !== "boolean") entry.include_plugin_usage_instructions = false;
if (typeof entry.include_apps_usage_instructions !== "boolean") entry.include_apps_usage_instructions = true;
if (typeof entry.supports_reasoning_summaries !== "boolean") entry.supports_reasoning_summaries = false;
if (typeof entry.default_reasoning_summary !== "string") entry.default_reasoning_summary = "none";
if (typeof entry.support_verbosity !== "boolean") entry.support_verbosity = true;
Expand Down
2 changes: 1 addition & 1 deletion src/codex/catalog/sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ export function deriveEntry(
const isCursorFallback = isRouted && model?.provider === "cursor";
const entry: RawEntry = {
slug, display_name: routedDisplayName(slug), description: desc,
shell_type: "shell_command", visibility: "list", supported_in_api: true,
shell_type: "unified_exec", visibility: "list", supported_in_api: true,
priority, base_instructions: "You are a helpful coding assistant.",
...(isRouted
? isCursorFallback
Expand Down
48 changes: 40 additions & 8 deletions src/codex/data/upstream-models.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
"multi_agent_version": "v2",
"use_responses_lite": true,
"include_skills_usage_instructions": false,
"node_repl_disabled": false,
"node_repl_auto_review_required": false,
"include_plugin_usage_instructions": true,
"include_apps_usage_instructions": true,
"auto_review_model_override": null,
"context_window": 372000,
"max_context_window": 372000,
Expand Down Expand Up @@ -57,7 +61,7 @@
"description": "Maximum reasoning with automatic task delegation"
}
],
"shell_type": "shell_command",
"shell_type": "unified_exec",
"visibility": "list",
"minimal_client_version": "0.142.2",
"supported_in_api": true,
Expand Down Expand Up @@ -135,6 +139,10 @@
"multi_agent_version": "v2",
"use_responses_lite": true,
"include_skills_usage_instructions": false,
"node_repl_disabled": false,
"node_repl_auto_review_required": false,
"include_plugin_usage_instructions": true,
"include_apps_usage_instructions": true,
"auto_review_model_override": null,
"context_window": 372000,
"max_context_window": 372000,
Expand Down Expand Up @@ -171,7 +179,7 @@
"description": "Maximum reasoning with automatic task delegation"
}
],
"shell_type": "shell_command",
"shell_type": "unified_exec",
"visibility": "list",
"minimal_client_version": "0.142.2",
"supported_in_api": true,
Expand Down Expand Up @@ -247,6 +255,10 @@
"multi_agent_version": "v1",
"use_responses_lite": true,
"include_skills_usage_instructions": false,
"node_repl_disabled": false,
"node_repl_auto_review_required": false,
"include_plugin_usage_instructions": true,
"include_apps_usage_instructions": true,
"auto_review_model_override": null,
"context_window": 372000,
"max_context_window": 372000,
Expand Down Expand Up @@ -279,7 +291,7 @@
"description": "Maximum reasoning depth for the hardest problems"
}
],
"shell_type": "shell_command",
"shell_type": "unified_exec",
"visibility": "list",
"minimal_client_version": "0.142.2",
"supported_in_api": true,
Expand Down Expand Up @@ -355,6 +367,10 @@
"multi_agent_version": null,
"use_responses_lite": false,
"include_skills_usage_instructions": true,
"node_repl_disabled": false,
"node_repl_auto_review_required": false,
"include_plugin_usage_instructions": true,
"include_apps_usage_instructions": true,
"auto_review_model_override": null,
"context_window": 272000,
"max_context_window": 272000,
Expand Down Expand Up @@ -383,7 +399,7 @@
"description": "Extra high reasoning depth for complex problems"
}
],
"shell_type": "shell_command",
"shell_type": "unified_exec",
"visibility": "list",
"minimal_client_version": "0.124.0",
"supported_in_api": true,
Expand Down Expand Up @@ -461,6 +477,10 @@
"multi_agent_version": null,
"use_responses_lite": false,
"include_skills_usage_instructions": false,
"node_repl_disabled": false,
"node_repl_auto_review_required": false,
"include_plugin_usage_instructions": true,
"include_apps_usage_instructions": true,
"auto_review_model_override": null,
"context_window": 272000,
"max_context_window": 1000000,
Expand Down Expand Up @@ -489,7 +509,7 @@
"description": "Extra high reasoning depth for complex problems"
}
],
"shell_type": "shell_command",
"shell_type": "unified_exec",
"visibility": "list",
"minimal_client_version": "0.98.0",
"supported_in_api": true,
Expand Down Expand Up @@ -562,6 +582,10 @@
"multi_agent_version": null,
"use_responses_lite": false,
"include_skills_usage_instructions": false,
"node_repl_disabled": false,
"node_repl_auto_review_required": false,
"include_plugin_usage_instructions": true,
"include_apps_usage_instructions": true,
"auto_review_model_override": null,
"context_window": 272000,
"max_context_window": 272000,
Expand Down Expand Up @@ -590,7 +614,7 @@
"description": "Extra high reasoning depth for complex problems"
}
],
"shell_type": "shell_command",
"shell_type": "unified_exec",
"visibility": "list",
"minimal_client_version": "0.98.0",
"supported_in_api": true,
Expand Down Expand Up @@ -658,6 +682,10 @@
"multi_agent_version": null,
"use_responses_lite": false,
"include_skills_usage_instructions": false,
"node_repl_disabled": false,
"node_repl_auto_review_required": false,
"include_plugin_usage_instructions": true,
"include_apps_usage_instructions": true,
"auto_review_model_override": null,
"context_window": 272000,
"max_context_window": 272000,
Expand Down Expand Up @@ -686,7 +714,7 @@
"description": "Extra high reasoning for complex problems"
}
],
"shell_type": "shell_command",
"shell_type": "unified_exec",
"visibility": "list",
"minimal_client_version": "0.0.1",
"supported_in_api": true,
Expand Down Expand Up @@ -754,6 +782,10 @@
"multi_agent_version": null,
"use_responses_lite": false,
"include_skills_usage_instructions": false,
"node_repl_disabled": false,
"node_repl_auto_review_required": false,
"include_plugin_usage_instructions": true,
"include_apps_usage_instructions": true,
"auto_review_model_override": null,
"context_window": 272000,
"max_context_window": 1000000,
Expand Down Expand Up @@ -782,7 +814,7 @@
"description": "Extra high reasoning depth for complex problems"
}
],
"shell_type": "shell_command",
"shell_type": "unified_exec",
"visibility": "hide",
"minimal_client_version": "0.98.0",
"supported_in_api": true,
Expand Down
58 changes: 57 additions & 1 deletion tests/codex-catalog.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2734,6 +2734,20 @@ describe("Codex catalog routed normalization", () => {
expect(terra?.multi_agent_version).toBe("v2");
expect(luna?.multi_agent_version).toBe("v1");

const codex0151Contract = {
shell_type: "unified_exec",
node_repl_disabled: false,
node_repl_auto_review_required: false,
include_plugin_usage_instructions: true,
include_apps_usage_instructions: true,
};
for (const slug of ["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"]) {
expect(upstreamNativeEntry(slug)).toMatchObject(codex0151Contract);
}
for (const entry of [sol, terra, luna]) {
expect(entry).toMatchObject(codex0151Contract);
}

// ocx adaptations: client-version gate stripped; ws preference gated off by default.
for (const e of [sol, terra, luna]) {
expect(e).not.toHaveProperty("minimal_client_version");
Expand Down Expand Up @@ -3240,7 +3254,14 @@ describe("Codex catalog routed normalization", () => {
]);

const routed = rows.find(row => row.slug === "deepseek/deepseek-v4-flash");
expect(routed?.tool_mode).toBe("code_mode_only");
expect(routed).toMatchObject({
tool_mode: "code_mode_only",
shell_type: "unified_exec",
node_repl_disabled: false,
node_repl_auto_review_required: false,
include_plugin_usage_instructions: false,
include_apps_usage_instructions: true,
});
});

test("buildCatalogEntries preserves native tool mode on account-qualified rows", () => {
Expand Down Expand Up @@ -5973,3 +5994,38 @@ describe("routed rows never carry native eligibility metadata", () => {
expect(entry).not.toHaveProperty("upgrade");
});
});

describe("Codex 0.151 catalog contract fields", () => {
test("legacy shell types canonicalize while disabled remains disabled", () => {
const normalizedLegacy = ["default", "local", "shell_command"].map(shell_type =>
ensureStrictCatalogFields({ slug: "test", shell_type }).shell_type);

expect(normalizedLegacy).toEqual(["unified_exec", "unified_exec", "unified_exec"]);
expect(ensureStrictCatalogFields({ slug: "test", shell_type: "disabled" }).shell_type)
.toBe("disabled");
});

test("missing booleans receive serde defaults", () => {
expect(ensureStrictCatalogFields({ slug: "test" })).toMatchObject({
node_repl_disabled: false,
node_repl_auto_review_required: false,
include_plugin_usage_instructions: false,
include_apps_usage_instructions: true,
});
});

test("explicit per-model booleans are never overwritten by defaults", () => {
expect(ensureStrictCatalogFields({
slug: "test",
node_repl_disabled: true,
node_repl_auto_review_required: true,
include_plugin_usage_instructions: true,
include_apps_usage_instructions: false,
})).toMatchObject({
node_repl_disabled: true,
node_repl_auto_review_required: true,
include_plugin_usage_instructions: true,
include_apps_usage_instructions: false,
});
});
});
5 changes: 2 additions & 3 deletions tests/codex-tool-mode.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ describe("Codex tool mode configuration (#2106)", () => {
const deepseekEntry = entries.find(e => e.slug === "deepseek/deepseek-v4-flash");
expect(deepseekEntry).toBeDefined();
expect(deepseekEntry?.tool_mode).toBe("code_mode_only");
expect(deepseekEntry?.shell_type).toBe("shell_command");
expect(deepseekEntry?.shell_type).toBe("unified_exec");
});

test("buildCatalogEntries leaves tool_mode unset when codexToolMode is shell", () => {
Expand All @@ -70,7 +70,7 @@ describe("Codex tool mode configuration (#2106)", () => {
const deepseekEntry = entries.find(e => e.slug === "deepseek/deepseek-v4-flash");
expect(deepseekEntry).toBeDefined();
expect(deepseekEntry?.tool_mode).toBeUndefined();
expect(deepseekEntry?.shell_type).toBe("shell_command");
expect(deepseekEntry?.shell_type).toBe("unified_exec");
});

test("under shell mode with declared exec_command, undeclared-tool-guard allows exec_command", () => {
Expand Down Expand Up @@ -238,4 +238,3 @@ describe("#2503 combo derivation preserves a member's verbosity opt-out", () =>
expect(derived?.supportsVerbosity).toBeUndefined();
});
});

9 changes: 5 additions & 4 deletions tests/native-model-toggle.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -391,19 +391,20 @@ describe("native GPT model toggles (bare slugs in disabledModels)", () => {
// catalog, so the effect is advertisement in discovery, not a newly reachable route. If this
// test ever needs to flip to rejection, the fix is a real provenance signal, not a longer
// list of fields to match.
test("a full-shape hand-written row IS accepted — the check is plausibility, not provenance", () => {
const forged = [{
test("full-shape unified_exec and legacy shell_command rows are accepted", () => {
const forged = (shell_type: string) => [{
slug: "gpt-not-a-real-model",
visibility: "list",
supported_in_api: true,
base_instructions: "anything non-empty",
comp_hash: null,
shell_type: "shell_command",
shell_type,
supported_reasoning_levels: [{ effort: "high" }],
model_messages: {},
}];

expect(accountBoundNativeOpenAiSlugs(forged)).toContain("gpt-not-a-real-model");
expect(accountBoundNativeOpenAiSlugs(forged("unified_exec"))).toContain("gpt-not-a-real-model");
expect(accountBoundNativeOpenAiSlugs(forged("shell_command"))).toContain("gpt-not-a-real-model");
});

test("exact account disables hide only the matching generated picker row", () => {
Expand Down
Loading