Fix Codex combo fallback and move Codex defaults to connections (#1176)

Integrated into release/v3.6.5. Added CHANGELOG breaking change entry for the removed /api/settings/codex-service-tier endpoint and deduplicated getCodexRequestDefaults in page.tsx (now imports from requestDefaults.ts).
This commit is contained in:
Randi
2026-04-12 20:40:32 -04:00
committed by GitHub
parent 1e9fbd216f
commit 1e2b7e728d
18 changed files with 735 additions and 299 deletions

View File

@@ -2,7 +2,21 @@
## [Unreleased]
---
### ✨ New Features
- **Antigravity AI Credits Fallback:** Automatically retries with `GOOGLE_ONE_AI` credit injection when free-tier quota is exhausted. Per-account credit balance (5-hour TTL) is cached from SSE `remainingCredits` and exposed as a numeric badge in the Provider Usage dashboard (#1190 — thanks @sFaxsy)
- **Claude Code Native Parity:** Full header/body signing parity with the Claude Code 2.1.87 OAuth client — CCH xxHash64 body signing, dynamic per-request fingerprint, bidirectional TitleCase ↔ lowercase tool name remapping (14 tools), API constraint enforcement (`temperature=1` for thinking, max 4 `cache_control` blocks, auto-inject ephemeral on last user message), and optional ZWJ obfuscation (#1188 — thanks @RaviTharuma)
- **Per-Connection Codex Defaults:** Codex Fast Service Tier and Reasoning Effort settings are now per-connection instead of a single global toggle. Existing connections are migrated automatically on startup via an idempotent backfill migration (#1176 — thanks @rdself)
### 🐛 Bug Fixes
- **Search Cache Coalescing with TTL=0:** Fixed a bug where providers configured with `cacheTTLMs: 0` (caching explicitly disabled) still had concurrent requests coalesced and returned `{ cached: true }`. Now each call gets its own independent upstream fetch (#1178 — thanks @sjhddh)
- **Codex Combo Smoke Test False Positives:** Fixed combo tests incorrectly reporting `ERROR` for valid Codex streaming responses when `response.output` is empty but text deltas were emitted. The summary now falls back to accumulated delta text (#1176 — thanks @rdself)
- **Electron NODE_PATH Resolution (Windows):** Fixed Electron desktop startup failures on Windows packaged builds caused by native modules (`better-sqlite3`) being under `app.asar.unpacked` while helpers were in `app/node_modules`. `resolveServerNodePath()` now merges both locations with deduplication and existence checks (#1172 — thanks @backryun)
### ⚠️ Breaking Changes
- **`DELETE /api/settings/codex-service-tier` removed:** This endpoint no longer exists. Codex Service Tier configuration has moved to per-connection `providerSpecificData.requestDefaults`. Existing connections are migrated automatically on first startup after upgrade. Any external scripts or integrations that call this endpoint should be updated — use `PUT /api/providers/:id` with `providerSpecificData.requestDefaults.serviceTier` instead (#1176).
## [3.6.4] — 2026-04-12

View File

@@ -2,6 +2,8 @@ import { BaseExecutor } from "./base.ts";
import { CODEX_DEFAULT_INSTRUCTIONS } from "../config/codexInstructions.ts";
import { PROVIDERS } from "../config/constants.ts";
import { refreshCodexToken } from "../services/tokenRefresh.ts";
import { getThinkingBudgetConfig, ThinkingMode } from "../services/thinkingBudget.ts";
import { getCodexRequestDefaults } from "@/lib/providers/requestDefaults";
// ─── T09: Codex vs Spark Scope-Aware Rate Limiting ────────────────────────
// Codex has two independent quota pools: "codex" (standard) and "spark" (premium).
@@ -160,7 +162,6 @@ export function getCodexDualWindowCooldownMs(
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 stringifyCodexInstructionContent(content: unknown): string {
if (typeof content === "string") {
@@ -285,10 +286,6 @@ function normalizeServiceTierValue(value: unknown): string | undefined {
return normalized;
}
export function setDefaultFastServiceTierEnabled(enabled: boolean): void {
defaultFastServiceTierEnabled = enabled;
}
/**
* Maximum reasoning effort allowed per Codex model.
* Models not listed here default to "xhigh" (unrestricted).
@@ -318,6 +315,12 @@ function clampEffort(model: string, requested: string): string {
return requested;
}
function normalizeEffortValue(value: unknown): string | undefined {
if (typeof value !== "string") return undefined;
const normalized = value.trim().toLowerCase();
return normalized || undefined;
}
/**
* Codex Executor - handles OpenAI Codex API (Responses API format)
* Automatically injects default instructions if missing.
@@ -394,6 +397,9 @@ export class CodexExecutor extends BaseExecutor {
transformRequest(model, body, stream, credentials) {
const nativeCodexPassthrough = body?._nativeCodexPassthrough === true;
const isCompactRequest = isCompactResponsesEndpoint(credentials?.requestEndpointPath);
const requestDefaults = getCodexRequestDefaults(credentials?.providerSpecificData);
const thinkingBudgetConfig = getThinkingBudgetConfig();
const allowConnectionReasoningDefaults = thinkingBudgetConfig.mode === ThinkingMode.PASSTHROUGH;
// Codex /responses rejects stream=false, but /responses/compact rejects the stream field entirely.
if (isCompactRequest) {
@@ -407,8 +413,8 @@ export class CodexExecutor extends BaseExecutor {
const requestServiceTier = normalizeServiceTierValue(body.service_tier);
if (requestServiceTier) {
body.service_tier = requestServiceTier;
} else if (defaultFastServiceTierEnabled) {
body.service_tier = CODEX_FAST_WIRE_VALUE;
} else if (requestDefaults.serviceTier) {
body.service_tier = requestDefaults.serviceTier;
}
// If no instructions provided, inject default Codex instructions
@@ -435,38 +441,43 @@ export class CodexExecutor extends BaseExecutor {
delete body.messages;
delete body.prompt;
if (nativeCodexPassthrough) {
return body;
}
// 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"];
let modelEffort: string | null = null;
// Track the clean model name (suffix stripped) for clamp lookup
let cleanModel = model;
let cleanModel = typeof body.model === "string" ? body.model : model;
for (const level of effortLevels) {
if (model.endsWith(`-${level}`)) {
if (typeof cleanModel === "string" && cleanModel.endsWith(`-${level}`)) {
modelEffort = level;
// Strip suffix from model name for actual API call
body.model = body.model.replace(`-${level}`, "");
body.model = cleanModel.slice(0, -`-${level}`.length);
cleanModel = body.model;
break;
}
}
// Priority: explicit reasoning.effort > reasoning_effort param > model suffix > default (medium)
if (!body.reasoning) {
const rawEffort = body.reasoning_effort || modelEffort || "medium";
// Clamp effort to the model's maximum allowed level (feature-07)
const effort = clampEffort(cleanModel, rawEffort);
body.reasoning = { effort };
} else if (body.reasoning.effort) {
// Also clamp if reasoning object was provided directly
body.reasoning.effort = clampEffort(cleanModel, body.reasoning.effort);
const explicitReasoning = normalizeEffortValue(body?.reasoning?.effort);
const requestReasoningEffort = normalizeEffortValue(body.reasoning_effort);
const fallbackReasoningEffort = allowConnectionReasoningDefaults
? requestDefaults.reasoningEffort || "medium"
: undefined;
const rawEffort =
explicitReasoning || requestReasoningEffort || modelEffort || fallbackReasoningEffort;
if (explicitReasoning) {
body.reasoning = {
...(body.reasoning && typeof body.reasoning === "object" ? body.reasoning : {}),
effort: clampEffort(cleanModel, explicitReasoning),
};
} else if (rawEffort) {
body.reasoning = {
...(body.reasoning && typeof body.reasoning === "object" ? body.reasoning : {}),
effort: clampEffort(cleanModel, rawEffort),
};
}
delete body.reasoning_effort;
if (nativeCodexPassthrough) {
return body;
}
// Remove unsupported parameters for Codex API
delete body.temperature;
delete body.top_p;

View File

@@ -261,6 +261,16 @@ function buildResponsesSummary(
let latestResponse: JsonRecord | null = null;
let usage: JsonRecord | null = null;
const textParts: string[] = [];
const buildOutputFromText = () =>
textParts.length > 0
? [
{
type: "message",
role: "assistant",
content: [{ type: "output_text", text: textParts.join("") }],
},
]
: [];
for (const payload of payloads) {
const eventType = toString(payload.type);
@@ -292,11 +302,12 @@ function buildResponsesSummary(
const picked = completed || latestResponse;
if (picked && Object.keys(picked).length > 0) {
const pickedOutput = Array.isArray(picked.output) ? picked.output : [];
return {
id: toString(picked.id, `resp_${Date.now()}`),
object: "response",
model: toString(picked.model, fallbackModel || "unknown"),
output: Array.isArray(picked.output) ? picked.output : [],
output: pickedOutput.length > 0 ? pickedOutput : buildOutputFromText(),
usage: picked.usage ?? usage ?? null,
status: toString(picked.status, completed ? "completed" : "in_progress"),
created_at: toNumber(picked.created_at, Math.floor(Date.now() / 1000)),
@@ -308,16 +319,7 @@ function buildResponsesSummary(
id: `resp_${Date.now()}`,
object: "response",
model: fallbackModel || "unknown",
output:
textParts.length > 0
? [
{
type: "message",
role: "assistant",
content: [{ type: "output_text", text: textParts.join("") }],
},
]
: [],
output: buildOutputFromText(),
usage: usage ?? null,
status: "completed",
created_at: Math.floor(Date.now() / 1000),

View File

@@ -46,6 +46,7 @@ import { resolveManagedModelAlias } from "@/shared/utils/providerModelAliases";
import { maskEmail, pickMaskedDisplayValue, pickDisplayValue } from "@/shared/utils/maskEmail";
import useEmailPrivacyStore from "@/store/emailPrivacyStore";
import EmailPrivacyToggle from "@/shared/components/EmailPrivacyToggle";
import { getCodexRequestDefaults as _getCodexRequestDefaults } from "@/lib/providers/requestDefaults";
type CompatByProtocolMap = Partial<
Record<
@@ -535,6 +536,13 @@ interface EditCompatibleNodeModalProps {
const CC_COMPATIBLE_LABEL = "CC Compatible";
const CC_COMPATIBLE_DETAILS_TITLE = "CC Compatible Details";
const CC_COMPATIBLE_DEFAULT_CHAT_PATH = "/v1/messages?beta=true";
const CODEX_REASONING_STRENGTH_OPTIONS = [
{ value: "none", label: "None" },
{ value: "low", label: "Low" },
{ value: "medium", label: "Medium" },
{ value: "high", label: "High" },
{ value: "xhigh", label: "XHigh" },
];
function normalizeCodexLimitPolicy(policy: unknown): { use5h: boolean; useWeekly: boolean } {
const record =
@@ -547,6 +555,21 @@ function normalizeCodexLimitPolicy(policy: unknown): { use5h: boolean; useWeekly
};
}
/**
* UI adapter around the canonical getCodexRequestDefaults from requestDefaults.ts.
* Adds the "medium" fallback for reasoningEffort required by the connection form.
*/
function getCodexRequestDefaults(providerSpecificData: unknown): {
reasoningEffort: string;
serviceTier?: "priority";
} {
const defaults = _getCodexRequestDefaults(providerSpecificData);
return {
reasoningEffort: defaults.reasoningEffort ?? "medium",
...(defaults.serviceTier ? { serviceTier: defaults.serviceTier } : {}),
};
}
function compatProtocolLabelKey(protocol: string): string {
if (protocol === "openai") return "compatProtocolOpenAI";
if (protocol === "openai-responses") return "compatProtocolOpenAIResponses";
@@ -5340,6 +5363,8 @@ function EditConnectionModal({ isOpen, connection, onSave, onClose }: EditConnec
tag: "",
customUserAgent: "",
accountId: "",
codexReasoningEffort: "medium",
codexFastServiceTier: false,
});
const [testing, setTesting] = useState(false);
const [testResult, setTestResult] = useState(null);
@@ -5358,6 +5383,7 @@ function EditConnectionModal({ isOpen, connection, onSave, onClose }: EditConnec
const isVertex = connection?.provider === "vertex";
const isGlm = connection?.provider === "glm";
const isCloudflare = connection?.provider === "cloudflare-ai";
const isCodex = connection?.provider === "codex";
const defaultRegion = "us-central1";
useEffect(() => {
@@ -5371,6 +5397,7 @@ function EditConnectionModal({ isOpen, connection, onSave, onClose }: EditConnec
typeof rawCustomUserAgent === "string" ? rawCustomUserAgent : "";
const rawAccountId = connection.providerSpecificData?.accountId;
const existingAccountId = typeof rawAccountId === "string" ? rawAccountId : "";
const codexRequestDefaults = getCodexRequestDefaults(connection.providerSpecificData);
setFormData({
name: connection.name || "",
priority: connection.priority || 1,
@@ -5383,6 +5410,8 @@ function EditConnectionModal({ isOpen, connection, onSave, onClose }: EditConnec
tag: (connection.providerSpecificData?.tag as string) || "",
customUserAgent: existingCustomUserAgent,
accountId: existingAccountId,
codexReasoningEffort: codexRequestDefaults.reasoningEffort,
codexFastServiceTier: codexRequestDefaults.serviceTier === "priority",
});
// Load existing extra keys from providerSpecificData
const existing = connection.providerSpecificData?.extraApiKeys;
@@ -5533,6 +5562,12 @@ function EditConnectionModal({ isOpen, connection, onSave, onClose }: EditConnec
...(connection.providerSpecificData || {}),
tag: formData.tag.trim() || undefined,
};
if (isCodex) {
updates.providerSpecificData.requestDefaults = {
reasoningEffort: formData.codexReasoningEffort,
...(formData.codexFastServiceTier ? { serviceTier: "priority" } : {}),
};
}
}
const error = (await onSave(updates)) as void | unknown;
if (error) {
@@ -5570,6 +5605,23 @@ function EditConnectionModal({ isOpen, connection, onSave, onClose }: EditConnec
placeholder="e.g. personal, work, team-a"
hint="Used to group accounts in the provider view"
/>
{isCodex && (
<div className="flex flex-col gap-4 rounded-lg border border-border/50 bg-surface/20 p-4">
<Select
label="Default thinking strength"
value={formData.codexReasoningEffort}
options={CODEX_REASONING_STRENGTH_OPTIONS}
onChange={(e) => setFormData({ ...formData, codexReasoningEffort: e.target.value })}
hint="Used when the client does not send a reasoning effort and the global Thinking Budget mode is passthrough."
/>
<Toggle
checked={formData.codexFastServiceTier}
onChange={(checked) => setFormData({ ...formData, codexFastServiceTier: checked })}
label="Codex Fast Service Tier"
description="When enabled, injects `service_tier=priority` for this connection if the client leaves the tier unset."
/>
</div>
)}
{isOAuth && connection.email && (
<div className="bg-sidebar/50 p-3 rounded-lg">
<p className="text-sm text-text-muted mb-1">{t("email")}</p>

View File

@@ -1,103 +0,0 @@
"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 (
<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=priority` 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.
Codex fast mode is sent upstream as `service_tier=priority`.
</p>
</div>
<button
onClick={() => save(!enabled)}
disabled={loading || saving}
className={`relative inline-flex h-6 w-11 items-center rounded-full border transition-colors ${
enabled
? "bg-sky-500 border-sky-500"
: "bg-black/10 border-black/10 dark:bg-white/10 dark:border-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>
);
}

View File

@@ -12,7 +12,6 @@ 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 ModelAliasesUnified from "./components/ModelAliasesUnified";
import BackgroundDegradationTab from "./components/BackgroundDegradationTab";
@@ -93,7 +92,6 @@ export default function SettingsPage() {
{activeTab === "ai" && (
<div className="flex flex-col gap-6">
<ThinkingBudgetTab />
<CodexServiceTierTab />
<SystemPromptTab />
<CacheSettingsTab />
<MemorySkillsTab />

View File

@@ -9,6 +9,7 @@ import { getConsistentMachineId } from "@/shared/utils/machineId";
import { syncToCloud } from "@/lib/cloudSync";
import { updateProviderConnectionSchema } from "@/shared/validation/schemas";
import { isValidationFailure, validateBody } from "@/shared/validation/helpers";
import { normalizeProviderSpecificData } from "@/lib/providers/requestDefaults";
function normalizeCodexLimitPolicy(
incoming: unknown,
@@ -142,7 +143,8 @@ export async function PUT(request: Request, { params }: { params: Promise<{ id:
}
}
updateData.providerSpecificData = mergedPsd;
updateData.providerSpecificData =
normalizeProviderSpecificData(existing.provider, mergedPsd) || {};
}
const updated = await updateProviderConnection(id, updateData);

View File

@@ -16,6 +16,7 @@ import { syncToCloud } from "@/lib/cloudSync";
import { createProviderSchema } from "@/shared/validation/schemas";
import { isValidationFailure, validateBody } from "@/shared/validation/helpers";
import { normalizeQoderPatProviderData } from "@omniroute/open-sse/services/qoderCli";
import { normalizeProviderSpecificData } from "@/lib/providers/requestDefaults";
// GET /api/providers - List all connections
export async function GET() {
@@ -132,6 +133,8 @@ export async function POST(request: Request) {
};
}
providerSpecificData = normalizeProviderSpecificData(provider, providerSpecificData) || null;
const newConnection = await createProviderConnection({
provider,
authType: "apikey",

View File

@@ -1,55 +0,0 @@
import { NextResponse } 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 });
}
}

View File

@@ -105,10 +105,11 @@ export async function registerNodejs(): Promise<void> {
}
try {
const [{ setCustomAliases }, { setDefaultFastServiceTierEnabled }] = await Promise.all([
import("@omniroute/open-sse/services/modelDeprecation.ts"),
import("@omniroute/open-sse/executors/codex.ts"),
]);
const [{ setCustomAliases }, { migrateCodexConnectionDefaultsFromLegacySettings }] =
await Promise.all([
import("@omniroute/open-sse/services/modelDeprecation.ts"),
import("@/lib/providers/codexConnectionDefaults"),
]);
const settings = await getSettings();
if (settings.modelAliases) {
@@ -124,16 +125,20 @@ export async function registerNodejs(): Promise<void> {
}
}
const persisted =
typeof settings.codexServiceTier === "string"
? JSON.parse(settings.codexServiceTier)
: settings.codexServiceTier;
if (typeof persisted?.enabled === "boolean") {
setDefaultFastServiceTierEnabled(persisted.enabled);
const migration = await migrateCodexConnectionDefaultsFromLegacySettings();
if (migration.migrated) {
console.log(
`[STARTUP] Restored Codex fast service tier: ${persisted.enabled ? "on" : "off"}`
`[STARTUP] Migrated Codex connection defaults for ${migration.updatedConnectionIds.length} connection(s)`
);
if (settings.cloudEnabled === true) {
const [{ syncToCloud }, { getConsistentMachineId }] = await Promise.all([
import("@/lib/cloudSync"),
import("@/shared/utils/machineId"),
]);
const machineId = await getConsistentMachineId();
await syncToCloud(machineId);
console.log("[STARTUP] Synced migrated Codex connection defaults to cloud");
}
}
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : String(err);

View File

@@ -7,6 +7,7 @@ import { getDbInstance, rowToCamel, cleanNulls } from "./core";
import { backupDbFile } from "./backup";
import { encryptConnectionFields, decryptConnectionFields } from "./encryption";
import { invalidateDbCache } from "./readCache";
import { normalizeProviderSpecificData } from "@/lib/providers/requestDefaults";
type JsonRecord = Record<string, unknown>;
@@ -67,6 +68,10 @@ export async function getProviderConnectionById(id: string) {
export async function createProviderConnection(data: JsonRecord) {
const db = getDbInstance() as unknown as DbLike;
const now = new Date().toISOString();
const normalizedProviderSpecificData = normalizeProviderSpecificData(
toStringOrNull(data.provider),
data.providerSpecificData
);
// Upsert check
// For Codex/OpenAI, a single email can have multiple workspaces (Team + Personal)
@@ -121,7 +126,11 @@ export async function createProviderConnection(data: JsonRecord) {
if (existing) {
const existingId = toStringOrNull(existing.id);
if (!existingId) return null;
const merged = { ...toRecord(rowToCamel(existing)), ...data, updatedAt: now };
const merged: JsonRecord = { ...toRecord(rowToCamel(existing)), ...data, updatedAt: now };
merged.providerSpecificData = normalizeProviderSpecificData(
toStringOrNull(merged.provider),
merged.providerSpecificData
);
_updateConnectionRow(db, existingId, merged);
backupDbFile("pre-write");
return cleanNulls(merged);
@@ -192,8 +201,8 @@ export async function createProviderConnection(data: JsonRecord) {
connection[field] = data[field];
}
}
if (data.providerSpecificData && Object.keys(data.providerSpecificData).length > 0) {
connection.providerSpecificData = data.providerSpecificData;
if (normalizedProviderSpecificData && Object.keys(normalizedProviderSpecificData).length > 0) {
connection.providerSpecificData = normalizedProviderSpecificData;
}
_insertConnectionRow(db, encryptConnectionFields({ ...connection }));
@@ -347,7 +356,15 @@ export async function updateProviderConnection(id: string, data: JsonRecord) {
const existing = db.prepare("SELECT * FROM provider_connections WHERE id = ?").get(id);
if (!existing) return null;
const merged = { ...rowToCamel(existing), ...data, updatedAt: new Date().toISOString() };
const merged: JsonRecord = {
...toRecord(rowToCamel(existing)),
...data,
updatedAt: new Date().toISOString(),
};
merged.providerSpecificData = normalizeProviderSpecificData(
toStringOrNull(merged.provider),
merged.providerSpecificData
);
_updateConnectionRow(db, id, encryptConnectionFields({ ...merged }));
backupDbFile("pre-write");
invalidateDbCache("connections"); // Bust connections read cache

View File

@@ -0,0 +1,84 @@
import { getProviderConnections, updateProviderConnection } from "@/lib/db/providers";
import { getSettings, updateSettings } from "@/lib/db/settings";
import { getCodexRequestDefaults } from "./requestDefaults";
type JsonRecord = Record<string, unknown>;
const MIGRATION_SETTING_KEY = "codexConnectionDefaultsMigrationV1";
function asRecord(value: unknown): JsonRecord {
return value && typeof value === "object" && !Array.isArray(value) ? (value as JsonRecord) : {};
}
function parseLegacyCodexServiceTier(value: unknown): { enabled: boolean } {
if (typeof value === "string") {
try {
return parseLegacyCodexServiceTier(JSON.parse(value));
} catch {
return { enabled: false };
}
}
const record = asRecord(value);
return { enabled: record.enabled === true };
}
export async function migrateCodexConnectionDefaultsFromLegacySettings(): Promise<{
migrated: boolean;
updatedConnectionIds: string[];
legacyFastEnabled: boolean;
}> {
const settings = await getSettings();
if (settings[MIGRATION_SETTING_KEY]) {
return {
migrated: false,
updatedConnectionIds: [],
legacyFastEnabled: parseLegacyCodexServiceTier(settings.codexServiceTier).enabled,
};
}
const legacyFastEnabled = parseLegacyCodexServiceTier(settings.codexServiceTier).enabled;
const codexConnections = await getProviderConnections({ provider: "codex" });
const updatedConnectionIds: string[] = [];
for (const connection of codexConnections) {
const providerSpecificData = asRecord(connection.providerSpecificData);
const existingDefaults = getCodexRequestDefaults(providerSpecificData);
const nextDefaults: JsonRecord = { ...existingDefaults };
if (!existingDefaults.reasoningEffort) {
nextDefaults.reasoningEffort = "medium";
}
if (legacyFastEnabled && !existingDefaults.serviceTier) {
nextDefaults.serviceTier = "priority";
}
const defaultsChanged =
nextDefaults.reasoningEffort !== existingDefaults.reasoningEffort ||
nextDefaults.serviceTier !== existingDefaults.serviceTier;
if (!defaultsChanged) continue;
await updateProviderConnection(connection.id, {
providerSpecificData: {
...providerSpecificData,
requestDefaults: nextDefaults,
},
});
updatedConnectionIds.push(connection.id);
}
await updateSettings({
[MIGRATION_SETTING_KEY]: {
completedAt: new Date().toISOString(),
updatedConnectionIds,
legacyFastEnabled,
},
});
return {
migrated: true,
updatedConnectionIds,
legacyFastEnabled,
};
}

View File

@@ -0,0 +1,101 @@
type JsonRecord = Record<string, unknown>;
export const CODEX_REASONING_EFFORT_VALUES = ["none", "low", "medium", "high", "xhigh"] as const;
export type CodexReasoningEffort = (typeof CODEX_REASONING_EFFORT_VALUES)[number];
const CODEX_REASONING_EFFORT_SET = new Set<string>(CODEX_REASONING_EFFORT_VALUES);
function asRecord(value: unknown): JsonRecord {
return value && typeof value === "object" && !Array.isArray(value) ? (value as JsonRecord) : {};
}
function normalizeString(value: unknown): string | undefined {
if (typeof value !== "string") return undefined;
const normalized = value.trim().toLowerCase();
return normalized || undefined;
}
export function normalizeCodexReasoningEffort(value: unknown): CodexReasoningEffort | undefined {
const normalized = normalizeString(value);
if (!normalized || !CODEX_REASONING_EFFORT_SET.has(normalized)) {
return undefined;
}
return normalized as CodexReasoningEffort;
}
export function normalizeCodexServiceTier(value: unknown): "priority" | undefined {
const normalized = normalizeString(value);
if (!normalized) return undefined;
if (normalized === "fast" || normalized === "priority") return "priority";
return undefined;
}
export function normalizeRequestDefaults(
provider: string | null | undefined,
value: unknown
): JsonRecord | undefined {
const record = asRecord(value);
if (Object.keys(record).length === 0) return undefined;
const normalized: JsonRecord = { ...record };
if (provider === "codex") {
const reasoningEffort = normalizeCodexReasoningEffort(record.reasoningEffort);
if (reasoningEffort) {
normalized.reasoningEffort = reasoningEffort;
} else {
delete normalized.reasoningEffort;
}
const serviceTier = normalizeCodexServiceTier(record.serviceTier);
if (serviceTier) {
normalized.serviceTier = serviceTier;
} else {
delete normalized.serviceTier;
}
}
return Object.keys(normalized).length > 0 ? normalized : undefined;
}
export function normalizeProviderSpecificData(
provider: string | null | undefined,
value: unknown
): JsonRecord | undefined {
const record = asRecord(value);
if (Object.keys(record).length === 0) return undefined;
const normalized: JsonRecord = { ...record };
if ("requestDefaults" in normalized) {
const requestDefaults = normalizeRequestDefaults(provider, normalized.requestDefaults);
if (requestDefaults) {
normalized.requestDefaults = requestDefaults;
} else {
delete normalized.requestDefaults;
}
}
return Object.keys(normalized).length > 0 ? normalized : undefined;
}
export function getProviderRequestDefaults(
provider: string | null | undefined,
providerSpecificData: unknown
): JsonRecord {
return normalizeRequestDefaults(provider, asRecord(providerSpecificData).requestDefaults) || {};
}
export function getCodexRequestDefaults(providerSpecificData: unknown): {
reasoningEffort?: CodexReasoningEffort;
serviceTier?: "priority";
} {
const defaults = getProviderRequestDefaults("codex", providerSpecificData);
const reasoningEffort = normalizeCodexReasoningEffort(defaults.reasoningEffort);
const serviceTier = normalizeCodexServiceTier(defaults.serviceTier);
return {
...(reasoningEffort ? { reasoningEffort } : {}),
...(serviceTier ? { serviceTier } : {}),
};
}

View File

@@ -11,6 +11,79 @@ function isHttpUrl(value: string): boolean {
}
}
const CODEX_REASONING_EFFORT_VALUES = new Set(["none", "low", "medium", "high", "xhigh"]);
const REQUEST_DEFAULT_SERVICE_TIER_VALUES = new Set(["priority", "fast"]);
function validateProviderSpecificData(
data: Record<string, unknown> | undefined,
ctx: z.RefinementCtx
): void {
if (!data) return;
const baseUrl = data.baseUrl;
if (baseUrl !== undefined && (typeof baseUrl !== "string" || !isHttpUrl(baseUrl))) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "providerSpecificData.baseUrl must be a valid http(s) URL",
path: ["baseUrl"],
});
}
const customUserAgent = data.customUserAgent;
if (
customUserAgent !== undefined &&
customUserAgent !== null &&
(typeof customUserAgent !== "string" || customUserAgent.length > 500)
) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "providerSpecificData.customUserAgent must be a string up to 500 chars",
path: ["customUserAgent"],
});
}
const requestDefaults = data.requestDefaults;
if (requestDefaults === undefined) return;
if (!requestDefaults || typeof requestDefaults !== "object" || Array.isArray(requestDefaults)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "providerSpecificData.requestDefaults must be an object",
path: ["requestDefaults"],
});
return;
}
const requestDefaultsRecord = requestDefaults as Record<string, unknown>;
const reasoningEffort = requestDefaultsRecord.reasoningEffort;
if (
reasoningEffort !== undefined &&
reasoningEffort !== null &&
(typeof reasoningEffort !== "string" ||
!CODEX_REASONING_EFFORT_VALUES.has(reasoningEffort.trim().toLowerCase()))
) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message:
"providerSpecificData.requestDefaults.reasoningEffort must be one of none, low, medium, high, xhigh",
path: ["requestDefaults", "reasoningEffort"],
});
}
const serviceTier = requestDefaultsRecord.serviceTier;
if (
serviceTier !== undefined &&
serviceTier !== null &&
(typeof serviceTier !== "string" ||
!REQUEST_DEFAULT_SERVICE_TIER_VALUES.has(serviceTier.trim().toLowerCase()))
) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "providerSpecificData.requestDefaults.serviceTier must be priority when provided",
path: ["requestDefaults", "serviceTier"],
});
}
}
// Re-export validation helpers from dedicated module to avoid webpack barrel-file
// optimization bug that truncates exports from large files.
export { validateBody, isValidationFailure } from "./helpers";
@@ -30,27 +103,7 @@ export const createProviderSchema = z.object({
.record(z.string(), z.unknown())
.optional()
.superRefine((data, ctx) => {
if (!data) return;
const baseUrl = data.baseUrl;
if (baseUrl !== undefined && (typeof baseUrl !== "string" || !isHttpUrl(baseUrl))) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "providerSpecificData.baseUrl must be a valid http(s) URL",
path: ["baseUrl"],
});
}
const customUserAgent = data.customUserAgent;
if (
customUserAgent !== undefined &&
customUserAgent !== null &&
(typeof customUserAgent !== "string" || customUserAgent.length > 500)
) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "providerSpecificData.customUserAgent must be a string up to 500 chars",
path: ["customUserAgent"],
});
}
validateProviderSpecificData(data, ctx);
}),
});
@@ -643,12 +696,6 @@ 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),
@@ -1101,27 +1148,7 @@ export const updateProviderConnectionSchema = z
.record(z.string(), z.unknown())
.optional()
.superRefine((data, ctx) => {
if (!data) return;
const baseUrl = data.baseUrl;
if (baseUrl !== undefined && (typeof baseUrl !== "string" || !isHttpUrl(baseUrl))) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "providerSpecificData.baseUrl must be a valid http(s) URL",
path: ["baseUrl"],
});
}
const customUserAgent = data.customUserAgent;
if (
customUserAgent !== undefined &&
customUserAgent !== null &&
(typeof customUserAgent !== "string" || customUserAgent.length > 500)
) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "providerSpecificData.customUserAgent must be a string up to 500 chars",
path: ["customUserAgent"],
});
}
validateProviderSpecificData(data, ctx);
}),
})
.superRefine((value, ctx) => {

View File

@@ -0,0 +1,132 @@
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-codex-defaults-"));
process.env.DATA_DIR = TEST_DATA_DIR;
const core = await import("../../src/lib/db/core.ts");
const providersDb = await import("../../src/lib/db/providers.ts");
const settingsDb = await import("../../src/lib/db/settings.ts");
const { migrateCodexConnectionDefaultsFromLegacySettings } =
await import("../../src/lib/providers/codexConnectionDefaults.ts");
async function resetStorage() {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
}
test.beforeEach(async () => {
await resetStorage();
});
test.after(async () => {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
});
test("migration backfills Codex request defaults, preserves existing providerSpecificData, and is idempotent", async () => {
const first = await providersDb.createProviderConnection({
provider: "codex",
authType: "oauth",
email: "first@example.com",
providerSpecificData: {
workspaceId: "ws-first",
tag: "team-a",
codexLimitPolicy: { use5h: false, useWeekly: true },
},
});
const second = await providersDb.createProviderConnection({
provider: "codex",
authType: "oauth",
email: "second@example.com",
providerSpecificData: {
workspaceId: "ws-second",
tag: "team-b",
requestDefaults: { reasoningEffort: "high" },
},
});
const untouched = await providersDb.createProviderConnection({
provider: "codex",
authType: "oauth",
email: "third@example.com",
providerSpecificData: {
workspaceId: "ws-third",
tag: "team-c",
requestDefaults: { reasoningEffort: "low", serviceTier: "priority" },
},
});
await settingsDb.updateSettings({ codexServiceTier: { enabled: true } });
const firstRun = await migrateCodexConnectionDefaultsFromLegacySettings();
const rows = await providersDb.getProviderConnections({ provider: "codex" });
const byId = new Map(rows.map((row) => [row.id, row]));
const settings = await settingsDb.getSettings();
assert.equal(firstRun.migrated, true);
assert.deepEqual(firstRun.updatedConnectionIds.sort(), [first.id, second.id].sort());
assert.deepEqual(byId.get(first.id).providerSpecificData.requestDefaults, {
reasoningEffort: "medium",
serviceTier: "priority",
});
assert.deepEqual(byId.get(second.id).providerSpecificData.requestDefaults, {
reasoningEffort: "high",
serviceTier: "priority",
});
assert.deepEqual(byId.get(untouched.id).providerSpecificData.requestDefaults, {
reasoningEffort: "low",
serviceTier: "priority",
});
assert.equal(byId.get(first.id).providerSpecificData.tag, "team-a");
assert.deepEqual(byId.get(first.id).providerSpecificData.codexLimitPolicy, {
use5h: false,
useWeekly: true,
});
assert.ok(settings.codexConnectionDefaultsMigrationV1);
const secondRun = await migrateCodexConnectionDefaultsFromLegacySettings();
assert.equal(secondRun.migrated, false);
assert.deepEqual(secondRun.updatedConnectionIds, []);
});
test("provider connection persistence normalizes request defaults without dropping unrelated keys", async () => {
const created = await providersDb.createProviderConnection({
provider: "codex",
authType: "oauth",
email: "normalize@example.com",
providerSpecificData: {
workspaceId: "ws-normalize",
tag: "team-z",
requestDefaults: {
reasoningEffort: "HIGH",
serviceTier: "fast",
customFlag: "keep-me",
},
},
});
assert.deepEqual(created.providerSpecificData.requestDefaults, {
reasoningEffort: "high",
serviceTier: "priority",
customFlag: "keep-me",
});
assert.equal(created.providerSpecificData.workspaceId, "ws-normalize");
assert.equal(created.providerSpecificData.tag, "team-z");
const updated = await providersDb.updateProviderConnection(created.id, {
providerSpecificData: {
...created.providerSpecificData,
requestDefaults: { reasoningEffort: "medium" },
},
});
assert.deepEqual(updated.providerSpecificData.requestDefaults, {
reasoningEffort: "medium",
});
assert.equal(updated.providerSpecificData.workspaceId, "ws-normalize");
assert.equal(updated.providerSpecificData.tag, "team-z");
});

View File

@@ -7,8 +7,16 @@ import {
getCodexRateLimitKey,
getCodexResetTime,
parseCodexQuotaHeaders,
setDefaultFastServiceTierEnabled,
} from "../../open-sse/executors/codex.ts";
import {
DEFAULT_THINKING_CONFIG,
setThinkingBudgetConfig,
ThinkingMode,
} from "../../open-sse/services/thinkingBudget.ts";
test.afterEach(() => {
setThinkingBudgetConfig(DEFAULT_THINKING_CONFIG);
});
test("Codex helper functions isolate rate-limit scopes and parse quota headers", () => {
const quota = parseCodexQuotaHeaders(
@@ -102,26 +110,121 @@ test("CodexExecutor.transformRequest injects default instructions, clamps reason
test("CodexExecutor.transformRequest preserves compact requests and native passthrough semantics", () => {
const executor = new CodexExecutor();
setDefaultFastServiceTierEnabled(true);
const body = {
_nativeCodexPassthrough: true,
instructions: "keep this",
stream: false,
};
const result = executor.transformRequest("gpt-5.3-codex", body, false, {
requestEndpointPath: "/responses/compact",
providerSpecificData: {
requestDefaults: { serviceTier: "priority" },
},
});
try {
const body = {
_nativeCodexPassthrough: true,
instructions: "keep this",
stream: false,
};
const result = executor.transformRequest("gpt-5.3-codex", body, false, {
requestEndpointPath: "/responses/compact",
});
assert.equal(result._nativeCodexPassthrough, undefined);
assert.equal(result.stream, undefined);
assert.equal(result.service_tier, "priority");
assert.equal(result.reasoning.effort, "medium");
assert.equal(result.store, false);
assert.equal(result.instructions, "keep this");
});
assert.equal(result._nativeCodexPassthrough, undefined);
assert.equal(result.stream, undefined);
assert.equal(result.service_tier, "priority");
assert.equal(result.store, false);
assert.equal(result.instructions, "keep this");
} finally {
setDefaultFastServiceTierEnabled(false);
}
test("CodexExecutor.transformRequest applies per-connection reasoning and service tier defaults", () => {
const executor = new CodexExecutor();
const result = executor.transformRequest(
"gpt-5.3-codex",
{ model: "gpt-5.3-codex", input: [] },
false,
{
providerSpecificData: {
requestDefaults: {
reasoningEffort: "high",
serviceTier: "priority",
},
},
}
);
assert.equal(result.reasoning.effort, "high");
assert.equal(result.service_tier, "priority");
});
test("CodexExecutor.transformRequest keeps explicit request values ahead of connection defaults", () => {
const executor = new CodexExecutor();
const result = executor.transformRequest(
"gpt-5.3-codex",
{
model: "gpt-5.3-codex",
input: [],
reasoning_effort: "none",
service_tier: "standard",
},
false,
{
providerSpecificData: {
requestDefaults: {
reasoningEffort: "high",
serviceTier: "priority",
},
},
}
);
assert.equal(result.reasoning.effort, "none");
assert.equal(result.service_tier, "standard");
});
test("CodexExecutor.transformRequest lets model suffix beat connection reasoning defaults", () => {
const executor = new CodexExecutor();
const result = executor.transformRequest(
"gpt-5.3-codex-high",
{ model: "gpt-5.3-codex-high", input: [] },
false,
{
providerSpecificData: {
requestDefaults: {
reasoningEffort: "low",
},
},
}
);
assert.equal(result.model, "gpt-5.3-codex");
assert.equal(result.reasoning.effort, "high");
});
test("CodexExecutor.transformRequest does not apply connection reasoning defaults when Thinking Budget is not passthrough", () => {
const executor = new CodexExecutor();
setThinkingBudgetConfig({ mode: ThinkingMode.AUTO });
const noDefaults = executor.transformRequest(
"gpt-5.3-codex",
{ model: "gpt-5.3-codex", input: [] },
false,
{
providerSpecificData: {
requestDefaults: {
reasoningEffort: "high",
},
},
}
);
const explicit = executor.transformRequest(
"gpt-5.3-codex",
{ model: "gpt-5.3-codex", input: [], reasoning_effort: "high" },
false,
{
providerSpecificData: {
requestDefaults: {
reasoningEffort: "low",
},
},
}
);
assert.equal(noDefaults.reasoning, undefined);
assert.equal(explicit.reasoning.effort, "high");
});
test("CodexExecutor.refreshCredentials refreshes OAuth tokens and returns null without a refresh token", async () => {

View File

@@ -8,7 +8,7 @@ import { shouldUseNativeCodexPassthrough } from "../../open-sse/handlers/chatCor
import { translateRequest } from "../../open-sse/translator/index.ts";
import { GithubExecutor } from "../../open-sse/executors/github.ts";
import { DefaultExecutor } from "../../open-sse/executors/default.ts";
import { CodexExecutor, setDefaultFastServiceTierEnabled } from "../../open-sse/executors/codex.ts";
import { CodexExecutor } from "../../open-sse/executors/codex.ts";
import { translateNonStreamingResponse } from "../../open-sse/handlers/responseTranslator.ts";
import { extractUsageFromResponse } from "../../open-sse/handlers/usageExtractor.ts";
import {
@@ -218,20 +218,19 @@ test("shouldUseNativeCodexPassthrough only enables responses-native Codex reques
);
});
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 can apply per-connection fast service tier defaults", () => {
const executor = new CodexExecutor();
const transformed = executor.transformRequest(
"gpt-5.1-codex",
{ model: "gpt-5.1-codex", input: [] },
true,
{
providerSpecificData: {
requestDefaults: { serviceTier: "priority" },
},
}
);
assert.equal(transformed.service_tier, "priority");
});
test("CodexExecutor always requests SSE accept header", () => {
@@ -275,7 +274,8 @@ test("CodexExecutor preserves native responses payloads for Codex passthrough",
assert.equal(transformed.instructions, "custom system prompt");
assert.equal(transformed.store, false);
assert.deepEqual(transformed.metadata, { source: "codex-client" });
assert.equal(transformed.reasoning_effort, "high");
assert.equal(transformed.reasoning.effort, "high");
assert.equal(transformed.reasoning_effort, undefined);
assert.ok(!("_nativeCodexPassthrough" in transformed));
});

View File

@@ -237,6 +237,49 @@ test("createSSEStream passthrough preserves Responses API events and completion
assert.equal(onCompletePayload.providerPayload.summary.object, "response");
});
test("buildStreamSummaryFromEvents falls back to response.output_text.delta when completed output is empty", () => {
const summary = buildStreamSummaryFromEvents(
[
{
index: 0,
data: {
type: "response.output_text.delta",
delta: "Hello ",
},
},
{
index: 1,
data: {
type: "response.output_text.delta",
delta: "world",
},
},
{
index: 2,
data: {
type: "response.completed",
response: {
id: "resp_fallback",
object: "response",
model: "gpt-5.4",
status: "completed",
output: [],
usage: { output_tokens: 2 },
},
},
},
],
FORMATS.OPENAI_RESPONSES,
"gpt-5.4"
);
assert.equal(summary.object, "response");
assert.equal(summary.output[0].type, "message");
assert.equal(summary.output[0].content[0].type, "output_text");
assert.equal(summary.output[0].content[0].text, "Hello world");
assert.equal(summary.usage.output_tokens, 2);
});
test("createSSEStream translate mode aborts on Responses failure with rate limit error", async () => {
let onCompletePayload = null;