diff --git a/open-sse/executors/codex.ts b/open-sse/executors/codex.ts
index 04ba86bc79..55a6df207c 100644
--- a/open-sse/executors/codex.ts
+++ b/open-sse/executors/codex.ts
@@ -6,6 +6,20 @@ import { refreshCodexToken } from "../services/tokenRefresh.ts";
// Ordered list of effort levels from lowest to highest
const EFFORT_ORDER = ["none", "low", "medium", "high", "xhigh"] as const;
type EffortLevel = (typeof EFFORT_ORDER)[number];
+const CODEX_FAST_WIRE_VALUE = "priority";
+let defaultFastServiceTierEnabled = false;
+
+function normalizeServiceTierValue(value: unknown): string | undefined {
+ if (typeof value !== "string") return undefined;
+ const normalized = value.trim().toLowerCase();
+ if (!normalized) return undefined;
+ if (normalized === "fast") return CODEX_FAST_WIRE_VALUE;
+ return normalized;
+}
+
+export function setDefaultFastServiceTierEnabled(enabled: boolean): void {
+ defaultFastServiceTierEnabled = enabled;
+}
/**
* Maximum reasoning effort allowed per Codex model.
@@ -103,6 +117,13 @@ export class CodexExecutor extends BaseExecutor {
// Ensure store is false (Codex requirement)
body.store = false;
+ const requestServiceTier = normalizeServiceTierValue(body.service_tier);
+ if (requestServiceTier) {
+ body.service_tier = requestServiceTier;
+ } else if (defaultFastServiceTierEnabled) {
+ body.service_tier = CODEX_FAST_WIRE_VALUE;
+ }
+
// Extract thinking level from model name suffix
// e.g., gpt-5.3-codex-high → high, gpt-5.3-codex → medium (default)
const effortLevels = ["none", "low", "medium", "high", "xhigh"];
diff --git a/open-sse/translator/request/openai-responses.ts b/open-sse/translator/request/openai-responses.ts
index b4b3b4709f..7a7ed10adb 100644
--- a/open-sse/translator/request/openai-responses.ts
+++ b/open-sse/translator/request/openai-responses.ts
@@ -363,6 +363,7 @@ export function openaiToOpenAIResponsesRequest(
}
// Pass through relevant fields
+ if (root.service_tier !== undefined) result.service_tier = root.service_tier;
if (root.temperature !== undefined) result.temperature = root.temperature;
if (root.max_tokens !== undefined) result.max_tokens = root.max_tokens;
if (root.top_p !== undefined) result.top_p = root.top_p;
diff --git a/src/app/(dashboard)/dashboard/settings/components/CodexServiceTierTab.tsx b/src/app/(dashboard)/dashboard/settings/components/CodexServiceTierTab.tsx
new file mode 100644
index 0000000000..21b36c0442
--- /dev/null
+++ b/src/app/(dashboard)/dashboard/settings/components/CodexServiceTierTab.tsx
@@ -0,0 +1,100 @@
+"use client";
+
+import { useEffect, useState } from "react";
+import { Card } from "@/shared/components";
+
+export default function CodexServiceTierTab() {
+ const [enabled, setEnabled] = useState(false);
+ const [loading, setLoading] = useState(true);
+ const [saving, setSaving] = useState(false);
+ const [status, setStatus] = useState<"" | "saved" | "error">("");
+
+ useEffect(() => {
+ fetch("/api/settings/codex-service-tier")
+ .then((res) => res.json())
+ .then((data) => {
+ setEnabled(Boolean(data.enabled));
+ setLoading(false);
+ })
+ .catch(() => setLoading(false));
+ }, []);
+
+ const save = async (nextEnabled: boolean) => {
+ setEnabled(nextEnabled);
+ setSaving(true);
+ setStatus("");
+
+ try {
+ const res = await fetch("/api/settings/codex-service-tier", {
+ method: "PUT",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ enabled: nextEnabled }),
+ });
+
+ if (res.ok) {
+ setStatus("saved");
+ setTimeout(() => setStatus(""), 2000);
+ } else {
+ setStatus("error");
+ setEnabled(!nextEnabled);
+ }
+ } catch {
+ setStatus("error");
+ setEnabled(!nextEnabled);
+ } finally {
+ setSaving(false);
+ }
+ };
+
+ return (
+
+
+
+
+ bolt
+
+
+
+
Codex Fast Service Tier
+
+ Inject `service_tier=fast` into Codex requests when the client leaves it unset.
+
+
+ {status === "saved" && (
+
+ check_circle
+ Saved
+
+ )}
+ {status === "error" && (
+
+ error
+ Failed to save
+
+ )}
+
+
+
+
+
Force fast tier for Codex
+
+ Off by default. Applies only to Codex requests and does not override an explicit tier.
+
+
+
+
+
+ );
+}
diff --git a/src/app/(dashboard)/dashboard/settings/page.tsx b/src/app/(dashboard)/dashboard/settings/page.tsx
index d307eb3801..428384cc2d 100644
--- a/src/app/(dashboard)/dashboard/settings/page.tsx
+++ b/src/app/(dashboard)/dashboard/settings/page.tsx
@@ -12,6 +12,7 @@ import ComboDefaultsTab from "./components/ComboDefaultsTab";
import ProxyTab from "./components/ProxyTab";
import AppearanceTab from "./components/AppearanceTab";
import ThinkingBudgetTab from "./components/ThinkingBudgetTab";
+import CodexServiceTierTab from "./components/CodexServiceTierTab";
import SystemPromptTab from "./components/SystemPromptTab";
import ModelAliasesTab from "./components/ModelAliasesTab";
import BackgroundDegradationTab from "./components/BackgroundDegradationTab";
@@ -85,6 +86,7 @@ export default function SettingsPage() {
{activeTab === "ai" && (
+
diff --git a/src/app/api/settings/codex-service-tier/route.ts b/src/app/api/settings/codex-service-tier/route.ts
new file mode 100644
index 0000000000..382e6c856a
--- /dev/null
+++ b/src/app/api/settings/codex-service-tier/route.ts
@@ -0,0 +1,55 @@
+import { NextResponse, type Request } from "next/server";
+import { getSettings, updateSettings } from "@/lib/localDb";
+import { setDefaultFastServiceTierEnabled } from "@omniroute/open-sse/executors/codex.ts";
+import { updateCodexServiceTierSchema } from "@/shared/validation/schemas";
+import { isValidationFailure, validateBody } from "@/shared/validation/helpers";
+
+export async function GET() {
+ try {
+ const settings = await getSettings();
+ const persisted =
+ typeof settings.codexServiceTier === "string"
+ ? JSON.parse(settings.codexServiceTier)
+ : settings.codexServiceTier;
+
+ return NextResponse.json({
+ enabled: typeof persisted?.enabled === "boolean" ? persisted.enabled : false,
+ });
+ } catch (error) {
+ console.error("[API ERROR] /api/settings/codex-service-tier GET:", error);
+ return NextResponse.json({ error: "Failed to get config" }, { status: 500 });
+ }
+}
+
+export async function PUT(request: Request) {
+ let rawBody;
+ try {
+ rawBody = await request.json();
+ } catch {
+ return NextResponse.json(
+ {
+ error: {
+ message: "Invalid request",
+ details: [{ field: "body", message: "Invalid JSON body" }],
+ },
+ },
+ { status: 400 }
+ );
+ }
+
+ try {
+ const validation = validateBody(updateCodexServiceTierSchema, rawBody);
+ if (isValidationFailure(validation)) {
+ return NextResponse.json({ error: validation.error }, { status: 400 });
+ }
+
+ const config = validation.data;
+ setDefaultFastServiceTierEnabled(config.enabled);
+ await updateSettings({ codexServiceTier: config });
+
+ return NextResponse.json(config);
+ } catch (error) {
+ console.error("[API ERROR] /api/settings/codex-service-tier PUT:", error);
+ return NextResponse.json({ error: "Failed to update config" }, { status: 500 });
+ }
+}
diff --git a/src/instrumentation.ts b/src/instrumentation.ts
index 8a6296fecf..fa302f1d18 100644
--- a/src/instrumentation.ts
+++ b/src/instrumentation.ts
@@ -52,7 +52,9 @@ export async function register() {
try {
const { getSettings } = await import("@/lib/db/settings");
const { setCustomAliases } = await import("@omniroute/open-sse/services/modelDeprecation.ts");
+ const { setDefaultFastServiceTierEnabled } = await import("@omniroute/open-sse/executors/codex.ts");
const settings = await getSettings();
+
if (settings.modelAliases) {
const aliases =
typeof settings.modelAliases === "string"
@@ -65,9 +67,19 @@ export async function register() {
);
}
}
+
+ const persisted =
+ typeof settings.codexServiceTier === "string"
+ ? JSON.parse(settings.codexServiceTier)
+ : settings.codexServiceTier;
+
+ if (typeof persisted?.enabled === "boolean") {
+ setDefaultFastServiceTierEnabled(persisted.enabled);
+ console.log(`[STARTUP] Restored Codex fast service tier: ${persisted.enabled ? "on" : "off"}`);
+ }
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : String(err);
- console.warn("[STARTUP] Could not restore model aliases:", msg);
+ console.warn("[STARTUP] Could not restore runtime settings:", msg);
}
// Compliance: Initialize audit_log table + cleanup expired logs
diff --git a/src/shared/validation/schemas.ts b/src/shared/validation/schemas.ts
index d0634eb28c..9ea77f3f6e 100644
--- a/src/shared/validation/schemas.ts
+++ b/src/shared/validation/schemas.ts
@@ -439,6 +439,12 @@ export const updateThinkingBudgetSchema = z
}
});
+export const updateCodexServiceTierSchema = z
+ .object({
+ enabled: z.boolean(),
+ })
+ .strict();
+
const ipFilterModeSchema = z.enum(["blacklist", "whitelist"]);
const tempBanSchema = z.object({
ip: z.string().trim().min(1),
diff --git a/tests/unit/plan3-p0.test.mjs b/tests/unit/plan3-p0.test.mjs
index 4b77c4b124..c240db62ed 100644
--- a/tests/unit/plan3-p0.test.mjs
+++ b/tests/unit/plan3-p0.test.mjs
@@ -6,7 +6,10 @@ import { getModelInfoCore } from "../../open-sse/services/model.ts";
import { detectFormat } from "../../open-sse/services/provider.ts";
import { translateRequest } from "../../open-sse/translator/index.ts";
import { GithubExecutor } from "../../open-sse/executors/github.ts";
-import { CodexExecutor } from "../../open-sse/executors/codex.ts";
+import {
+ CodexExecutor,
+ setDefaultFastServiceTierEnabled,
+} from "../../open-sse/executors/codex.ts";
import { translateNonStreamingResponse } from "../../open-sse/handlers/responseTranslator.ts";
import { extractUsageFromResponse } from "../../open-sse/handlers/usageExtractor.ts";
import { parseSSEToResponsesOutput } from "../../open-sse/handlers/sseParser.ts";
@@ -60,6 +63,32 @@ test("CodexExecutor forces stream=true for upstream compatibility", () => {
assert.equal(transformed.stream, true);
});
+test("CodexExecutor maps fast service tier to priority", () => {
+ const executor = new CodexExecutor();
+ const transformed = executor.transformRequest(
+ "gpt-5.1-codex",
+ { model: "gpt-5.1-codex", input: [], service_tier: "fast" },
+ true
+ );
+ assert.equal(transformed.service_tier, "priority");
+});
+
+test("CodexExecutor can force fast service tier from settings", () => {
+ setDefaultFastServiceTierEnabled(true);
+
+ try {
+ const executor = new CodexExecutor();
+ const transformed = executor.transformRequest(
+ "gpt-5.1-codex",
+ { model: "gpt-5.1-codex", input: [] },
+ true
+ );
+ assert.equal(transformed.service_tier, "priority");
+ } finally {
+ setDefaultFastServiceTierEnabled(false);
+ }
+});
+
test("CodexExecutor always requests SSE accept header", () => {
const executor = new CodexExecutor();
const headers = executor.buildHeaders({ accessToken: "test-token" }, false);
@@ -166,6 +195,24 @@ test("translateRequest normalizes openai-responses input string into list payloa
assert.equal(translated.input[0].content[0].text, "hello from responses");
});
+test("translateRequest preserves service_tier when converting openai to openai-responses", () => {
+ const translated = translateRequest(
+ FORMATS.OPENAI,
+ FORMATS.OPENAI_RESPONSES,
+ "gpt-5.1-codex",
+ {
+ model: "gpt-5.1-codex",
+ messages: [{ role: "user", content: "hello from chat completions" }],
+ service_tier: "fast",
+ stream: false,
+ },
+ false
+ );
+
+ assert.equal(translated.service_tier, "fast");
+ assert.ok(Array.isArray(translated.input));
+});
+
test("parseSSEToResponsesOutput parses completed response from SSE payload", () => {
const rawSSE = [
"event: response.created",