Skip to content
Closed
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
16 changes: 12 additions & 4 deletions docs-site/src/content/docs/guides/codex-app-models.md
Original file line number Diff line number Diff line change
Expand Up @@ -341,10 +341,18 @@ Desktop if it still has the old list, select the routed model, then choose **Ult
in its speed menu. Standard remains the default.

Only an eligible `service-tier` wire with an explicit `ultrafast → ultrafast` mapping adds
this choice. Ordinary Fast support, a GPT-like name, native/account rows and combo aliases
do not enable it. Removing the mapping or disabling that model's service-tier support
removes the generated choice from both the catalog and cache on the next successful sync.
The separate `ultraFastTier` setting still only retains hand-supplied catalog tiers.
this choice to routed rows; ordinary Fast support or a GPT-like name is insufficient.
That routed mapping does not apply to native/account rows or combo aliases. Removing the
mapping or disabling that model's service-tier support removes the generated routed choice
from both the catalog and cache on the next successful sync.
The active declaration also controls the Ultra Fast choice when Codex loads its model list over HTTP.

For routed rows, `ultraFastTier` retains independently supplied Ultra Fast tiers from the
same exact persisted row; generated routed tiers are not treated as operator-supplied.
For bare native `gpt-6-astra` / `gpt-6.1-sol` rows and their trusted account-bound clones,
enabling `ultraFastTier` also generates a missing Ultrafast choice without changing the
native default tier. Disabling it withdraws only native tier metadata tracked as generated;
independently supplied or edited native tier descriptors are preserved.

