diff --git a/docs-site/public/pr-screenshots/provider-discovery-recovery.png b/docs-site/public/pr-screenshots/provider-discovery-recovery.png new file mode 100644 index 0000000000..76d58d2520 Binary files /dev/null and b/docs-site/public/pr-screenshots/provider-discovery-recovery.png differ diff --git a/docs-site/src/content/docs/guides/providers.md b/docs-site/src/content/docs/guides/providers.md index 46348e5371..a895101933 100644 --- a/docs-site/src/content/docs/guides/providers.md +++ b/docs-site/src/content/docs/guides/providers.md @@ -245,6 +245,12 @@ full model slug sent upstream. ClinePass quota is shared by the account across r weekly, and monthly limits. A 2026-08-13 live probe verified that every static ClinePass model accepts `low`, `medium`, `high`, `xhigh`, and `max` at the gateway input boundary. opencodex preserves those requested tiers; any backend-specific normalization remains ClinePass's responsibility. +Cline documents the Chat Completions route but not an OpenAI-compatible `GET /models` route, so +opencodex uses the maintained subscription catalog instead of sending an unsupported discovery probe. + +**MiMo Free** also uses a maintained static catalog. Its anonymous JWT-backed chat endpoint has no +model-list contract; `mimo-auto` remains available without an API key and opencodex does not append +an invalid `/models` probe to the chat URL. **Cline** is the same API key and endpoint on pay-as-you-go usage billing across 100+ models (OpenRouter-style ids like `anthropic/claude-sonnet-4-6`). Cline's promotional free models are only diff --git a/docs-site/src/content/docs/guides/web-dashboard.md b/docs-site/src/content/docs/guides/web-dashboard.md index a32643fbb6..71be214abd 100644 --- a/docs-site/src/content/docs/guides/web-dashboard.md +++ b/docs-site/src/content/docs/guides/web-dashboard.md @@ -49,7 +49,7 @@ the browser or password manager's decision. | **Add provider** | Search registry-backed presets for account login, API-key services, local servers, or a custom endpoint. | | **Codex Auth** | Add ChatGPT/Codex pool accounts, select the next-session account, refresh 5h / weekly / 30d quotas, enable or disable quota auto-switch, set its 1–100% threshold, and configure transient-failure failover. | | **Subagents** | Feature up to five bare native or namespaced routed models in the `spawn_agent` override list. | -| **Models** | Toggle native GPT and routed models, set provider allowlists and context caps, choose v1/base/v2, and configure the v2 thread limit. Configured providers stay visible as zero-model groups when discovery is off or returns no rows. | +| **Models** | Toggle native GPT and routed models, set provider allowlists and context caps, choose v1/base/v2, and configure the v2 thread limit. Configured providers stay visible as zero-model groups when discovery is off or returns no rows. When discovery fails, the badge hover keeps configured fallback models explicit and directs OAuth providers, such as xAI, to reauthenticate with `ocx login ` or dashboard reauthentication; other providers link to endpoint and discovery settings. | | **Logs** | Auto-refresh recent requests with tokens, requested effort and (when available) effective outbound effort, resolved model, provider, status, request id, duration, and error details. The detail view includes the exact reasoning wire field when the adapter emits one. Filter by opaque conversation/session id (when the client sends one) to total tokens and estimated list-price cost for the currently loaded Logs ring. | | **Usage / Debug** | Inspect token-usage coverage and trends, or enable opt-in provider transport and usage-extraction diagnostics. | | **Storage** | Read-only CODEX_HOME disk breakdown (sessions, archives, DBs, attachments). Optional archived cleanup: preview the oldest N%, then quarantine to `CODEX_HOME/.trash` (default) or permanently delete behind an explicit checkbox. **Auto-cleanup policy** is opt-in and **default OFF** (`storageCleanupPolicy.enabled`); configure threshold/target/schedule/mode on the Storage page, or trigger **Run now**. Quarantined entries can be restored from the Storage page (JSONL + threads). Active sessions stay read-only. Cleanup and restore are refused while Codex holds the newest/active `state_*.sqlite` locked. | diff --git a/gui/src/components/provider-workspace/ProviderSettings.tsx b/gui/src/components/provider-workspace/ProviderSettings.tsx index 26ca85fa35..4c81633a2b 100644 --- a/gui/src/components/provider-workspace/ProviderSettings.tsx +++ b/gui/src/components/provider-workspace/ProviderSettings.tsx @@ -36,7 +36,7 @@ export default function ProviderSettings({ onRegisterSave?: (save: (() => Promise) | null) => void; }) { const t = useT(); - const initialAuth = String(item.authMode ?? (item.keyOptional ? "local" : "key")); + const initialAuth = String(item.authMode ?? "key"); const [adapter, setAdapter] = useState(item.adapter); const [baseUrl, setBaseUrl] = useState(item.baseUrl); const [defaultModel, setDefaultModel] = useState(item.defaultModel ?? ""); @@ -59,7 +59,7 @@ export default function ProviderSettings({ setAdapter(item.adapter); setBaseUrl(item.baseUrl); setDefaultModel(item.defaultModel ?? ""); - setAuthMode(String(item.authMode ?? (item.keyOptional ? "local" : "key"))); + setAuthMode(String(item.authMode ?? "key")); setApiKeyTransport(item.apiKeyTransport ?? "x-api-key"); setNote(item.note ?? ""); setAllowPrivateNetwork(item.allowPrivateNetwork ?? false); @@ -112,7 +112,7 @@ export default function ProviderSettings({ const dirty = adapter.trim() !== item.adapter || baseUrl.trim() !== item.baseUrl || defaultModel.trim() !== (item.defaultModel ?? "") - || authMode !== String(item.authMode ?? (item.keyOptional ? "local" : "key")) + || authMode !== String(item.authMode ?? "key") || (adapter.trim() === "anthropic" && authMode === "key" && apiKeyTransport !== (item.apiKeyTransport ?? "x-api-key")) || note.trim() !== (item.note ?? "") || allowPrivateNetwork !== (item.allowPrivateNetwork ?? false) @@ -335,10 +335,17 @@ export default function ProviderSettings({ {t("pws.allowPrivateNetwork")} {dirty && ( diff --git a/gui/src/i18n/de.ts b/gui/src/i18n/de.ts index 1ddf5a014f..22587a71a7 100644 --- a/gui/src/i18n/de.ts +++ b/gui/src/i18n/de.ts @@ -560,6 +560,9 @@ export const de: Record = { "models.discoveryFailedNetwork": "Die Modellerkennung ist an einem Netzwerkfehler gescheitert.", "models.discoveryFailedProvider": "Der Anbieter meldete einen Fehler bei der Modellerkennung.", "models.discoveryFailedGeneric": "Die Modellerkennung ist fehlgeschlagen.", + "models.discoveryRecoveryReauth": "Melden Sie sich erneut an, um die Live-Modellerkennung wiederherzustellen. Konfigurierte Modelle bleiben verfügbar.", + "models.discoveryRecoverySettings": "Konfigurierte Modelle bleiben verfügbar. Prüfen Sie den Endpunkt oder deaktivieren Sie die Live-Modellerkennung in den Anbietereinstellungen.", + "models.openProviderLogin": "Anbieter-Anmeldung öffnen", "models.openProviderSettings": "Anbietereinstellungen öffnen", "models.loading": "Lädt…", "models.search": "Modelle suchen…", @@ -1642,6 +1645,7 @@ export const de: Record = { "pws.allowPrivateNetwork": "Lokales/privates Netzwerk erlauben", "pws.liveModels": "Modelle beim Anbieter erkennen", "pws.liveModelsDesc": "Lädt den Live-Modellkatalog des Anbieters. Ausschalten, um nur konfigurierte statische Modelle zu verwenden.", + "pws.liveModelsUnsupportedDesc": "Dieser Anbieter hat keinen Endpunkt für die Modellliste. OpenCodex verwendet seinen gepflegten statischen Katalog.", "pws.optionalPlaceholder": "Optional", "pws.providerId": "Anbieter-ID", "pws.reauth": "Erneute Anmeldung erforderlich", diff --git a/gui/src/i18n/en.ts b/gui/src/i18n/en.ts index 2d2fc96606..5000030fe7 100644 --- a/gui/src/i18n/en.ts +++ b/gui/src/i18n/en.ts @@ -585,6 +585,9 @@ export const en = { "models.discoveryFailedNetwork": "Model discovery failed due to a network error.", "models.discoveryFailedProvider": "The provider reported a model discovery error.", "models.discoveryFailedGeneric": "Model discovery failed.", + "models.discoveryRecoveryReauth": "Re-login to restore live model discovery. Configured models remain available.", + "models.discoveryRecoverySettings": "Configured models remain available. Check the endpoint or disable live model discovery in provider settings.", + "models.openProviderLogin": "Open provider login", "models.openProviderSettings": "Open provider settings", "models.loading": "Loading…", "models.search": "Search models…", @@ -1153,6 +1156,7 @@ export const en = { "pws.allowPrivateNetwork": "Allow local/private network", "pws.liveModels": "Discover models from provider", "pws.liveModelsDesc": "Fetch the provider's live model catalog. Turn this off to use only configured/static models.", + "pws.liveModelsUnsupportedDesc": "This provider has no model-list endpoint. OpenCodex uses its maintained static catalog.", "pws.optionalPlaceholder": "Optional", "pws.providerId": "Provider ID", "pws.reauth": "Needs re-auth", diff --git a/gui/src/i18n/ja.ts b/gui/src/i18n/ja.ts index d87f58f9de..6a60def98a 100644 --- a/gui/src/i18n/ja.ts +++ b/gui/src/i18n/ja.ts @@ -541,6 +541,9 @@ export const ja: Record = { "models.discoveryFailedNetwork": "ネットワークエラーによりモデル検出に失敗しました。", "models.discoveryFailedProvider": "プロバイダーがモデル検出エラーを報告しました。", "models.discoveryFailedGeneric": "モデル検出に失敗しました。", + "models.discoveryRecoveryReauth": "再ログインしてライブモデル検出を復元してください。設定済みのモデルは引き続き使用できます。", + "models.discoveryRecoverySettings": "設定済みのモデルは引き続き使用できます。エンドポイントを確認するか、プロバイダー設定でライブモデル検出を無効にしてください。", + "models.openProviderLogin": "プロバイダーログインを開く", "models.openProviderSettings": "プロバイダー設定を開く", "models.loading": "読み込み中…", "models.search": "モデルを検索…", @@ -1095,6 +1098,7 @@ export const ja: Record = { "pws.allowPrivateNetwork": "ローカル/プライベートネットワークを許可", "pws.liveModels": "プロバイダーからモデルを検出", "pws.liveModelsDesc": "プロバイダーのライブモデルカタログを取得します。オフにすると設定済みの静的モデルのみを使用します。", + "pws.liveModelsUnsupportedDesc": "このプロバイダーにはモデル一覧エンドポイントがありません。OpenCodex は管理された静的カタログを使用します。", "pws.optionalPlaceholder": "任意", "pws.providerId": "プロバイダー ID", "pws.reauth": "再認証が必要", diff --git a/gui/src/i18n/ko.ts b/gui/src/i18n/ko.ts index bbf55e9071..7ff4a4d62f 100644 --- a/gui/src/i18n/ko.ts +++ b/gui/src/i18n/ko.ts @@ -571,6 +571,9 @@ export const ko: Record = { "models.discoveryFailedNetwork": "네트워크 오류로 모델 검색에 실패했습니다.", "models.discoveryFailedProvider": "프로바이더가 모델 검색 오류를 보고했습니다.", "models.discoveryFailedGeneric": "모델 검색에 실패했습니다.", + "models.discoveryRecoveryReauth": "다시 로그인하여 실시간 모델 검색을 복구하세요. 구성된 모델은 계속 사용할 수 있습니다.", + "models.discoveryRecoverySettings": "구성된 모델은 계속 사용할 수 있습니다. 엔드포인트를 확인하거나 프로바이더 설정에서 실시간 모델 검색을 끄세요.", + "models.openProviderLogin": "프로바이더 로그인 열기", "models.openProviderSettings": "프로바이더 설정 열기", "models.loading": "불러오는 중…", "models.search": "모델 검색…", @@ -1669,6 +1672,7 @@ export const ko: Record = { "pws.allowPrivateNetwork": "로컬/사설 네트워크 허용", "pws.liveModels": "프로바이더에서 모델 검색", "pws.liveModelsDesc": "프로바이더의 실시간 모델 카탈로그를 가져옵니다. 끄면 설정된 정적 모델만 사용합니다.", + "pws.liveModelsUnsupportedDesc": "이 프로바이더에는 모델 목록 엔드포인트가 없습니다. OpenCodex는 관리되는 정적 카탈로그를 사용합니다.", "pws.optionalPlaceholder": "선택사항", "pws.providerId": "프로바이더 ID", "pws.reauth": "재인증 필요", diff --git a/gui/src/i18n/ru.ts b/gui/src/i18n/ru.ts index 60064fd394..c66357df2a 100644 --- a/gui/src/i18n/ru.ts +++ b/gui/src/i18n/ru.ts @@ -573,6 +573,9 @@ export const ru: Record = { "models.discoveryFailedNetwork": "Обнаружение моделей не удалось из-за сетевой ошибки.", "models.discoveryFailedProvider": "Провайдер сообщил об ошибке обнаружения моделей.", "models.discoveryFailedGeneric": "Не удалось обнаружить модели.", + "models.discoveryRecoveryReauth": "Войдите снова, чтобы восстановить обнаружение моделей. Настроенные модели остаются доступными.", + "models.discoveryRecoverySettings": "Настроенные модели остаются доступными. Проверьте конечную точку или отключите обнаружение моделей в настройках провайдера.", + "models.openProviderLogin": "Открыть вход провайдера", "models.openProviderSettings": "Открыть настройки провайдера", "models.loading": "Загрузка…", "models.search": "Поиск моделей…", @@ -1137,6 +1140,7 @@ export const ru: Record = { "pws.allowPrivateNetwork": "Разрешить локальную/частную сеть", "pws.liveModels": "Обнаруживать модели провайдера", "pws.liveModelsDesc": "Загружать актуальный каталог моделей провайдера. Выключите, чтобы использовать только настроенные статические модели.", + "pws.liveModelsUnsupportedDesc": "У этого провайдера нет эндпоинта списка моделей. OpenCodex использует поддерживаемый статический каталог.", "pws.optionalPlaceholder": "Необязательно", "pws.providerId": "ID провайдера", "pws.reauth": "Нужна переавторизация", diff --git a/gui/src/i18n/tr.ts b/gui/src/i18n/tr.ts index 32b11d6196..82a7ed3eb9 100644 --- a/gui/src/i18n/tr.ts +++ b/gui/src/i18n/tr.ts @@ -576,6 +576,9 @@ export const tr: Record = { "models.discoveryFailedNetwork": "Model keşfi ağ hatası nedeniyle başarısız oldu.", "models.discoveryFailedProvider": "Sağlayıcı bir model keşfi hatası bildirdi.", "models.discoveryFailedGeneric": "Model keşfi başarısız oldu.", + "models.discoveryRecoveryReauth": "Canlı model keşfini geri yüklemek için yeniden giriş yapın. Yapılandırılmış modeller kullanılabilir durumda kalır.", + "models.discoveryRecoverySettings": "Yapılandırılmış modeller kullanılabilir durumda kalır. Uç noktayı kontrol edin veya sağlayıcı ayarlarında canlı model keşfini kapatın.", + "models.openProviderLogin": "Sağlayıcı girişini aç", "models.openProviderSettings": "Sağlayıcı ayarlarını aç", "models.loading": "Yükleniyor…", "models.search": "Modellerde ara…", @@ -1144,6 +1147,7 @@ export const tr: Record = { "pws.allowPrivateNetwork": "Yerel/özel ağa izin ver", "pws.liveModels": "Sağlayıcıdan canlı model keşfet", "pws.liveModelsDesc": "Sağlayıcının canlı model kataloğunu çekin.", + "pws.liveModelsUnsupportedDesc": "Bu sağlayıcının model listesi uç noktası yoktur. OpenCodex bakımı yapılan statik kataloğunu kullanır.", "pws.optionalPlaceholder": "İsteğe bağlı", "pws.providerId": "Sağlayıcı ID", "pws.reauth": "Yeniden doğrulama gerekiyor", diff --git a/gui/src/i18n/zh-TW.ts b/gui/src/i18n/zh-TW.ts index 3edb42ebb9..3d9483b7cc 100644 --- a/gui/src/i18n/zh-TW.ts +++ b/gui/src/i18n/zh-TW.ts @@ -441,6 +441,9 @@ export const zhTW: Record = { "models.discoveryFailedNetwork": "由於網路錯誤,模型發現失敗。", "models.discoveryFailedProvider": "供應商報告了模型發現錯誤。", "models.discoveryFailedGeneric": "模型發現失敗。", + "models.discoveryRecoveryReauth": "請重新登入以恢復即時模型發現。已設定的模型仍可使用。", + "models.discoveryRecoverySettings": "已設定的模型仍可使用。請檢查端點,或在供應商設定中關閉即時模型發現。", + "models.openProviderLogin": "開啟供應商登入", "models.openProviderSettings": "開啟供應商設定", "models.loading": "載入中…", "models.search": "搜尋模型…", @@ -934,6 +937,7 @@ export const zhTW: Record = { "pws.allowPrivateNetwork": "允許本地/私有網路", "pws.liveModels": "從供應商發現模型", "pws.liveModelsDesc": "取得供應商的即時模型目錄。關閉後僅使用已配置的靜態模型。", + "pws.liveModelsUnsupportedDesc": "此供應商沒有模型清單端點。OpenCodex 將使用其維護的靜態目錄。", "pws.optionalPlaceholder": "可選", "pws.providerId": "供應商 ID", "pws.reauth": "需要重新認證", diff --git a/gui/src/i18n/zh.ts b/gui/src/i18n/zh.ts index 899e1fd6c2..951fedc159 100644 --- a/gui/src/i18n/zh.ts +++ b/gui/src/i18n/zh.ts @@ -568,6 +568,9 @@ export const zh: Record = { "models.discoveryFailedNetwork": "由于网络错误,模型发现失败。", "models.discoveryFailedProvider": "提供方报告了模型发现错误。", "models.discoveryFailedGeneric": "模型发现失败。", + "models.discoveryRecoveryReauth": "请重新登录以恢复实时模型发现。已配置的模型仍可使用。", + "models.discoveryRecoverySettings": "已配置的模型仍可使用。请检查端点,或在提供方设置中关闭实时模型发现。", + "models.openProviderLogin": "打开提供方登录", "models.openProviderSettings": "打开提供方设置", "models.loading": "加载中…", "models.search": "搜索模型…", @@ -1662,6 +1665,7 @@ export const zh: Record = { "pws.allowPrivateNetwork": "允许本地/私有网络", "pws.liveModels": "从提供方发现模型", "pws.liveModelsDesc": "获取提供方的实时模型目录。关闭后仅使用已配置的静态模型。", + "pws.liveModelsUnsupportedDesc": "此提供方没有模型列表端点。OpenCodex 将使用其维护的静态目录。", "pws.optionalPlaceholder": "可选", "pws.providerId": "提供商 ID", "pws.reauth": "需要重新认证", diff --git a/gui/src/models-groups.ts b/gui/src/models-groups.ts index e6cce7f5cf..f9a5df4a6b 100644 --- a/gui/src/models-groups.ts +++ b/gui/src/models-groups.ts @@ -21,6 +21,7 @@ export interface ConfiguredProviderSummary { export interface ProviderModelGroup { provider: string; + authMode?: string; rows: Row[]; native: boolean; liveModels: boolean; @@ -56,6 +57,7 @@ export function buildProviderModelGroups 0 && providerRows.every(row => row.native === true), liveModels: configured?.liveModels !== false, diff --git a/gui/src/pages/Models.tsx b/gui/src/pages/Models.tsx index bf63718c2c..874c4210a5 100644 --- a/gui/src/pages/Models.tsx +++ b/gui/src/pages/Models.tsx @@ -48,7 +48,7 @@ import { THREAD_OPTION_SET, THREAD_OPTIONS, writeCollapsedProviders, - discoveryFailureLabel, + discoveryFailureTitle, type ModelRow, type ProviderContextCapsResponse, type ShadowCallData, @@ -912,7 +912,7 @@ export default function Models({ apiBase }: { apiBase: string }) { const selectedModelMap = selectedModels ?? {}; const renderGroup = (group: ProviderModelGroup) => { - const { provider, rows, native, liveModels, discovery } = group; + const { provider, rows, native, liveModels, discovery, authMode } = group; const isCollapsed = collapsed.has(provider); // Final visibility, not just the disable flag: a model is visible to Codex only when the // provider allowlist admits it AND it is not disabled. Reading `disabled` alone made the @@ -970,7 +970,7 @@ export default function Models({ apiBase }: { apiBase: string }) { {t("models.discoveryFailedBadge")} @@ -1054,7 +1054,7 @@ export default function Models({ apiBase }: { apiBase: string }) {
{isNative &&

{t("models.nativeHint")}

} {rows.length === 0 && ( - + )} {rows.length > PAGE / 2 && ( {failed && showFailureBadge && <>{t("models.discoveryFailedBadge")}{" "}} {failed - ? `${discoveryFailureLabel(t, failed)} ` + ? `${discoveryFailureLabel(t, failed)} ${discoveryRecoveryLabel(t, authMode)} ` : `${t(liveModels ? "models.emptyDiscovery" : "models.emptyDiscoveryDisabled")} `}