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
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- **feat(cli-tools):** applying Codex settings can also write an OmniRoute model catalog (`model_catalog_json`, opt-in checkbox, off by default) so Codex's `/model` picker lists routable provider-prefixed models; fails open with a warning when Codex can't be read ([#16910](https://github.com/diegosouzapw/OmniRoute/pull/16910)) — thanks @kalfian
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ export default function CodexToolCard({
const [modelMappings, setModelMappings] = useState<Record<string, string>>({});
const [reasoningEffort, setReasoningEffort] = useState("xhigh");
const [wireApi, setWireApi] = useState("responses");
// Opt-in: the catalog file is ~60 KB per model (MBs with a large catalog) in ~/.codex.
const [writeModelCatalog, setWriteModelCatalog] = useState(false);
const [modalOpen, setModalOpen] = useState(false);
const [modalTarget, setModalTarget] = useState<string | null>(null); // null = default model, string = mapping key
const [modelAliases, setModelAliases] = useState({});
Expand Down Expand Up @@ -190,11 +192,16 @@ export default function CodexToolCard({
reasoningEffort,
wireApi,
modelMappings,
modelCatalog: writeModelCatalog,
}),
});
const data = await res.json();
if (res.ok) {
setMessage({ type: "success", text: t("settingsApplied") });
setMessage(
data.catalogWarning
? { type: "warning", text: `${t("settingsApplied")} ${t("codexCatalogWarning")}` }
: { type: "success", text: t("settingsApplied") }
);
checkCodexStatus();
} else {
setMessage({
Expand Down Expand Up @@ -652,6 +659,19 @@ openai_base_url = "${getEffectiveBaseUrl()}"
</select>
</div>

<label
className="flex items-center gap-2 cursor-pointer pl-[8.5rem]"
title={t("codexModelCatalogHint")}
>
<input
type="checkbox"
checked={writeModelCatalog}
onChange={(e) => setWriteModelCatalog(e.target.checked)}
className="rounded border-border accent-primary"
/>
<span className="text-xs text-text-main">{t("codexModelCatalog")}</span>
</label>

<div className="h-px bg-border/50 my-2"></div>

<div className="text-[11px] text-text-muted mb-2 font-medium uppercase tracking-wider text-right">
Expand Down Expand Up @@ -703,10 +723,14 @@ openai_base_url = "${getEffectiveBaseUrl()}"

{message && (
<div
className={`flex items-center gap-2 px-2 py-1.5 rounded text-xs ${message.type === "success" ? "bg-green-500/10 text-green-600" : "bg-red-500/10 text-red-600"}`}
className={`flex items-center gap-2 px-2 py-1.5 rounded text-xs ${message.type === "success" ? "bg-green-500/10 text-green-600" : message.type === "warning" ? "bg-amber-500/10 text-amber-600" : "bg-red-500/10 text-red-600"}`}
>
<span className="material-symbols-outlined text-[14px]">
{message.type === "success" ? "check_circle" : "error"}
{message.type === "success"
? "check_circle"
: message.type === "warning"
? "warning"
: "error"}
</span>
<span>{message.text}</span>
</div>
Expand Down
45 changes: 42 additions & 3 deletions src/app/api/cli-tools/codex-settings/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,18 @@ import {
} from "@/shared/services/cliRuntime";
import { createMultiBackup } from "@/shared/services/backupService";
import { saveCliToolLastConfigured, deleteCliToolLastConfigured } from "@/lib/db/cliToolState";
import { cliModelConfigSchema } from "@/shared/validation/schemas";
import { codexSettingsConfigSchema } from "@/shared/validation/schemas";
import { isValidationFailure, validateBody } from "@/shared/validation/helpers";
import { resolveApiKey } from "@/shared/services/apiKeyResolver";
import { normalizeCodexBaseUrl } from "@/shared/utils/codexBaseUrl";
import { migrateCodexFeatureFlags } from "@/shared/utils/codexConfig";
import {
getCodexCatalogPath,
listOmniRouteChatModels,
removeCodexModelCatalog,
writeCodexModelCatalog,
type CodexCatalogWriteResult,
} from "@/lib/cliTools/codexModelCatalogWriter";
import {
configClobberRefusalResponse,
parseJsonObject,
Expand All @@ -26,6 +33,9 @@ import {
const getCodexConfigPath = () => getCliConfigPaths("codex").config;
const getCodexAuthPath = () => getCliConfigPaths("codex").auth;
const getCodexDir = () => path.dirname(getCodexConfigPath());
// TOML basic strings treat a backslash as an escape and this file's writer does not escape,
// so the catalog path is stored with forward slashes (Codex accepts them on Windows).
const toTomlPath = (filePath: string) => filePath.replace(/\\/g, "/");

// Parse TOML config to object (simple parser for codex config)
const parseToml = (content: string) => {
Expand Down Expand Up @@ -224,11 +234,12 @@ export async function POST(request: Request) {
// we always write the full key value to the config file.
const keyId = typeof rawBody?.keyId === "string" ? rawBody.keyId.trim() : null;

const validation = validateBody(cliModelConfigSchema, rawBody);
const validation = validateBody(codexSettingsConfigSchema, rawBody);
if (isValidationFailure(validation)) {
return NextResponse.json({ error: validation.error }, { status: 400 });
}
const { baseUrl, model, reasoningEffort, wireApi, modelMappings } = validation.data;
const { baseUrl, model, reasoningEffort, wireApi, modelMappings, modelCatalog } =
validation.data;
// Canonical key resolution (#13563): by keyId -> submitted apiKey -> sk_omniroute.
// Matches cline/forge/openclaw/grok-build/jcode-settings.
const apiKey = await resolveApiKey(keyId, validation.data.apiKey);
Expand Down Expand Up @@ -295,6 +306,18 @@ export async function POST(request: Request) {
delete parsed._sections["notice.model_migrations"];
}

// Point Codex's /model picker at an OmniRoute catalog. Fail-open: on any failure
// Apply still succeeds, a previous value is left alone and a warning is returned.
let catalog: CodexCatalogWriteResult | null = null;
if (modelCatalog === true) {
catalog = await writeCodexModelCatalog({
codexDir,
selectedModels: [model, ...Object.values(modelMappings ?? {})],
listModels: () => listOmniRouteChatModels(request),
});
if (catalog.catalogPath) parsed._root.model_catalog_json = toTomlPath(catalog.catalogPath);
}

// Write merged config
const configContent = toToml(parsed);
await fs.writeFile(configPath, configContent);
Expand All @@ -314,6 +337,10 @@ export async function POST(request: Request) {
success: true,
message: "Codex settings applied successfully!",
configPath,
...(catalog?.catalogPath
? { catalogPath: catalog.catalogPath, catalogModelCount: catalog.count }
: {}),
...(catalog?.warning ? { catalogWarning: catalog.warning } : {}),
});
} catch (error) {
const refusal = configClobberRefusalResponse(error);
Expand Down Expand Up @@ -368,6 +395,16 @@ export async function DELETE(request: Request) {
// Remove omniroute provider section
delete parsed._sections["model_providers.omniroute"];

// Remove the model catalog only when it is the one OmniRoute wrote
const codexDir = getCodexDir();
const catalogValue = parsed._root.model_catalog_json;
if (
typeof catalogValue === "string" &&
toTomlPath(catalogValue) === toTomlPath(getCodexCatalogPath(codexDir))
) {
delete parsed._root.model_catalog_json;
}

// Write updated config
const configContent = toToml(parsed);
await fs.writeFile(configPath, configContent);
Expand All @@ -389,6 +426,8 @@ export async function DELETE(request: Request) {
/* No auth file */
}

await removeCodexModelCatalog(codexDir);

// Clear last-configured timestamp
try {
deleteCliToolLastConfigured("codex");
Expand Down
3 changes: 3 additions & 0 deletions src/i18n/messages/am.json
Original file line number Diff line number Diff line change
Expand Up @@ -3241,6 +3241,9 @@
"copilotReloadInstruction": "ከዚያ VS Codeን ዳግም ያስጀምሩ እና በግቤት መጠየቂያው ውስጥ የAPI ቁልፉን ያስገቡ።",
"wireApiChatCompletions": "የውይይት ማጠናቀቂያዎች (/chat/completions)",
"wireApiResponses": "የምላሾች API (/responses)",
"codexModelCatalog": "የOmniRoute ሞዴሎችን በCodex /model መምረጫ ውስጥ ዘርዝር",
"codexModelCatalogHint": "ከተጫነው Codex የራሱ የሞዴል ካታሎግ ~/.codex/omniroute-models.json ይጽፋል (በአንድ ሞዴል 60 KB ገደማ)። Codexን ካሻሻሉ በኋላ እንደገና ይተግብሩ።",
"codexCatalogWarning": "የCodex /model መምረጫ አልዘመነም፦ OmniRoute የተጫነውን የCodex ሞዴል ካታሎግ ማንበብ አልቻለም።",
"cliproxyapiActionFailed": "እርምጃው አልተሳካም",
"cliproxyapiActionSucceeded": "እርምጃው ተሳክቷል",
"cliproxyapiCheckUpdates": "ዝማኔዎችን ይፈትሹ",
Expand Down
3 changes: 3 additions & 0 deletions src/i18n/messages/ar.json
Original file line number Diff line number Diff line change
Expand Up @@ -3241,6 +3241,9 @@
"copilotReloadInstruction": "ثم أعد تحميل VS Code وعيّن مفتاح API في موجه الإدخال.",
"wireApiChatCompletions": "مكتملات الدردشة (/chat/مكتملات)",
"wireApiResponses": "واجهة برمجة تطبيقات الاستجابات (/ الاستجابات)",
"codexModelCatalog": "عرض نماذج OmniRoute في منتقي ‎/model في Codex",
"codexModelCatalogHint": "يكتب ~/.codex/omniroute-models.json انطلاقًا من كتالوج النماذج الخاص بـ Codex المثبت (نحو 60 KB لكل نموذج). أعد التطبيق بعد ترقية Codex.",
"codexCatalogWarning": "لم يتم تحديث منتقي ‎/model في Codex: تعذّر على OmniRoute قراءة كتالوج نماذج Codex المثبت.",
"cliproxyapiActionFailed": "فشل الإجراء",
"cliproxyapiActionSucceeded": "تمت العملية بنجاح",
"cliproxyapiCheckUpdates": "تحقق من التحديثات",
Expand Down
3 changes: 3 additions & 0 deletions src/i18n/messages/az.json
Original file line number Diff line number Diff line change
Expand Up @@ -3241,6 +3241,9 @@
"copilotReloadInstruction": "Sonra VS Code-u yenidən yükləyin və daxiletmə sorğusunda API açarını təyin edin.",
"wireApiChatCompletions": "Söhbət Tamamlamaları (/chat/tamamlamalar)",
"wireApiResponses": "Responses API (/cavablar)",
"codexModelCatalog": "OmniRoute modellərini Codex-in /model seçicisində göstər",
"codexModelCatalogHint": "Quraşdırılmış Codex-in öz model kataloqundan ~/.codex/omniroute-models.json faylını yazır (hər model üçün təxminən 60 KB). Codex-i yenilədikdən sonra yenidən tətbiq edin.",
"codexCatalogWarning": "Codex-in /model seçicisi yenilənmədi: OmniRoute quraşdırılmış Codex model kataloqunu oxuya bilmədi.",
"cliproxyapiActionFailed": "Əməliyyat uğursuz oldu",
"cliproxyapiActionSucceeded": "Əməliyyat uğurla başa çatdı",
"cliproxyapiCheckUpdates": "Yeniləmələri Yoxla",
Expand Down
3 changes: 3 additions & 0 deletions src/i18n/messages/bg.json
Original file line number Diff line number Diff line change
Expand Up @@ -3241,6 +3241,9 @@
"copilotReloadInstruction": "След това рестартирайте VS Code и задайте API ключа в полето за въвеждане.",
"wireApiChatCompletions": "Завършвания на чат (/chat/completions)",
"wireApiResponses": "API за отговори (/отговори)",
"codexModelCatalog": "Показване на моделите на OmniRoute в избора /model на Codex",
"codexModelCatalogHint": "Записва ~/.codex/omniroute-models.json от собствения каталог с модели на инсталирания Codex (около 60 KB на модел). Приложете отново след обновяване на Codex.",
"codexCatalogWarning": "Изборът /model на Codex не беше обновен: OmniRoute не успя да прочете каталога с модели на инсталирания Codex.",
"cliproxyapiActionFailed": "Действието не успя",
"cliproxyapiActionSucceeded": "Действието беше успешно",
"cliproxyapiCheckUpdates": "Провери актуализации",
Expand Down
3 changes: 3 additions & 0 deletions src/i18n/messages/bn.json
Original file line number Diff line number Diff line change
Expand Up @@ -3241,6 +3241,9 @@
"copilotReloadInstruction": "তারপর VS Code রিলোড করুন এবং ইনপুট প্রম্পটে API কী সেট করুন।",
"wireApiChatCompletions": "চ্যাট সমাপ্তি (/চ্যাট/সম্পূর্ণতা)",
"wireApiResponses": "প্রতিক্রিয়া API (/প্রতিক্রিয়া)",
"codexModelCatalog": "Codex-এর /model পিকারে OmniRoute মডেলগুলি দেখান",
"codexModelCatalogHint": "ইনস্টল করা Codex-এর নিজস্ব মডেল ক্যাটালগ থেকে ~/.codex/omniroute-models.json লেখে (প্রতি মডেলে প্রায় 60 KB)। Codex আপগ্রেড করার পরে আবার প্রয়োগ করুন।",
"codexCatalogWarning": "Codex-এর /model পিকার আপডেট হয়নি: OmniRoute ইনস্টল করা Codex মডেল ক্যাটালগ পড়তে পারেনি।",
"cliproxyapiActionFailed": "কার্য সম্পন্ন হয়নি",
"cliproxyapiActionSucceeded": "কার্য সফল হয়েছে",
"cliproxyapiCheckUpdates": "আপডেট পরীক্ষা করুন",
Expand Down
3 changes: 3 additions & 0 deletions src/i18n/messages/bs.json
Original file line number Diff line number Diff line change
Expand Up @@ -3241,6 +3241,9 @@
"copilotReloadInstruction": "Zatim ponovo učitajte VS Code i postavite API ključ u promptu za unos.",
"wireApiChatCompletions": "Dovršavanja razgovora (/chat/completions)",
"wireApiResponses": "API odgovora (/responses)",
"codexModelCatalog": "Prikaži OmniRoute modele u Codex /model biraču",
"codexModelCatalogHint": "Upisuje ~/.codex/omniroute-models.json iz vlastitog kataloga modela instaliranog Codexa (oko 60 KB po modelu). Ponovo primijenite nakon nadogradnje Codexa.",
"codexCatalogWarning": "Codex /model birač nije ažuriran: OmniRoute nije mogao pročitati katalog modela instaliranog Codexa.",
"cliproxyapiActionFailed": "Akcija nije uspjela",
"cliproxyapiActionSucceeded": "Akcija je uspjela",
"cliproxyapiCheckUpdates": "Provjeri ažuriranja",
Expand Down
3 changes: 3 additions & 0 deletions src/i18n/messages/cs.json
Original file line number Diff line number Diff line change
Expand Up @@ -3241,6 +3241,9 @@
"copilotReloadInstruction": "Poté znovu načtěte VS Code a nastavte klíč API ve vstupním poli.",
"wireApiChatCompletions": "Dokončení chatu (/chat/completions)",
"wireApiResponses": "API odpovědí (/responses)",
"codexModelCatalog": "Zobrazit modely OmniRoute ve výběru /model v Codexu",
"codexModelCatalogHint": "Zapíše ~/.codex/omniroute-models.json z vlastního katalogu modelů nainstalovaného Codexu (asi 60 KB na model). Po aktualizaci Codexu použijte znovu.",
"codexCatalogWarning": "Výběr /model v Codexu nebyl aktualizován: OmniRoute nedokázal načíst katalog modelů nainstalovaného Codexu.",
"cliproxyapiActionFailed": "Akce selhala",
"cliproxyapiActionSucceeded": "Akce byla úspěšná",
"cliproxyapiCheckUpdates": "Zkontrolovat aktualizace",
Expand Down
3 changes: 3 additions & 0 deletions src/i18n/messages/da.json
Original file line number Diff line number Diff line change
Expand Up @@ -3241,6 +3241,9 @@
"copilotReloadInstruction": "Genstart derefter VS Code, og angiv API-nøglen i inputfeltet.",
"wireApiChatCompletions": "Chatafslutninger (/chat/afslutninger)",
"wireApiResponses": "Svar-API (/responses)",
"codexModelCatalog": "Vis OmniRoute-modeller i Codex' /model-vælger",
"codexModelCatalogHint": "Skriver ~/.codex/omniroute-models.json ud fra den installerede Codex' egen modelkatalog (ca. 60 KB pr. model). Anvend igen efter opgradering af Codex.",
"codexCatalogWarning": "Codex' /model-vælger blev ikke opdateret: OmniRoute kunne ikke læse den installerede Codex' modelkatalog.",
"cliproxyapiActionFailed": "Handlingen mislykkedes",
"cliproxyapiActionSucceeded": "Handlingen lykkedes",
"cliproxyapiCheckUpdates": "Tjek Opdateringer",
Expand Down
3 changes: 3 additions & 0 deletions src/i18n/messages/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -3241,6 +3241,9 @@
"copilotReloadInstruction": "Laden Sie dann VS Code neu und legen Sie den API-Schlüssel in der Eingabeaufforderung fest.",
"wireApiChatCompletions": "Chat-Abschlüsse (/chat/completions)",
"wireApiResponses": "Antwort-API (/responses)",
"codexModelCatalog": "OmniRoute-Modelle in der /model-Auswahl von Codex anzeigen",
"codexModelCatalogHint": "Schreibt ~/.codex/omniroute-models.json aus dem eigenen Modellkatalog des installierten Codex (ca. 60 KB pro Modell). Nach einem Codex-Update erneut anwenden.",
"codexCatalogWarning": "Die /model-Auswahl von Codex wurde nicht aktualisiert: OmniRoute konnte den Modellkatalog des installierten Codex nicht lesen.",
"cliproxyapiActionFailed": "Aktion fehlgeschlagen",
"cliproxyapiActionSucceeded": "Aktion erfolgreich",
"cliproxyapiCheckUpdates": "Updates überprüfen",
Expand Down
3 changes: 3 additions & 0 deletions src/i18n/messages/el.json
Original file line number Diff line number Diff line change
Expand Up @@ -3241,6 +3241,9 @@
"copilotReloadInstruction": "Στη συνέχεια, επαναφορτώστε το VS Code και ορίστε το κλειδί API στο πεδίο εισαγωγής.",
"wireApiChatCompletions": "Ολοκληρώσεις συνομιλίας (/chat/completions)",
"wireApiResponses": "API αποκρίσεων (/responses)",
"codexModelCatalog": "Εμφάνιση μοντέλων OmniRoute στον επιλογέα /model του Codex",
"codexModelCatalogHint": "Γράφει το ~/.codex/omniroute-models.json από τον κατάλογο μοντέλων του εγκατεστημένου Codex (περίπου 60 KB ανά μοντέλο). Εφαρμόστε ξανά μετά από αναβάθμιση του Codex.",
"codexCatalogWarning": "Ο επιλογέας /model του Codex δεν ενημερώθηκε: το OmniRoute δεν μπόρεσε να διαβάσει τον κατάλογο μοντέλων του εγκατεστημένου Codex.",
"cliproxyapiActionFailed": "Η ενέργεια απέτυχε",
"cliproxyapiActionSucceeded": "Η ενέργεια ολοκληρώθηκε με επιτυχία",
"cliproxyapiCheckUpdates": "Έλεγχος ενημερώσεων",
Expand Down
3 changes: 3 additions & 0 deletions src/i18n/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -3241,6 +3241,9 @@
"copilotReloadInstruction": "Then reload VS Code and set the API key in the input prompt.",
"wireApiChatCompletions": "Chat Completions (/chat/completions)",
"wireApiResponses": "Responses API (/responses)",
"codexModelCatalog": "List OmniRoute models in Codex's /model picker",
"codexModelCatalogHint": "Writes ~/.codex/omniroute-models.json from your installed Codex's own model catalog (about 60 KB per model). Re-apply after upgrading Codex.",
"codexCatalogWarning": "Codex's /model picker was not updated: OmniRoute could not read the installed Codex model catalog.",
"cliproxyapiActionFailed": "Action failed",
"cliproxyapiActionSucceeded": "Action succeeded",
"cliproxyapiCheckUpdates": "Check Updates",
Expand Down
3 changes: 3 additions & 0 deletions src/i18n/messages/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -3241,6 +3241,9 @@
"copilotReloadInstruction": "A continuación, vuelve a cargar VS Code y establece la clave de API en el campo de entrada.",
"wireApiChatCompletions": "Finalizaciones de chat (/chat/completions)",
"wireApiResponses": "API de respuestas (/respuestas)",
"codexModelCatalog": "Mostrar los modelos de OmniRoute en el selector /model de Codex",
"codexModelCatalogHint": "Escribe ~/.codex/omniroute-models.json a partir del catálogo de modelos del propio Codex instalado (unos 60 KB por modelo). Vuelve a aplicar tras actualizar Codex.",
"codexCatalogWarning": "No se actualizó el selector /model de Codex: OmniRoute no pudo leer el catálogo de modelos del Codex instalado.",
"cliproxyapiActionFailed": "Acción fallida",
"cliproxyapiActionSucceeded": "Acción exitosa",
"cliproxyapiCheckUpdates": "Buscar Actualizaciones",
Expand Down
Loading
Loading