[OpenAI's API guide](https://developers.openai.com/api/docs/guides/ultrafast-mode) documents
Ultrafast for GPT-6 Astra and GPT-6.1 Sol. API access and
Expand Down
18 changes: 13 additions & 5 deletions docs-site/src/content/docs/ko/guides/codex-app-models.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,11 +198,19 @@ opencodex의 Fast 모드를 **Auto**로 유지하세요(`fastMode` 생략). Fast
목록이 남아 있다면 Codex 데스크톱을 다시 시작한 뒤 라우팅 모델의 속도 메뉴에서 **Ultra Fast**를
선택하세요. 기본 속도는 Standard로 유지됩니다.

사용 가능한 `service-tier` 전송 경로와 명시적인 `ultrafast → ultrafast` 매핑이 있어야 합니다.
일반 Fast 지원, GPT와 비슷한 이름, 네이티브·계정별 항목, combo 별칭만으로는 추가되지 않습니다.
매핑을 제거하거나 모델의 service-tier 지원을 끄면 다음 동기화 성공 시 카탈로그와 캐시에서
자동 생성된 선택지도 제거됩니다. 별도의 `ultraFastTier` 설정은 기존처럼 사용자가 직접 넣은
카탈로그 tier를 보존하는 용도입니다.
이 선택지를 라우팅 항목에 자동 생성하려면 사용 가능한 `service-tier` 전송 경로와 명시적인
`ultrafast → ultrafast` 매핑이 있어야 합니다. 일반 Fast 지원이나 GPT와 비슷한 이름만으로는
추가되지 않습니다. 이 라우팅 매핑은 네이티브·계정별 네이티브 항목이나 combo 별칭에는
적용되지 않습니다. 매핑을 제거하거나 모델의 service-tier 지원을 끄면 다음 동기화 성공 시
카탈로그와 캐시에서 자동 생성된 라우팅 선택지도 제거됩니다.
Codex가 HTTP로 모델 목록을 불러올 때도 현재 적용 중인 설정의 동일한 선언이 Ultra Fast 선택지를 결정합니다.

라우팅 항목에서는 `ultraFastTier`가 `provider/model` 식별자가 정확히 일치하는 저장된 항목에 사용자가
별도로 넣은 Ultra Fast tier를 보존합니다. 자동 생성된 라우팅 tier는 사용자 제공 tier로
취급하지 않습니다. bare native `gpt-6-astra` / `gpt-6.1-sol` 항목과 신뢰된 계정별 복제
항목에서는 `ultraFastTier`를 켜면 네이티브 기본 tier를 바꾸지 않고 누락된 Ultrafast 선택지도
생성합니다. 끄면 자동 생성한 것으로 추적되는 네이티브 tier 메타데이터만 제거하며,
사용자가 별도로 넣거나 수정한 네이티브 tier 항목은 보존합니다.

[OpenAI API 문서](https://developers.openai.com/api/docs/guides/ultrafast-mode)는 GPT-6 Astra와
GPT-6.1 Sol의 Ultrafast 지원을 안내합니다. API 사용 권한과
Expand Down
2 changes: 1 addition & 1 deletion scripts/test-layout/layout.json
Original file line number Diff line number Diff line change
Expand Up @@ -1749,7 +1749,7 @@
"tray-proxy-deadline.test.ts": "windows",
"tray-proxy.test.ts": "windows",
"types-barrel-identity.test.ts": "config",
"ultrafast-tier-honesty.test.ts": "codex-integration", "configured-ultrafast-picker.test.ts": "codex-integration", "native-ultrafast-picker.test.ts": "codex-integration",
"ultrafast-tier-honesty.test.ts": "codex-integration", "configured-ultrafast-picker.test.ts": "codex-integration", "native-ultrafast-picker.test.ts": "codex-integration", "ultrafast-live-catalog.test.ts": "codex-integration",
"umans-provider.test.ts": "providers",
"uninstall.test.ts": "cli",
"update-async-routes.test.ts": "server",
Expand Down
10 changes: 9 additions & 1 deletion src/codex/catalog/build-entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ import { NATIVE_RESERVE_MODEL } from "./native-models";
import { isReserveCatalogProjection, type ReserveCatalogProjection } from "./reserve";
import { deriveEntry, finishUpstreamNativeEntry, isExactComboCatalogEntry } from "./derive-entry";
import { PICKER_ORDER_PRIORITY_BASE, SPAWN_PRIORITY_FIELD } from "./subagent-roster";
import { applyConfiguredUltraFast } from "./configured-tiers";
import { applyConfiguredUltraFast, configuredUltraFastSlugs } from "./configured-tiers";
import { applyNativeUltraFastTier } from "./native-ultrafast";

export interface ObservedCatalogEntryBuildInput {
Expand Down Expand Up @@ -111,6 +111,14 @@ export function buildCatalogEntries(
accountNativeSlugs,
accountNativeSlugsBySelector,
});
// Live discovery does not run the disk merge; use the same captured declarations and row fences.
const configured = configuredUltraFastSlugs(goModels);
for (const entry of entries) {
if (typeof entry.slug !== "string" || !entry.slug.includes("/")
|| isNativeAliasCatalogEntry(entry) || isExactComboCatalogEntry(entry, exactComboSlugs)
|| trustedAccountBoundNativeCatalogSlug(entry) !== undefined) continue;
applyConfiguredUltraFast(entry, undefined, configured.has(entry.slug), false);
}
applyFullModelPickerOrder(entries, modelPickerOrder);
return entries;
}
Expand Down
2 changes: 1 addition & 1 deletion src/codex/catalog/configured-tiers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export function supportsConfiguredUltraFast(policy: ResolvedFastPolicy | undefin
&& policy.fastWire.canonicalToWire.ultrafast === "ultrafast";
}

/** Carry captured capability to the pure merge without treating persisted metadata as authority. */
/** Carry captured capability to catalog projection without treating persisted metadata as authority. */
export function configuredUltraFastSlugs(models: readonly CatalogModel[]): ReadonlySet<string> {
const declarations = new Map<string, boolean>();
for (const model of models) {
Expand Down
2 changes: 1 addition & 1 deletion structure/catalog.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ to the native default singleton. A default must belong to the projected list. Fu
is still not inferred from a GPT name. Stored configuration and native capability maps are
unchanged. Request-time native effort clamps remain canonical-forward only.

The shared merge uses `src/codex/catalog/configured-tiers.ts` to project Ultra Fast into Codex's native speed picker only for an eligible captured service-tier wire explicitly mapping `ultrafast` to `ultrafast`. Both writers pass current exact routed slugs; templates, native/account rows and combos cannot grant the capability. Generated choices carry an exact-slug ownership marker so removing the declaration also removes stale generated tiers, even with operator retention enabled; publication leaves the default unchanged. Coverage: `tests/codex-integration/configured-ultrafast-picker.test.ts`.
The public builder in `src/codex/catalog/build-entries.ts` and shared merge use `src/codex/catalog/configured-tiers.ts` to project Ultra Fast only for an eligible captured service-tier wire explicitly mapping `ultrafast` to `ultrafast`. Live `/v1/models?client_version=...` discovery and both writers use current exact routed slugs; conflicting duplicates, templates, native/account rows and combos cannot grant capability. Live rows import no persisted operator tiers. Generated choices carry an exact-slug ownership marker so declaration withdrawal removes stale generated tiers even with operator retention enabled; publication leaves the default unchanged. Coverage: `tests/codex-integration/configured-ultrafast-picker.test.ts` and `tests/codex-integration/ultrafast-live-catalog.test.ts`.
Separately, `src/codex/catalog/operator-tiers.ts` preserves a hand-supplied Ultra Fast tier from the exact persisted routed row only when `ultraFastTier` is enabled. Baselines and native templates cannot supply it. Current configured capability takes precedence; previously generated tiers cannot become operator evidence. Both overlays run after admission, preserve Fast policy and never create rows. Disabling `ultraFastTier` removes only independently supplied operator tiers; disabling the wire declaration removes generated ones. Operator defaults are retained only alongside an independently supplied tier declaration. Coverage: `tests/codex-integration/ultrafast-tier-honesty.test.ts`.
Native rows are the one place the `ultraFastTier` flag itself adds a tier. `src/codex/catalog/native-ultrafast.ts` appends the documented `{ id: "ultrafast", name: "Ultrafast" }` descriptor to `service_tiers` and `"ultrafast"` to `additional_speed_tiers` on bare and trusted account-bound `gpt-6-astra` and `gpt-6.1-sol` rows, after the existing Fast tier and only when no Ultrafast entry is present. Codex reads the picker from `model_catalog_json` instead of ChatGPT's plan-aware live list, so without it Desktop never offers the choice. Each field it fills is recorded in the row's `opencodex_generated_service_tiers` marker, and disabling withdraws only marked fields (and only the unedited descriptor), so an operator-written native entry survives even when its text matches. Aliases, combos, reserve projections and other models are untouched, and the default tier never changes. Coverage: `tests/codex-integration/native-ultrafast-picker.test.ts`.

Expand Down
135 changes: 135 additions & 0 deletions tests/codex-integration/ultrafast-live-catalog.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { describe, expect, test } from "bun:test";
import { spawnSync } from "node:child_process";
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { buildCatalogEntries } from "../../src/codex/catalog/build-entries";
import { applyProviderConfigHints } from "../../src/codex/catalog/model-hints";
import type { CatalogModel, RawEntry } from "../../src/codex/catalog/parsing";
import type { OcxProviderConfig } from "../../src/types";
import { repoRoot } from "../helpers/repo-root";

const slug = "gateway/task-model";

/** Derive the declaration from provider policy, as live discovery does. */
function model(overrides: Partial<OcxProviderConfig> = {}): CatalogModel {
return applyProviderConfigHints("gateway", {
adapter: "openai-responses", baseUrl: "https://provider.invalid/v1", liveModels: false,
models: ["task-model"], modelSupportsServiceTier: { "task-model": true },
fastWire: { kind: "service-tier", canonicalToWire: { priority: "priority", ultrafast: "ultrafast" }, foreignCallerTiers: "verbatim" },
...overrides,
}, { provider: "gateway", id: "task-model" });
}

/** Use the public builder called by GET /v1/models?client_version, without a disk merge. */
function build(models: CatalogModel[], template: RawEntry | null = null): RawEntry[] {
return buildCatalogEntries(template, [], models);
}

describe("live Codex catalog Ultra Fast", () => {
test("the direct builder publishes the choice without changing its default or inputs", () => {
const models = [model()];
const before = structuredClone(models);
const first = build(models);
const row = first.find(entry => entry.slug === slug)!;
expect(row.service_tiers.map((tier: { id: string }) => tier.id)).toEqual(["priority", "ultrafast"]);
expect(row.additional_speed_tiers).toEqual(["fast", "ultrafast"]);
expect(row.default_service_tier).toBeNull();
expect(row.service_tier).toBeUndefined();
expect(row.opencodex_managed_ultrafast).toBe(slug);
expect(build(models)).toEqual(first);
expect(models).toEqual(before);
});

test.each([
["ordinary Fast", { fastWire: undefined }],
["model opt-out", { modelSupportsServiceTier: { "task-model": false } }],
["wrong wire tier", { fastWire: { kind: "service-tier", canonicalToWire: { ultrafast: "priority" }, foreignCallerTiers: "verbatim" } }],
] as [string, Partial<OcxProviderConfig>][])("%s cannot borrow Ultra Fast from a template", (_label, override) => {
const template = { slug: "gpt-5.5", service_tiers: [{ id: "ultrafast", name: "Stale" }], additional_speed_tiers: ["ultrafast"] };
const row = build([model(override)], template).find(entry => entry.slug === slug)!;
expect(row.service_tiers?.some((tier: { id: string }) => tier.id === "ultrafast") ?? false).toBe(false);
expect(row.additional_speed_tiers ?? []).not.toContain("ultrafast");
expect(row.opencodex_managed_ultrafast).toBeUndefined();
});

test.each([false, true])("conflicting duplicate declarations fail closed in either order: %s", reverse => {
const supported = model();
const unsupported = { ...supported, supportsUltraFastTier: false };
const rows = build(reverse ? [unsupported, supported] : [supported, unsupported]);
expect(rows.filter(row => row.slug === slug).length).toBeGreaterThan(0);
for (const row of rows.filter(row => row.slug === slug)) {
expect(row.additional_speed_tiers ?? []).not.toContain("ultrafast");
expect(row.opencodex_managed_ultrafast).toBeUndefined();
}
});

test("exact routed aliases receive the choice while combos and native/account rows keep their policy", () => {
const supported = model();
const combo = { ...supported, provider: "combo", id: "task-combo", alias: "friendly-combo", nativeAlias: true };
const entries = buildCatalogEntries(null, ["gpt-6.1-sol"], [
{ ...supported, alias: "gateway/friendly" }, combo,
], undefined, false, "default", new Set(["friendly-combo"]), ["main"]);
expect(entries.find(row => row.slug === "gateway/friendly")!.additional_speed_tiers).toEqual(["fast", "ultrafast"]);
for (const nativeSlug of ["friendly-combo", "gpt-6.1-sol", "main/gpt-6.1-sol"]) {
const row = entries.find(entry => entry.slug === nativeSlug)!;
expect(row).toBeDefined();
expect(row.opencodex_managed_ultrafast).toBeUndefined();
}
});
});

test("the real HTTP catalog repeatedly publishes only the declared routed tier", () => {
const root = mkdtempSync(join(tmpdir(), "ocx-ultrafast-http-"));
const codexHome = join(root, "codex");
const ocxHome = join(root, "ocx");
mkdirSync(codexHome); mkdirSync(ocxHome);
writeFileSync(join(codexHome, "config.toml"), 'model_catalog_json = "opencodex-catalog.json"\n');
writeFileSync(join(codexHome, "opencodex-catalog.json"), JSON.stringify({ models: [
{ slug: "gpt-5.5", visibility: "list", base_instructions: "You are Codex." },
] }));
writeFileSync(join(ocxHome, "config.json"), JSON.stringify({
port: 0, hostname: "127.0.0.1", defaultProvider: "gateway", ultraFastTier: true,
providers: { gateway: {
adapter: "openai-responses", baseUrl: "https://provider.invalid/v1", apiKey: "fixture-key", liveModels: false,
models: ["task-model", "ordinary-model"], modelSupportsServiceTier: { "task-model": true },
fastWire: { kind: "service-tier", canonicalToWire: { priority: "priority", ultrafast: "ultrafast" }, foreignCallerTiers: "verbatim" },
} },
}));
const script = `
const { startServer } = require("./src/server");
const server = startServer(0);
const passes = [];
try {
for (let read = 0; read < 2; read++) {
const response = await fetch(new URL("/v1/models?client_version=0.160.0", server.url), { signal: AbortSignal.timeout(10000) });
if (response.status !== 200) throw new Error("HTTP status " + response.status);
const body = await response.json();
passes.push(body.models.filter(row => row.slug.startsWith("gateway/")));
}
console.log("ULTRAFAST_HTTP_RESULT=" + JSON.stringify(passes));
} finally { await server.stop(true); }
`;
try {
const result = spawnSync(process.execPath, ["--eval", script], {
cwd: repoRoot(), env: { ...process.env, CODEX_HOME: codexHome, OPENCODEX_HOME: ocxHome },
encoding: "utf8", maxBuffer: 4 * 1024 * 1024, timeout: 40_000,
});
expect({ status: result.status, stderr: result.stderr }).toMatchObject({ status: 0 });
const receipt = result.stdout.split("\n").find(line => line.startsWith("ULTRAFAST_HTTP_RESULT="));
expect(receipt).toBeDefined();
const passes = JSON.parse(receipt!.slice("ULTRAFAST_HTTP_RESULT=".length)) as RawEntry[][];
expect(passes).toHaveLength(2);
expect(passes[1]).toEqual(passes[0]);
for (const rows of passes) {
const row = rows.find(entry => entry.slug === slug)!;
expect(row).toBeDefined();
expect(row.service_tiers.map((tier: { id: string }) => tier.id)).toEqual(["priority", "ultrafast"]);
expect(row.additional_speed_tiers).toEqual(["fast", "ultrafast"]);
expect(row.default_service_tier).toBeNull();
const ordinary = rows.find(entry => entry.slug === "gateway/ordinary-model")!;
expect(ordinary).toBeDefined();
expect(ordinary.additional_speed_tiers ?? []).not.toContain("ultrafast");
}
} finally { rmSync(root, { recursive: true, force: true }); }
}, 45_000);
2 changes: 1 addition & 1 deletion tests/fixtures/test-layout-expected.json
Original file line number Diff line number Diff line change
Expand Up @@ -1126,7 +1126,7 @@
"translator-budget.test.ts": "adapters", "translator-budget-lifetime.test.ts": "adapters", "catalog-async-lifetime.test.ts": "codex-integration", "transport-null-body.test.ts": "lib",
"tray-proxy-deadline.test.ts": "windows", "tray-proxy.test.ts": "windows",
"types-barrel-identity.test.ts": "config", "ultrafast-tier-honesty.test.ts": "codex-integration",
"configured-ultrafast-picker.test.ts": "codex-integration",
"configured-ultrafast-picker.test.ts": "codex-integration", "ultrafast-live-catalog.test.ts": "codex-integration",
"umans-provider.test.ts": "providers", "uninstall.test.ts": "cli",
"update-async-routes.test.ts": "server", "update-badge.test.ts": "update",
"update-bun-ownership-lease.test.ts": "update", "update-desktop-badge.test.ts": "update",
Expand Down
Loading