fix(cliproxy): harden pinned routing follow-up

This commit is contained in:
Tam Nhu Tran committed 2026-04-10 01:16:31 -04:00
1 parent b6aa6fb0d4
commit 40c4816acf
13 files changed
+124 -50

No files matched your search

+1 -1
View File
@@ -20,7 +20,7 @@ export interface CatalogRoutingSnapshot {
export async function getCatalogRoutingSnapshot(): Promise<CatalogRoutingSnapshot> {
const snapshot: ResolvedCatalogSnapshot = await getResolvedCatalogSnapshot();
const modelsResponse = await fetchCliproxyModels();
const modelsResponse = snapshot.source === 'live' ? await fetchCliproxyModels() : null;
const routing = buildCliproxyRoutingHints(snapshot.catalogs, modelsResponse?.models ?? []);
return {
+33 -15
View File
@@ -3,6 +3,8 @@ import { buildManagementHeaders, buildProxyUrl, getProxyTarget } from './proxy-t
import { mapExternalProviderName } from './provider-capabilities';
import type { CLIProxyProvider } from './types';
const MANAGED_PREFIX_REQUEST_TIMEOUT_MS = 3000;
interface ManagementAuthFileRecord {
account_type?: string;
name: string;
@@ -25,11 +27,24 @@ function normalizeProvider(record: ManagementAuthFileRecord): CLIProxyProvider |
return providerName ? mapExternalProviderName(providerName) : null;
}
async function listAuthFiles(): Promise<ManagementAuthFileRecord[]> {
async function fetchManagementEndpoint(path: string, init: RequestInit = {}): Promise<Response> {
const target = getProxyTarget();
const response = await fetch(buildProxyUrl(target, '/v0/management/auth-files'), {
headers: buildManagementHeaders(target),
});
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), MANAGED_PREFIX_REQUEST_TIMEOUT_MS);
try {
return await fetch(buildProxyUrl(target, path), {
...init,
headers: buildManagementHeaders(target, init.headers as Record<string, string> | undefined),
signal: controller.signal,
});
} finally {
clearTimeout(timeoutId);
}
}
async function listAuthFiles(): Promise<ManagementAuthFileRecord[]> {
const response = await fetchManagementEndpoint('/v0/management/auth-files');
if (!response.ok) {
throw new Error(`auth file listing failed with status ${response.status}`);
@@ -40,12 +55,11 @@ async function listAuthFiles(): Promise<ManagementAuthFileRecord[]> {
}
async function patchAuthFilePrefix(name: string, prefix: string): Promise<void> {
const target = getProxyTarget();
const response = await fetch(buildProxyUrl(target, '/v0/management/auth-files/fields'), {
const response = await fetchManagementEndpoint('/v0/management/auth-files/fields', {
method: 'PATCH',
headers: buildManagementHeaders(target, {
headers: {
'Content-Type': 'application/json',
}),
},
body: JSON.stringify({ name, prefix }),
});
@@ -55,14 +69,9 @@ async function patchAuthFilePrefix(name: string, prefix: string): Promise<void>
}
async function readAuthFileMetadata(name: string): Promise<AuthFileMetadata> {
const target = getProxyTarget();
const url = buildProxyUrl(
target,
const response = await fetchManagementEndpoint(
`/v0/management/auth-files/download?name=${encodeURIComponent(name)}`
);
const response = await fetch(url, {
headers: buildManagementHeaders(target),
});
if (!response.ok) {
throw new Error(`auth file download failed for ${name} with status ${response.status}`);
@@ -89,8 +98,17 @@ async function readAuthFileMetadata(name: string): Promise<AuthFileMetadata> {
export async function ensureManagedModelPrefixes(
providers?: CLIProxyProvider[]
): Promise<ManagedPrefixSyncResult> {
const allowedProviders = new Set(
(providers ?? [])
.map((provider) => provider.trim())
.filter((provider) => getManagedModelPrefix(provider))
);
if (providers && allowedProviders.size === 0) {
return { checked: 0, updated: 0 };
}
const files = await listAuthFiles();
const allowedProviders = new Set((providers ?? []).map((provider) => provider.trim()));
let checked = 0;
let updated = 0;
+1 -1
View File
@@ -125,7 +125,7 @@ function getSelectableModelId(
const hint = routing?.models.find(
(entry) => entry.modelId.toLowerCase() === modelId.toLowerCase()
);
return hint?.pinnedAvailable ? hint.recommendedModelId : modelId;
return hint?.recommendedModelId ?? modelId;
}
function getBackendLabel(backend: CLIProxyBackend): string {
+3 -5
View File
@@ -162,9 +162,7 @@ export function buildCliproxyRoutingHints(
.map((candidate) => candidate.id)
.sort((left, right) => left.localeCompare(right));
const managedPinnedId = `${prefix}/${model.id}`;
const recommendedModelId = pinnedCandidates.includes(managedPinnedId)
? managedPinnedId
: (pinnedCandidates[0] ?? managedPinnedId);
const pinnedAvailable = pinnedCandidates.includes(managedPinnedId);
const mergedModel = mergedModelMap.get(normalize(model.id));
const effectiveProvider = mergedModel ? inferProvider(mergedModel) : null;
const effectiveDisplayName =
@@ -188,8 +186,8 @@ export function buildCliproxyRoutingHints(
modelName: model.name?.trim() || model.id,
prefix,
pinnedModelId: managedPinnedId,
recommendedModelId,
pinnedAvailable: pinnedCandidates.length > 0,
recommendedModelId: managedPinnedId,
pinnedAvailable,
unprefixedStatus,
effectiveProvider,
effectiveDisplayName,
@@ -1017,7 +1017,6 @@ router.get('/:provider/status', async (req: Request, res: Response): Promise<voi
return;
}
pendingManualAuthState.delete(state);
try {
await ensureManagedModelPrefixes([account.provider]);
} catch {
@@ -1033,6 +1032,7 @@ router.get('/:provider/status', async (req: Request, res: Response): Promise<voi
isDefault: account.isDefault,
},
});
pendingManualAuthState.delete(state);
return;
}
@@ -1167,7 +1167,11 @@ router.post('/:provider/submit-callback', async (req: Request, res: Response): P
}
if (parsed.state) {
pendingManualAuthState.delete(parsed.state);
try {
await ensureManagedModelPrefixes([account.provider]);
} catch {
// Keep manual callback success path non-fatal when prefix repair cannot run.
}
}
res.json({
@@ -1180,6 +1184,9 @@ router.post('/:provider/submit-callback', async (req: Request, res: Response): P
isDefault: account.isDefault,
},
});
if (parsed.state) {
pendingManualAuthState.delete(parsed.state);
}
return;
}