fix(combo): align custom provider ids across creation and auth lookup (#3058)

* fix(combo): align custom provider ids

* fix(combo): tighten alias resolution

---------

Co-authored-by: minisforum <no@mail.com>
This commit is contained in:
CitrusIce
2026-06-02 13:18:06 +08:00
committed by GitHub
parent fd26e601a2
commit 7b87d2f169
3 changed files with 70 additions and 4 deletions

View File

@@ -2512,7 +2512,19 @@ function ComboFormModal({ isOpen, combo, onClose, onSave, activeProviders, combo
};
const handleAddModel = (model) => {
const nextEntry = { model: model.value, weight: 0 };
const qualifiedModel = typeof model?.value === "string" ? model.value : "";
const parsedModel = parseQualifiedModel(qualifiedModel);
const resolvedProviderId =
resolveComboBuilderProviderId(model?.providerId, builderProviders) ||
resolveComboBuilderProviderId(parsedModel?.providerId, builderProviders) ||
(typeof model?.providerId === "string" && model.providerId.trim()) ||
parsedModel?.providerId ||
null;
const nextEntry = {
model: qualifiedModel,
...(resolvedProviderId ? { providerId: resolvedProviderId } : {}),
weight: 0,
};
if (hasExactModelStepDuplicate(models, nextEntry)) {
setBuilderError(
getI18nOrFallback(

View File

@@ -1,6 +1,7 @@
import { randomUUID, createHash } from "crypto";
import {
getProviderConnections,
getProviderNodes,
validateApiKey,
updateProviderConnection,
getSettings,
@@ -44,6 +45,7 @@ import {
import { looksLikeQuotaExhausted } from "@/shared/utils/classify429";
import { getCodexModelScope } from "@omniroute/open-sse/executors/codex.ts";
import {
getProviderById,
getProviderAlias,
resolveProviderId,
NOAUTH_PROVIDERS,
@@ -791,7 +793,7 @@ export { fisherYatesShuffle, getNextFromDeckSync as getNextFromDeck };
/**
* Resolve provider aliases (e.g., nvidia -> nvidia_nim) for DB lookup
*/
function getProviderSearchPool(provider: string): string[] {
async function getProviderSearchPool(provider: string): Promise<string[]> {
const canonicalProvider = resolveProviderId(provider);
const canonicalAlias = getProviderAlias(canonicalProvider);
@@ -802,7 +804,34 @@ function getProviderSearchPool(provider: string): string[] {
return ["nvidia_nim", "nvidia"];
}
return Array.from(new Set([provider, canonicalProvider, canonicalAlias].filter(Boolean)));
const searchPool = new Set([provider, canonicalProvider, canonicalAlias].filter(Boolean));
// Built-in providers already resolve through static ids/aliases. Only
// compatible/custom providers need provider_nodes expansion back to the
// generated internal connection ids.
if (getProviderById(canonicalProvider)) {
return Array.from(searchPool);
}
// Custom provider nodes are referenced by user-facing prefixes in combos
// (for example "78code/gpt-5.4"), but live credentials are stored under
// internal provider ids like openai-compatible-responses-<uuid>.
try {
const providerNodes = await getProviderNodes();
for (const node of Array.isArray(providerNodes) ? providerNodes : []) {
const nodeRecord = asRecord(node);
const nodePrefix = typeof nodeRecord.prefix === "string" ? nodeRecord.prefix.trim() : "";
const nodeId = typeof nodeRecord.id === "string" ? nodeRecord.id.trim() : "";
if (!nodePrefix || !nodeId) continue;
if (nodePrefix === provider || nodePrefix === canonicalProvider || nodePrefix === canonicalAlias) {
searchPool.add(nodeId);
}
}
} catch {
// Best-effort alias expansion only.
}
return Array.from(searchPool);
}
/**
@@ -864,7 +893,7 @@ export async function getProviderCredentials(
);
// Fix #922: Check for aliases (nvidia/nvidia_nim) to ensure credentials are found
const providersToSearch = getProviderSearchPool(provider);
const providersToSearch = await getProviderSearchPool(provider);
const connectionResults = await Promise.all(
providersToSearch.map((p) => getProviderConnections({ provider: p, isActive: true }))
);

View File

@@ -149,3 +149,28 @@ test("#2778 matching logic: node with prefix=flymux and id=UUID-id still matches
assert.ok(matchByPrefixOrId !== undefined, "Alias-based match must still work after the fix");
assert.strictEqual(matchByPrefixOrId?.id, FAKE_UUID_NODE_ID);
});
test("custom provider auth lookup search pool maps alias prefixes to internal provider ids", async () => {
const authSrc = fs.readFileSync(path.resolve(__dirname, "../../src/sse/services/auth.ts"), "utf8");
assert.match(
authSrc,
/async function getProviderSearchPool\(provider: string\): Promise<string\[]>/,
"getProviderSearchPool should be async so it can expand custom provider aliases via provider_nodes"
);
assert.match(
authSrc,
/getProviderNodes\(/,
"auth lookup should read provider_nodes to map custom prefixes like 78code/micu back to internal provider ids"
);
assert.match(
authSrc,
/nodePrefix === provider \|\| nodePrefix === canonicalProvider \|\| nodePrefix === canonicalAlias/,
"auth lookup should match provider node prefixes against the requested alias/canonical provider values"
);
assert.match(
authSrc,
/searchPool\.add\(nodeId\)/,
"auth lookup should add the matched custom provider node id into the credential search pool"
);
});