mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-14 06:17:27 +00:00
fix(cliproxy): harden pinned routing follow-up
This commit is contained in:
1 parent
b6aa6fb0d4
commit
40c4816acf
13 files changed
+124
-50
No files matched your search
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user