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 apps/mobile/src/lib/modelOptions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,6 @@ describe("mobile model options", () => {
});

expect(option?.capabilities?.optionDescriptors?.[0]?.id).toBe("serviceTier");
expect(option?.selection.options).toEqual([{ id: "serviceTier", value: "default" }]);
expect(option?.selection.options).toEqual([{ id: "serviceTier", value: "priority" }]);
});
});
10 changes: 9 additions & 1 deletion apps/server/src/codexModelOptions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,13 @@ it("keeps legacy persisted fast mode selections working", () => {
{ id: "fastMode", value: true },
]);

assert.equal(getCodexServiceTierOptionValue(selection), "fast");
assert.equal(getCodexServiceTierOptionValue(selection), "priority");
});

it("keeps legacy persisted fast-off selections on explicit Standard routing", () => {
const selection = createModelSelection(ProviderInstanceId.make("codex"), "gpt-5.4", [
{ id: "fastMode", value: false },
]);

assert.equal(getCodexServiceTierOptionValue(selection), "default");
});
23 changes: 20 additions & 3 deletions apps/server/src/codexModelOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,28 @@ import {
getModelSelectionStringOptionValue,
} from "@t3tools/shared/model";

/** Explicit Standard routing sentinel used by Codex 0.145+ app-server and CLI config. */
export const CODEX_STANDARD_SERVICE_TIER = "default";
/** Canonical model-catalog/request id for Codex Fast routing. */
export const CODEX_FAST_SERVICE_TIER = "priority";

export function normalizeCodexServiceTier(value: string | undefined): string | undefined {
const normalized = value?.trim().toLowerCase();
if (!normalized) return undefined;
// `fast` is the legacy config/T3 value; current model catalogs advertise `priority`.
return normalized === "fast" ? CODEX_FAST_SERVICE_TIER : normalized;
}

export function getCodexServiceTierOptionValue(
modelSelection: ModelSelection | null | undefined,
): string | undefined {
return (
getModelSelectionStringOptionValue(modelSelection, "serviceTier") ??
(getModelSelectionBooleanOptionValue(modelSelection, "fastMode") === true ? "fast" : undefined)
const selected = normalizeCodexServiceTier(
getModelSelectionStringOptionValue(modelSelection, "serviceTier"),
);
if (selected) return selected;

const legacyFastMode = getModelSelectionBooleanOptionValue(modelSelection, "fastMode");
if (legacyFastMode === true) return CODEX_FAST_SERVICE_TIER;
if (legacyFastMode === false) return CODEX_STANDARD_SERVICE_TIER;
return undefined;
}
47 changes: 47 additions & 0 deletions apps/server/src/provider/Layers/CodexAdapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,24 @@ validationLayer("CodexAdapterLive validation", (it) => {
}),
);

it.effect("keeps explicit Standard routing when starting a session", () =>
Effect.gen(function* () {
validationRuntimeFactory.factory.mockClear();
const adapter = yield* CodexAdapter;

yield* adapter.startSession({
provider: ProviderDriverKind.make("codex"),
threadId: asThreadId("thread-standard"),
modelSelection: createModelSelection(ProviderInstanceId.make("codex"), "gpt-5.4", [
{ id: "serviceTier", value: "default" },
]),
runtimeMode: "full-access",
});

NodeAssert.equal(validationRuntimeFactory.factory.mock.calls[0]?.[0].serviceTier, "default");
}),
);

