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
21 changes: 21 additions & 0 deletions open-sse/executors/codex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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"];
Expand Down
1 change: 1 addition & 0 deletions open-sse/translator/request/openai-responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
};
Comment on lines +22 to +47

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

The error handling in the save function can be improved:

  1. Silent Failure: The status is set to "error" on failure, but there is no corresponding UI element to display this error. This means the save operation fails silently for the user.
  2. Inconsistent State: The optimistic UI update is not reverted on failure, leaving the toggle in a state that doesn't match the backend.

To fix the inconsistent state, you can revert the enabled state on failure. For the silent failure, you should add a UI element to display when status === 'error'. Here is a suggestion to fix the state inconsistency:

  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 (
<Card>
<div className="flex items-center gap-3 mb-5">
<div className="p-2 rounded-lg bg-sky-500/10 text-sky-500">
<span className="material-symbols-outlined text-[20px]" aria-hidden="true">
bolt
</span>
</div>
<div className="flex-1">
<h3 className="text-lg font-semibold">Codex Fast Service Tier</h3>
<p className="text-sm text-text-muted">
Inject `service_tier=fast` into Codex requests when the client leaves it unset.
</p>
</div>
{status === "saved" && (
<span className="text-xs font-medium text-emerald-500 flex items-center gap-1">
<span className="material-symbols-outlined text-[14px]">check_circle</span>
Saved
</span>
)}
{status === "error" && (
<span className="text-xs font-medium text-rose-500 flex items-center gap-1">
<span className="material-symbols-outlined text-[14px]">error</span>
Failed to save
</span>
)}
</div>

<div className="flex items-center justify-between p-4 rounded-lg bg-surface/30 border border-border/30">
<div>
<p className="text-sm font-medium">Force fast tier for Codex</p>
<p className="text-xs text-text-muted mt-0.5">
Off by default. Applies only to Codex requests and does not override an explicit tier.
</p>
</div>
<button
onClick={() => save(!enabled)}
disabled={loading || saving}
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${
enabled ? "bg-sky-500" : "bg-white/10"
}`}
>
<span
className={`inline-block h-4 w-4 rounded-full bg-white transition-transform ${
enabled ? "translate-x-6" : "translate-x-1"
}`}
/>
</button>
</div>
</Card>
);
}
2 changes: 2 additions & 0 deletions src/app/(dashboard)/dashboard/settings/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -85,6 +86,7 @@ export default function SettingsPage() {
{activeTab === "ai" && (
<div className="flex flex-col gap-6">
<ThinkingBudgetTab />
<CodexServiceTierTab />
<SystemPromptTab />
<CacheStatsCard />
</div>
Expand Down
55 changes: 55 additions & 0 deletions src/app/api/settings/codex-service-tier/route.ts
Original file line number Diff line number Diff line change
@@ -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;
await updateSettings({ codexServiceTier: config });
setDefaultFastServiceTierEnabled(config.enabled);

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 });
}
}
14 changes: 13 additions & 1 deletion src/instrumentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
Expand Down
6 changes: 6 additions & 0 deletions src/shared/validation/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
49 changes: 48 additions & 1 deletion tests/unit/plan3-p0.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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",
Expand Down