fix(providers): improve refresh validation and model catalog UI (#3261)

Provider refresh/validation, OpenRouter catalog and proxy UI fixes — incl. NVIDIA NIM /models-suffix path fix (real-VPS validated).

Integrated into release/v3.8.12. Thanks @strangersp.
This commit is contained in:
Thiago Reis
2026-06-06 00:26:40 -03:00
committed by GitHub
parent e364764dc7
commit 583bceb53d
9 changed files with 339 additions and 129 deletions

View File

@@ -582,6 +582,7 @@ interface PassthroughModelRowProps {
modelId: string;
fullModel: string;
source?: string;
isFree?: boolean;
isHidden?: boolean;
copied?: string;
onCopy: (text: string, key: string) => void;
@@ -966,6 +967,7 @@ function ModelCompatPopover({
getUpstreamHeadersRecord,
onCompatPatch,
showDeveloperToggle = true,
compact = false,
disabled,
}: {
t: (key: string) => string;
@@ -981,6 +983,7 @@ function ModelCompatPopover({
}
) => void;
showDeveloperToggle?: boolean;
compact?: boolean;
disabled?: boolean;
}) {
const [open, setOpen] = useState(false);
@@ -1123,7 +1126,7 @@ function ModelCompatPopover({
title={t("compatAdjustmentsTitle")}
>
<span className="material-symbols-outlined text-base leading-none">tune</span>
{t("compatButtonLabel")}
{!compact && t("compatButtonLabel")}
</button>
{open &&
typeof document !== "undefined" &&
@@ -4518,8 +4521,8 @@ export default function ProviderDetailPage() {
/>
<span className="text-sm font-medium text-text-muted">
{selectedIds.size > 0
? `${selectedIds.size} selected`
: `${connections.length} accounts`}
? t("selectedCount", { count: selectedIds.size })
: t("accountsCount", { count: connections.length })}
</span>
</label>
@@ -4677,8 +4680,8 @@ export default function ProviderDetailPage() {
/>
<span className="text-sm font-medium text-text-muted">
{selectedIds.size > 0
? `${selectedIds.size} selected`
: `${connections.length} accounts`}
? t("selectedCount", { count: selectedIds.size })
: t("accountsCount", { count: connections.length })}
</span>
</label>
@@ -4691,7 +4694,7 @@ export default function ProviderDetailPage() {
loading={distributingProxies}
onClick={() => handleDistributeProxies()}
>
Distribute Proxies
{t("distributeProxies")}
</Button>
)}
{selectedIds.size > 0 && (
@@ -5726,6 +5729,7 @@ function PassthroughModelsSection({
alias: string | null;
displayName: string;
source: string;
isFree: boolean;
isHidden: boolean;
}> = [];
const seenModelIds = new Set<string>();
@@ -5746,6 +5750,10 @@ function PassthroughModelsSection({
alias: aliasByModelId.get(model.id) || null,
displayName: model.name || model.id,
source,
isFree:
Boolean((model as any).free) ||
model.id.endsWith(":free") ||
/\bgr[aá]tis\b|\bfree\b/i.test(model.name || ""),
isHidden: isModelHidden(model.id),
});
seenModelIds.add(model.id);
@@ -5773,6 +5781,10 @@ function PassthroughModelsSection({
alias: alias as string,
displayName: alias as string,
source: customModel ? customModel.source || "custom" : "alias",
isFree:
modelId.endsWith(":free") ||
Boolean((customModel as any)?.free) ||
/\bgr[aá]tis\b|\bfree\b/i.test(customModel?.name || alias || ""),
isHidden: isModelHidden(modelId),
});
seenModelIds.add(modelId);
@@ -5876,27 +5888,33 @@ function PassthroughModelsSection({
selectAllDisabled={hiddenFilteredCount === 0 || bulkTogglePending}
deselectAllDisabled={visibleFilteredCount === 0 || bulkTogglePending}
/>
{filteredModels.map(({ modelId, fullModel, alias, isHidden, source }) => (
<PassthroughModelRow
key={fullModel as string}
modelId={modelId}
fullModel={fullModel}
source={source}
isHidden={isHidden}
copied={copied}
onCopy={onCopy}
onDeleteAlias={source === "alias" && alias ? () => onDeleteAlias(alias) : undefined}
t={t}
showDeveloperToggle
effectiveModelNormalize={effectiveModelNormalize}
effectiveModelPreserveDeveloper={effectiveModelPreserveDeveloper}
getUpstreamHeadersRecord={(p) => getUpstreamHeadersRecord(modelId, p)}
saveModelCompatFlags={saveModelCompatFlags}
compatDisabled={compatSavingModelId === modelId}
onToggleHidden={onToggleHidden}
togglingHidden={togglingModelId === modelId}
/>
))}
<div className="grid grid-cols-1 gap-3 xl:grid-cols-2">
{filteredModels.map(({ modelId, fullModel, alias, isHidden, source, isFree }) => (
<PassthroughModelRow
key={fullModel as string}
modelId={modelId}
fullModel={fullModel}
source={source}
isFree={isFree}
isHidden={isHidden}
copied={copied}
onCopy={onCopy}
onDeleteAlias={source === "alias" && alias ? () => onDeleteAlias(alias) : undefined}
t={t}
showDeveloperToggle
effectiveModelNormalize={effectiveModelNormalize}
effectiveModelPreserveDeveloper={effectiveModelPreserveDeveloper}
getUpstreamHeadersRecord={(p) => getUpstreamHeadersRecord(modelId, p)}
saveModelCompatFlags={saveModelCompatFlags}
compatDisabled={compatSavingModelId === modelId}
onToggleHidden={onToggleHidden}
togglingHidden={togglingModelId === modelId}
onTestModel={onTestModel}
testStatus={modelTestStatus?.[modelId] || null}
testingModel={testingModelId === modelId}
/>
))}
</div>
{filteredModels.length === 0 && modelFilter && (
<p className="py-2 text-sm text-text-muted">
{providerText(t, "noModelsMatch", `No models match "${modelFilter}"`, {
@@ -5914,6 +5932,7 @@ function PassthroughModelRow({
modelId,
fullModel,
source,
isFree,
isHidden,
copied,
onCopy,
@@ -5933,37 +5952,43 @@ function PassthroughModelRow({
}: PassthroughModelRowProps) {
return (
<div
className={`flex gap-0 rounded-lg border border-border p-3 transition-opacity hover:bg-sidebar/50 ${
className={`flex min-w-0 flex-col gap-2 rounded-lg border border-border px-3.5 py-3 transition-opacity hover:bg-sidebar/50 ${
isHidden ? "opacity-50" : ""
}`}
>
<div className="flex min-w-0 flex-1 items-start gap-3">
<div className="flex min-w-0 items-center gap-2">
<span
className="material-symbols-outlined shrink-0 text-base text-text-muted"
style={{ color: isHidden ? "var(--color-text-muted)" : undefined }}
>
smart_toy
</span>
<div className="min-w-0 flex-1">
<p className="truncate text-sm font-medium">{modelId}</p>
<div className="mt-1 flex flex-wrap items-center gap-1">
<code className="rounded bg-sidebar px-1.5 py-0.5 font-mono text-xs text-text-muted">
{fullModel}
</code>
<ModelSourceBadge source={source} />
<button
onClick={() => onCopy(fullModel, `model-${modelId}`)}
className="rounded p-0.5 text-text-muted hover:bg-sidebar hover:text-primary"
title={t("copyModel")}
>
<span className="material-symbols-outlined text-sm">
{copied === `model-${modelId}` ? "check" : "content_copy"}
</span>
</button>
</div>
</div>
<code
className="min-w-0 truncate rounded bg-sidebar px-1.5 py-0.5 font-mono text-xs text-text-muted"
title={fullModel}
>
{fullModel}
</code>
</div>
<div className="flex shrink-0 items-center gap-1 self-start">
<div className="flex min-w-0 items-center justify-between gap-2">
<div className="flex min-w-0 flex-wrap items-center gap-1.5">
<ModelSourceBadge source={source} />
{isFree && (
<Badge variant="success" className="shrink-0 px-1.5 py-0 text-[10px]">
{providerText(t, "freeBadge", "Free")}
</Badge>
)}
</div>
<div className="flex shrink-0 items-center gap-1">
<button
onClick={() => onCopy(fullModel, `model-${modelId}`)}
className="rounded p-0.5 text-text-muted hover:bg-sidebar hover:text-primary"
title={t("copyModel")}
>
<span className="material-symbols-outlined text-sm">
{copied === `model-${modelId}` ? "check" : "content_copy"}
</span>
</button>
{onTestModel && (
<button
onClick={() => onTestModel(modelId, fullModel)}
@@ -6017,6 +6042,7 @@ function PassthroughModelRow({
saveModelCompatFlags(modelId, { compatByProtocol: { [protocol]: payload } })
}
showDeveloperToggle={showDeveloperToggle}
compact
disabled={compatDisabled}
/>
{onDeleteAlias && (
@@ -6028,6 +6054,7 @@ function PassthroughModelRow({
<span className="material-symbols-outlined text-sm">delete</span>
</button>
)}
</div>
</div>
</div>
);
@@ -6609,6 +6636,7 @@ function CompatibleModelsSection({
alias: string | null;
displayName: string;
source: string;
isFree: boolean;
isHidden: boolean;
}> = [];
const seenModelIds = new Set<string>();
@@ -6626,6 +6654,10 @@ function CompatibleModelsSection({
alias: aliasByModelId.get(model.id) || null,
displayName: model.name || model.id,
source,
isFree:
Boolean((model as any).free) ||
model.id.endsWith(":free") ||
/\bgr[aá]tis\b|\bfree\b/i.test(model.name || ""),
isHidden: isModelHidden(model.id),
});
seenModelIds.add(model.id);
@@ -6656,6 +6688,10 @@ function CompatibleModelsSection({
alias: alias as string,
displayName: alias as string,
source: customModel ? customModel.source || "custom" : "alias",
isFree:
modelId.endsWith(":free") ||
Boolean((customModel as any)?.free) ||
/\bgr[aá]tis\b|\bfree\b/i.test(customModel?.name || alias || ""),
isHidden: isModelHidden(modelId),
});
seenModelIds.add(modelId);
@@ -6846,33 +6882,42 @@ function CompatibleModelsSection({
selectAllDisabled={hiddenFilteredCount === 0 || bulkTogglePending}
deselectAllDisabled={visibleFilteredCount === 0 || bulkTogglePending}
/>
{filteredModels.map(({ modelId, alias, isHidden, source }) => (
<PassthroughModelRow
key={`${providerStorageAlias}:${modelId}`}
modelId={modelId}
fullModel={`${providerDisplayAlias}/${modelId}`}
source={source}
isHidden={isHidden}
copied={copied}
onCopy={onCopy}
onDeleteAlias={
source === "custom" || source === "manual"
? () => handleDeleteModel(modelId, alias)
: source === "alias" && alias
? () => onDeleteAlias(alias)
: undefined
}
t={t}
showDeveloperToggle={!isAnthropic}
effectiveModelNormalize={effectiveModelNormalize}
effectiveModelPreserveDeveloper={effectiveModelPreserveDeveloper}
getUpstreamHeadersRecord={(p) => getUpstreamHeadersRecord(modelId, p)}
saveModelCompatFlags={saveModelCompatFlags}
compatDisabled={compatSavingModelId === modelId}
onToggleHidden={onToggleHidden}
togglingHidden={togglingModelId === modelId}
/>
))}
<div className="grid grid-cols-1 gap-3 xl:grid-cols-2">
{filteredModels.map(({ modelId, alias, isHidden, source, isFree }) => {
const fullModel = `${providerDisplayAlias}/${modelId}`;
return (
<PassthroughModelRow
key={`${providerStorageAlias}:${modelId}`}
modelId={modelId}
fullModel={fullModel}
source={source}
isFree={isFree}
isHidden={isHidden}
copied={copied}
onCopy={onCopy}
onDeleteAlias={
source === "custom" || source === "manual"
? () => handleDeleteModel(modelId, alias)
: source === "alias" && alias
? () => onDeleteAlias(alias)
: undefined
}
t={t}
showDeveloperToggle={!isAnthropic}
effectiveModelNormalize={effectiveModelNormalize}
effectiveModelPreserveDeveloper={effectiveModelPreserveDeveloper}
getUpstreamHeadersRecord={(p) => getUpstreamHeadersRecord(modelId, p)}
saveModelCompatFlags={saveModelCompatFlags}
compatDisabled={compatSavingModelId === modelId}
onToggleHidden={onToggleHidden}
togglingHidden={togglingModelId === modelId}
onTestModel={onTestModel}
testStatus={modelTestStatus?.[modelId] || null}
testingModel={testingModelId === modelId}
/>
);
})}
</div>
{filteredModels.length === 0 && modelFilter && (
<p className="py-2 text-sm text-text-muted">
{providerText(t, "noModelsMatch", `No models match "${modelFilter}"`, {
@@ -7510,40 +7555,6 @@ function ConnectionRow({
</button>
</>
)}
{onToggleProxyEnabled && (
<>
<span className="text-text-muted/30 select-none">|</span>
<button
onClick={() => onToggleProxyEnabled(!proxyEnabled)}
className={`inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-xs font-medium transition-all cursor-pointer ${
proxyEnabled
? "bg-emerald-500/15 text-emerald-500 hover:bg-emerald-500/25"
: "bg-black/[0.03] dark:bg-white/[0.03] text-text-muted/50 hover:text-text-muted hover:bg-black/[0.06] dark:hover:bg-white/[0.06]"
}`}
title={proxyEnabled ? t("proxyEnabledTitle") : t("proxyDisabledTitle")}
>
<span className="material-symbols-outlined text-[13px]">vpn_lock</span>
{proxyEnabled ? t("proxyOn") : t("proxyOff")}
</button>
</>
)}
{onTogglePerKeyProxyEnabled && (
<>
<span className="text-text-muted/30 select-none">|</span>
<button
onClick={() => onTogglePerKeyProxyEnabled(!perKeyProxyEnabled)}
className={`inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-xs font-medium transition-all cursor-pointer ${
perKeyProxyEnabled
? "bg-violet-500/15 text-violet-500 hover:bg-violet-500/25"
: "bg-black/[0.03] dark:bg-white/[0.03] text-text-muted/50 hover:text-text-muted hover:bg-black/[0.06] dark:hover:bg-white/[0.06]"
}`}
title={perKeyProxyEnabled ? t("perKeyProxyEnabledTitle") : t("perKeyProxyDisabledTitle")}
>
<span className="material-symbols-outlined text-[13px]">key</span>
{perKeyProxyEnabled ? t("perKeyProxyOn") : t("perKeyProxyOff")}
</button>
</>
)}
{hasProxy &&
(() => {
const colorClass =

View File

@@ -38,6 +38,7 @@ export async function GET(request: Request) {
name: model.name || model.root || model.id,
type: model.type || "chat",
custom: model.custom === true,
...(model.free === true ? { free: true } : {}),
...(model.capabilities ? { capabilities: model.capabilities } : {}),
...(typeof model.context_length === "number"
? { context_length: model.context_length }

View File

@@ -46,19 +46,13 @@ export async function POST(_request: Request, { params }: { params: Promise<{ id
const provider = connection.provider;
// Codex multi-account family-revocation cascade guard.
// Rotating-refresh providers (Codex/OpenAI share one Auth0 client_id, etc.)
// mint a single-use refresh_token on every refresh. This endpoint is invoked
// per-connection by the dashboard (incl. an OLD cached frontend that bulk-
// refreshes every expiring connection on a page load); rotating several
// sibling accounts makes Auth0 revoke the whole token family
// (openai/codex#9648), killing every account but the last. Never proactively
// rotate a rotating provider here — the access_token is reused as-is and
// genuine expiry is handled by the reactive, serialized 401 path on the next
// real request. This was the last unguarded proactive-refresh entry point
// (refreshAndUpdateCredentials and the connection-test route are already
// guarded). Non-rotating providers keep refreshing on demand below.
if (rotationGroupFor(provider) !== null) {
// Codex/OpenAI multi-account family-revocation cascade guard.
// These two providers share the same Auth0 client_id and can revoke sibling
// accounts when several refresh_tokens are rotated proactively. Other
// serialized providers (for example Kiro) still support safe manual refresh;
// the serializer only prevents concurrent sibling refreshes.
const rotationGroup = rotationGroupFor(provider);
if (rotationGroup === "openai-auth0") {
return NextResponse.json({
success: true,
skipped: true,

View File

@@ -20,6 +20,7 @@ import { CODEX_NATIVE_UNPREFIXED_MODELS } from "@omniroute/open-sse/services/mod
import { resolveNestedComboTargets } from "@omniroute/open-sse/services/combo";
import { getAllSyncedAvailableModels, type SyncedAvailableModel } from "@/lib/db/models";
import { getCompatibleFallbackModels } from "@/lib/providers/managedAvailableModels";
import { getOpenRouterCatalog } from "@/lib/catalog/openrouterCatalog";
import { hasEligibleConnectionForModel } from "@/domain/connectionModelRules";
import {
INTERNAL_PROXY_ERROR,
@@ -143,6 +144,50 @@ function getVisionCapabilityFields(modelId: string) {
};
}
function qualifyOpenRouterModelId(modelId: string): string {
return modelId.startsWith("openrouter/") ? modelId : `openrouter/${modelId}`;
}
function normalizeOpenRouterModalities(value: unknown): string[] {
return Array.isArray(value)
? value.filter((entry): entry is string => typeof entry === "string" && entry.length > 0)
: [];
}
function getOpenRouterModelType(inputModalities: string[], outputModalities: string[]) {
if (outputModalities.includes("image")) return "image";
if (outputModalities.includes("audio")) return "audio";
if (outputModalities.includes("video")) return "video";
if (outputModalities.includes("embedding")) return "embedding";
return "chat";
}
function isZeroPrice(value: unknown) {
if (typeof value === "number") return value === 0;
if (typeof value !== "string") return false;
const parsed = Number(value);
return Number.isFinite(parsed) && parsed === 0;
}
function isOpenRouterFreeModel(model: {
id?: string;
pricing?: { prompt?: string; completion?: string };
}) {
if (typeof model.id === "string" && model.id.endsWith(":free")) return true;
return isZeroPrice(model.pricing?.prompt) && isZeroPrice(model.pricing?.completion);
}
function getOpenRouterDisplayName(model: {
id?: string;
name?: string;
pricing?: { prompt?: string; completion?: string };
}) {
const name = model.name || model.id || "OpenRouter model";
return isOpenRouterFreeModel(model) && !/\bgr[aá]tis\b/i.test(name)
? `${name} (Grátis)`
: name;
}
function extractBearer(headers: Headers): string | null {
const authHeader = headers.get("authorization") || headers.get("Authorization");
if (!authHeader?.trim().toLowerCase().startsWith("bearer ")) return null;
@@ -822,6 +867,72 @@ export async function getUnifiedModelsResponse(
console.error("[catalog] Error fetching synced provider models:", err);
}
if (
activeAliases.has("openrouter") &&
!blockedProviders.has("openrouter") &&
!providersWithSyncedModels.has("openrouter")
) {
try {
const openRouterCatalog = await getOpenRouterCatalog();
for (const openRouterModel of openRouterCatalog.data || []) {
if (!openRouterModel?.id || typeof openRouterModel.id !== "string") continue;
const qualifiedId = qualifyOpenRouterModelId(openRouterModel.id);
if (models.some((existingModel: any) => existingModel?.id === qualifiedId)) continue;
const inputModalities = normalizeOpenRouterModalities(
openRouterModel.architecture?.input_modalities
);
const outputModalities = normalizeOpenRouterModalities(
openRouterModel.architecture?.output_modalities
);
const modelType = getOpenRouterModelType(inputModalities, outputModalities);
const isFree = isOpenRouterFreeModel(openRouterModel);
const supportedParameters = Array.isArray(openRouterModel.supported_parameters)
? openRouterModel.supported_parameters
: [];
const capabilities: Record<string, boolean> = {};
if (inputModalities.includes("image")) capabilities.vision = true;
if (
supportedParameters.includes("reasoning") ||
supportedParameters.includes("include_reasoning")
) {
capabilities.reasoning = true;
}
if (supportedParameters.includes("tools")) capabilities.tool_calling = true;
if (
supportedParameters.includes("structured_outputs") ||
supportedParameters.includes("response_format")
) {
capabilities.structured_output = true;
}
models.push({
id: qualifiedId,
object: "model",
created: openRouterModel.created || timestamp,
owned_by: "openrouter",
permission: [],
root: openRouterModel.id,
parent: null,
name: getOpenRouterDisplayName(openRouterModel),
type: modelType,
...(isFree ? { free: true } : {}),
...(typeof openRouterModel.context_length === "number"
? { context_length: openRouterModel.context_length }
: {}),
...(typeof openRouterModel.top_provider?.max_completion_tokens === "number"
? { max_output_tokens: openRouterModel.top_provider.max_completion_tokens }
: {}),
...(inputModalities.length > 0 ? { input_modalities: inputModalities } : {}),
...(outputModalities.length > 0 ? { output_modalities: outputModalities } : {}),
...(Object.keys(capabilities).length > 0 ? { capabilities } : {}),
});
}
} catch (err) {
console.error("[catalog] Error loading OpenRouter catalog:", err);
}
}
// Helper: check if a provider is active (by provider id or alias)
const isProviderActive = (provider: string) => {
if (activeAliases.size === 0) return false; // No active connections = show nothing

View File

@@ -4381,6 +4381,11 @@
"apiKeyProviders": "Provedores por Chave de API",
"compatibleProviders": "Provedores Compatíveis por Chave de API",
"testAll": "Testar Todos",
"freeBadge": "Grátis",
"distributeProxies": "Distribuir proxies",
"distributing": "Distribuindo...",
"selectedCount": "{count, plural, one {# selecionada} other {# selecionadas}}",
"accountsCount": "{count, plural, one {# conta} other {# contas}}",
"testAllOAuth": "Testar todas as conexões OAuth",
"testAllFree": "Testar todas as conexões gratuitas",
"testAllApiKey": "Testar todas as conexões por chave de API",
@@ -4580,6 +4585,15 @@
"configured": "configurado",
"providerProxyConfigureHint": "Configurar proxy para todas as conexões deste provedor",
"providerProxy": "Proxy do Provedor",
"proxyConfiguredBySource": "Proxy ({source}): {host}",
"proxyOn": "Proxy ligado",
"proxyOff": "Proxy desligado",
"proxyEnabledTitle": "Proxy ativado para esta conta",
"proxyDisabledTitle": "Proxy desativado para esta conta",
"perKeyProxyOn": "Por chave",
"perKeyProxyOff": "Por conta",
"perKeyProxyEnabledTitle": "Distribuição de proxy por chave ativada para este provedor",
"perKeyProxyDisabledTitle": "Distribuição de proxy por conta ativada para este provedor",
"repairEnv": "Repair env",
"repairEnvWorking": "Repairing...",
"repairEnvHint": "Restore missing OAuth defaults into .env without overwriting existing values.",

View File

@@ -44,9 +44,12 @@ interface CatalogEntry {
};
architecture?: {
modality?: string;
input_modalities?: string[];
output_modalities?: string[];
tokenizer?: string;
instruct_type?: string;
};
supported_parameters?: string[];
created?: number;
}

View File

@@ -113,6 +113,9 @@ function addModelsSuffix(baseUrl: string) {
if (!normalized) return "";
const suffixes = ["/chat/completions", "/responses", "/chat", "/messages"];
if (normalized.endsWith("/models")) {
return normalized;
}
for (const suffix of suffixes) {
if (normalized.endsWith(suffix)) {
return `${normalized.slice(0, -suffix.length)}/models`;
@@ -4012,9 +4015,10 @@ export async function validateProviderApiKey({ provider, apiKey, providerSpecifi
const baseUrlRaw =
providerSpecificData?.baseUrl || "https://integrate.api.nvidia.com/v1/chat/completions";
const normalized = normalizeBaseUrl(baseUrlRaw);
const chatBase = normalized.replace(/\/models$/, "");
const chatUrl = normalized.endsWith("/chat/completions")
? normalized
: `${normalized}/chat/completions`;
: `${chatBase}/chat/completions`;
// #3116: probe a universally-available model rather than models[0]
// (z-ai/glm-5.1), which requires the "Public API Endpoints" account permission
// and can hang/be DEGRADED — making a *valid* key fail with "Upstream Error".
@@ -4170,6 +4174,30 @@ export async function validateProviderApiKey({ provider, apiKey, providerSpecifi
});
}
if (entry.format === "antigravity") {
const expiresAt =
providerSpecificData?.tokenExpiresAt ||
providerSpecificData?.expiresAt ||
providerSpecificData?.expiry_date ||
providerSpecificData?.expiryDate;
const expiryMs =
typeof expiresAt === "number"
? expiresAt
: typeof expiresAt === "string" && expiresAt.trim()
? Date.parse(expiresAt)
: Number.NaN;
if (Number.isFinite(expiryMs) && expiryMs > 0 && expiryMs < Date.now()) {
return {
valid: false,
error: "Antigravity OAuth token has expired. Re-import or refresh the CLI login.",
unsupported: false,
};
}
return { valid: true, error: null, unsupported: false };
}
return { valid: false, error: "Provider validation not supported", unsupported: true };
} catch (error: any) {
return toValidationErrorResult(error);

View File

@@ -32,13 +32,13 @@ test("manual refresh route imports rotationGroupFor", async () => {
);
});
test("manual refresh route skips proactive refresh for rotating providers BEFORE calling getAccessToken", async () => {
test("manual refresh route skips proactive refresh for the OpenAI Auth0 family BEFORE calling getAccessToken", async () => {
const src = await read();
const guardIdx = src.search(/rotationGroupFor\s*\(\s*[\w.]*provider[\w.]*\s*\)\s*!==\s*null/);
const guardIdx = src.search(/rotationGroup\s*===\s*["']openai-auth0["']/);
assert.ok(
guardIdx >= 0,
"refresh route must guard with `rotationGroupFor(provider) !== null` to skip rotating providers"
"refresh route must only skip proactive refresh for the OpenAI Auth0 family"
);
const getAccessTokenIdx = src.indexOf("getAccessToken(");
@@ -46,7 +46,7 @@ test("manual refresh route skips proactive refresh for rotating providers BEFORE
assert.ok(
guardIdx < getAccessTokenIdx,
"the rotating-provider guard must run BEFORE getAccessToken so the rotating refresh_token is never exercised"
"the OpenAI Auth0 guard must run BEFORE getAccessToken so the risky refresh_token is never exercised"
);
// The guard short-circuits with an early return (no token rotation).
@@ -54,6 +54,20 @@ test("manual refresh route skips proactive refresh for rotating providers BEFORE
assert.match(
guardBlock,
/return\b/,
"the rotating-provider guard must return early (defer to the reactive 401 path) instead of refreshing"
"the OpenAI Auth0 guard must return early (defer to the reactive 401 path) instead of refreshing"
);
});
test("manual refresh route does not skip Kiro just because it is serialized", async () => {
const src = await read();
assert.doesNotMatch(
src,
/rotationGroupFor\s*\(\s*[\w.]*provider[\w.]*\s*\)\s*!==\s*null/,
"a blanket rotation-group skip blocks Kiro manual refresh"
);
assert.match(
src,
/rotationGroup\s*===\s*["']openai-auth0["']/,
"only the OpenAI Auth0 family should be skipped by the manual refresh route"
);
});

View File

@@ -79,7 +79,7 @@ test("nvidia specialty validator returns Invalid API key on 401", async () => {
);
});
test("nvidia specialty validator skips /models probe entirely", async () => {
test("nvidia specialty validator accepts a successful chat probe", async () => {
const calls: string[] = [];
await withMockServer(
(req, res) => {
@@ -105,3 +105,37 @@ test("nvidia specialty validator skips /models probe entirely", async () => {
}
);
});
test("nvidia specialty validator falls back to stable chat validation model", async () => {
let payload: any = null;
const calls: string[] = [];
await withMockServer(
(req, res) => {
calls.push(String(req.url));
let body = "";
req.on("data", (chunk) => {
body += String(chunk);
});
req.on("end", () => {
if (String(req.url).endsWith("/chat/completions")) {
payload = JSON.parse(body || "{}");
}
res.writeHead(200, { "content-type": "application/json" });
res.end(JSON.stringify({}));
});
},
async (baseUrl) => {
const result = await validateProviderApiKey({
provider: "nvidia",
apiKey: "nv-key",
providerSpecificData: { baseUrl },
});
assert.equal(result.valid, true);
assert.ok(
calls.some((u) => u.endsWith("/chat/completions")),
`should fall back to /chat/completions, called: ${JSON.stringify(calls)}`
);
assert.equal(payload?.model, "meta/llama-3.1-8b-instruct");
}
);
});