it.effect("injects the canonical MCP server into Codex app-server sessions", () => {
const runtimeFactory = makeRuntimeFactory();
const baseEnvironment = { KEEP_ME: "1" };
Expand Down Expand Up @@ -464,6 +482,35 @@ sessionErrorLayer("CodexAdapterLive session errors", (it) => {
}),
);

it.effect("sends a persisted Fast-to-Standard choice on later turns", () =>
Effect.gen(function* () {
const adapter = yield* CodexAdapter;
const threadId = asThreadId("sess-fast-to-standard");
yield* adapter.startSession({
provider: ProviderDriverKind.make("codex"),
threadId,
modelSelection: createModelSelection(ProviderInstanceId.make("codex"), "gpt-5.4", [
{ id: "serviceTier", value: "priority" },
]),
runtimeMode: "full-access",
});
const runtime = sessionRuntimeFactory.lastRuntime;
NodeAssert.ok(runtime);
runtime.sendTurnImpl.mockClear();

yield* adapter.sendTurn({
threadId,
input: "continue on Standard",
modelSelection: createModelSelection(ProviderInstanceId.make("codex"), "gpt-5.4", [
{ id: "serviceTier", value: "default" },
]),
attachments: [],
});

NodeAssert.equal(runtime.sendTurnImpl.mock.calls[0]?.[0].serviceTier, "default");
}),
);

it.effect("passes configured launch args into the session runtime", () => {
const runtimeFactory = makeRuntimeFactory();
const layer = Layer.effect(
Expand Down
6 changes: 4 additions & 2 deletions apps/server/src/provider/Layers/CodexAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1581,7 +1581,9 @@ export const makeCodexAdapter = Effect.fn("makeCodexAdapter")(function* (
...(input.modelSelection?.instanceId === boundInstanceId
? { model: input.modelSelection.model }
: {}),
...(compatibility.serviceTier ? { serviceTier: compatibility.serviceTier } : {}),
...(compatibility.serviceTier !== undefined
? { serviceTier: compatibility.serviceTier }
: {}),
...(mcpSession
? makeCodexMcpRuntimeConfig(mcpSession, options?.environment ?? process.env)
: {}),
Expand Down Expand Up @@ -1942,7 +1944,7 @@ export const makeCodexAdapter = Effect.fn("makeCodexAdapter")(function* (
effort: reasoningEffort as EffectCodexSchema.V2TurnStartParams__ReasoningEffort,
}
: {}),
...(serviceTier ? { serviceTier } : {}),
...(serviceTier !== undefined ? { serviceTier } : {}),
...(input.interactionMode !== undefined ? { interactionMode: input.interactionMode } : {}),
...(codexAttachments.length > 0 ? { attachments: codexAttachments } : {}),
})
Expand Down
188 changes: 187 additions & 1 deletion apps/server/src/provider/Layers/CodexProvider.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,65 @@
import { assert, it } from "@effect/vitest";
import * as Effect from "effect/Effect";
import type * as CodexClient from "effect-codex-app-server/client";
import type * as CodexSchema from "effect-codex-app-server/schema";
import { ProviderInstanceId } from "@t3tools/contracts";
import {
buildProviderOptionSelectionsFromDescriptors,
createModelSelection,
} from "@t3tools/shared/model";

import { applyPreferredCodexDefaultModel, mapCodexModelCapabilities } from "./CodexProvider.ts";
import { getCodexServiceTierOptionValue } from "../../codexModelOptions.ts";
import {
applyPreferredCodexDefaultModel,
mapCodexModelCapabilities,
requestAllCodexModels,
} from "./CodexProvider.ts";

const priorityDefaultModel = {
additionalSpeedTiers: [],
defaultReasoningEffort: "medium",
description: "Test model",
displayName: "GPT Test",
hidden: false,
id: "gpt-test",
isDefault: true,
model: "gpt-test",
defaultServiceTier: "priority",
serviceTiers: [
{
id: "priority",
name: "Fast",
description: "1.5x speed, increased usage",
},
{
id: "flex",
name: "Flex",
description: "Lower-cost asynchronous routing.",
},
],
supportedReasoningEfforts: [],
} satisfies CodexSchema.V2ModelListResponse__Model;

function selectedServiceTier(
capabilities: ReturnType<typeof mapCodexModelCapabilities>,
): string | undefined {
const descriptor = capabilities.optionDescriptors?.find(
(candidate) => candidate.id === "serviceTier",
);
return descriptor?.type === "select" ? descriptor.currentValue : undefined;
}

function dispatchedServiceTier(
capabilities: ReturnType<typeof mapCodexModelCapabilities>,
): string | undefined {
return getCodexServiceTierOptionValue(
createModelSelection(
ProviderInstanceId.make("codex"),
priorityDefaultModel.model,
buildProviderOptionSelectionsFromDescriptors(capabilities.optionDescriptors ?? []),
),
);
}

it("maps current Codex model capability fields", () => {
const capabilities = mapCodexModelCapabilities({
Expand Down Expand Up @@ -101,8 +160,135 @@ it("uses standard routing when the catalog has no default service tier", () => {
currentValue: "default",
},
]);
assert.equal(dispatchedServiceTier(capabilities), "default");
});

it("uses Standard when effective config carries the durable Fast-default opt-out", () => {
const capabilities = mapCodexModelCapabilities(priorityDefaultModel, {
service_tier: null,
notice: { fast_default_opt_out: true },
});

assert.equal(selectedServiceTier(capabilities), "default");
assert.equal(dispatchedServiceTier(capabilities), "default");
const descriptor = capabilities.optionDescriptors?.find(
(candidate) => candidate.id === "serviceTier",
);
assert.equal(descriptor?.type, "select");
if (descriptor?.type === "select") {
assert.equal(
descriptor.options.some((option) => option.id === "priority"),
true,
);
}
assert.equal(
selectedServiceTier(
mapCodexModelCapabilities(priorityDefaultModel, {
service_tier: "fast",
notice: { fast_default_opt_out: true },
}),
),
"priority",
);
});

it("uses Standard when effective config explicitly selects the default tier", () => {
const capabilities = mapCodexModelCapabilities(priorityDefaultModel, {
service_tier: "default",
});

assert.equal(selectedServiceTier(capabilities), "default");
assert.equal(dispatchedServiceTier(capabilities), "default");
});

it("adopts the catalog-managed Fast default only when config has no explicit choice", () => {
const capabilities = mapCodexModelCapabilities(priorityDefaultModel, {
service_tier: null,
notice: null,
});

assert.equal(selectedServiceTier(capabilities), "priority");
assert.equal(dispatchedServiceTier(capabilities), "priority");
});

it("honors explicit Fast config in legacy and canonical forms", () => {
for (const configured of ["fast", "priority"] as const) {
const capabilities = mapCodexModelCapabilities(priorityDefaultModel, {
service_tier: configured,
});
assert.equal(selectedServiceTier(capabilities), "priority");
assert.equal(dispatchedServiceTier(capabilities), "priority");
}
});

it("honors explicit Flex config ahead of the catalog-managed Fast default", () => {
const capabilities = mapCodexModelCapabilities(priorityDefaultModel, {
service_tier: "flex",
});

assert.equal(selectedServiceTier(capabilities), "flex");
assert.equal(dispatchedServiceTier(capabilities), "flex");
});

it("uses explicit Standard when a configured tier is unsupported by the model", () => {
const capabilities = mapCodexModelCapabilities(
{
...priorityDefaultModel,
serviceTiers: priorityDefaultModel.serviceTiers.filter((tier) => tier.id === "priority"),
},
{ service_tier: "flex" },
);

assert.equal(selectedServiceTier(capabilities), "default");
assert.equal(dispatchedServiceTier(capabilities), "default");
});

it.effect(
"reads Windows and WSL catalogs through each provider client's own effective config",
() =>
Effect.gen(function* () {
const makeClient = (
config: CodexSchema.V2ConfigReadResponse__Config,
expectedCwd: string,
): CodexClient.CodexAppServerClient["Service"] => {
const client = {
request: (method: string, params: unknown) => {
if (method === "config/read") {
assert.deepStrictEqual(params, { cwd: expectedCwd });
return Effect.succeed({ config, origins: {} });
}
if (method === "model/list") {
return Effect.succeed({ data: [priorityDefaultModel], nextCursor: null });
}
return Effect.die(new Error(`Unexpected request: ${method}`));
},
};
return client as unknown as CodexClient.CodexAppServerClient["Service"];
};

const windowsModels = yield* requestAllCodexModels(
makeClient(
{ service_tier: null, notice: { fast_default_opt_out: true } },
"C:\\work\\project",
),
"C:\\work\\project",
);
const wslModels = yield* requestAllCodexModels(
makeClient({ service_tier: null, notice: null }, "/home/work/project"),
"/home/work/project",
);

assert.equal(
selectedServiceTier(windowsModels[0]?.capabilities ?? { optionDescriptors: [] }),
"default",
);
assert.equal(
selectedServiceTier(wslModels[0]?.capabilities ?? { optionDescriptors: [] }),
"priority",
);
}),
);

it("marks the most preferred available model as default", () => {
const models = applyPreferredCodexDefaultModel([
{ slug: "gpt-5.6-terra", name: "GPT-5.6-Terra", isCustom: false, capabilities: null },
Expand Down
Loading
Loading