fix(dashboard): treat UncloseAI as a no-auth provider so the connect form no longer forces a fake API key (#8864)

This commit is contained in:
Markus Hartung
2026-08-21 19:54:05 -03:00
parent 3caa59107e
commit c74641d1ee
5 changed files with 48 additions and 20 deletions

View File

@@ -0,0 +1 @@
- fix(dashboard): treat UncloseAI as a no-auth provider so the connect form no longer forces a fake API key (#8864)

View File

@@ -639,20 +639,6 @@ export const APIKEY_PROVIDERS_GATEWAYS = {
text: "Dahl auto-generates tokens via https://inference.dahl.global/tokens. No signup needed. Rate limits apply. You can also add your own API key.",
},
},
uncloseai: {
id: "uncloseai",
alias: "unc",
name: "UncloseAI",
icon: "auto_awesome",
color: "#8B5CF6",
textIcon: "UN",
website: "https://uncloseai.com",
hasFree: true,
freeNote: "Free forever — no signup, no credit card. OpenAI-compatible endpoints.",
passthroughModels: true,
authHint:
"No auth required. API accepts any non-empty string as key for identification. If older built-in models return 404, use Available Models → Import from /models or Auto-Sync; verified live model: solidrust/Hermes-3-Llama-3.1-8B-AWQ.",
},
hackclub: {
id: "hackclub",
alias: "hc",

View File

@@ -175,6 +175,25 @@ export const NOAUTH_PROVIDERS = {
text: "ZCode runs locally through its native app-server. OmniRoute never receives or stores the Z.ai credential.",
},
},
uncloseai: {
id: "uncloseai",
alias: "unc",
name: "UncloseAI",
icon: "auto_awesome",
color: "#8B5CF6",
textIcon: "UN",
website: "https://uncloseai.com",
noAuth: true,
hasFree: true,
passthroughModels: true,
serviceKinds: ["llm"],
authHint:
"No auth required. API accepts any non-empty string as key for identification. If older built-in models return 404, use Available Models → Import from /models or Auto-Sync; verified live model: solidrust/Hermes-3-Llama-3.1-8B-AWQ.",
freeNote: "Free forever — no signup, no credit card. OpenAI-compatible endpoints.",
notice: {
text: "UncloseAI needs no API key. API accepts any non-empty string as key for identification. If older built-in models return 404, use Available Models → Import from /models or Auto-Sync.",
},
},
aihorde: {
id: "aihorde",
alias: "horde",

View File

@@ -24,7 +24,8 @@
// (base-reds round 3, #9985) are both included in that measurement; Cursor API (specialty-media,
// #10729) brings it to 229; Token Kiosk (gateways, #10722) — merged in the same
// merge-train batch — independently bumped the gateways family too, landing at 231; Freebuff
// (gateways, #10531) brings it to 232.
// (gateways, #10531) brings it to 232. #8864 moves uncloseai (gateways family) into
// NOAUTH_PROVIDERS, dropping the APIKEY_PROVIDERS count to 231.
import { test } from "node:test";
import assert from "node:assert/strict";
@@ -53,12 +54,12 @@ test("barrel still exports every catalog + key helpers", () => {
}
});
test("APIKEY_PROVIDERS merges the 6 family files into 232 entries (no loss / no dup)", async () => {
test("APIKEY_PROVIDERS merges the 6 family files into 231 entries (no loss / no dup)", async () => {
const keys = Object.keys((P as Record<string, object>).APIKEY_PROVIDERS);
assert.equal(keys.length, 232);
assert.equal(new Set(keys).size, 232, "duplicate keys after spread-merge");
assert.equal(keys.length, 231);
assert.equal(new Set(keys).size, 231, "duplicate keys after spread-merge");
// the merged object's entry-count equals the sum of the 6 semantic family files; families are a
// strict partition (every provider in exactly one), so the sum must be exactly 232.
// strict partition (every provider in exactly one), so the sum must be exactly 231.
const families: [string, string][] = [
["gateways", "APIKEY_PROVIDERS_GATEWAYS"],
["frontier-labs", "APIKEY_PROVIDERS_FRONTIER"],
@@ -78,7 +79,7 @@ test("APIKEY_PROVIDERS merges the 6 family files into 232 entries (no loss / no
seen.add(k);
}
}
assert.equal(famTotal, 232, "families must partition all 232 providers");
assert.equal(famTotal, 231, "families must partition all 231 providers");
});
test("AI_PROVIDERS Proxy aggregates all sections; lookups resolve", () => {

View File

@@ -0,0 +1,21 @@
import { test } from "node:test";
import assert from "node:assert/strict";
import { NOAUTH_PROVIDERS, providerAllowsOptionalApiKey } from "@/shared/constants/providers";
test("uncloseai should be treated as a no-auth provider", () => {
const isNoAuthRegistered = Object.prototype.hasOwnProperty.call(NOAUTH_PROVIDERS, "uncloseai");
const allowsOptionalKey = providerAllowsOptionalApiKey("uncloseai");
assert.equal(
isNoAuthRegistered || allowsOptionalKey,
true,
"uncloseai must be registered in NOAUTH_PROVIDERS or allow an optional API key " +
"so the dashboard doesn't force users to enter a key for a no-auth provider"
);
assert.equal(
isNoAuthRegistered,
true,
"uncloseai must be registered in NOAUTH_PROVIDERS (not just allow an optional key) " +
"so the dashboard renders the NoAuthProviderControls flow"
);
});