diff --git a/changelog.d/fixes/8864-uncloseai-noauth.md b/changelog.d/fixes/8864-uncloseai-noauth.md new file mode 100644 index 0000000000..8a38e6b836 --- /dev/null +++ b/changelog.d/fixes/8864-uncloseai-noauth.md @@ -0,0 +1 @@ +- fix(dashboard): treat UncloseAI as a no-auth provider so the connect form no longer forces a fake API key (#8864) diff --git a/src/shared/constants/providers/apikey/gateways.ts b/src/shared/constants/providers/apikey/gateways.ts index 606d6eb493..febe913909 100644 --- a/src/shared/constants/providers/apikey/gateways.ts +++ b/src/shared/constants/providers/apikey/gateways.ts @@ -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", diff --git a/src/shared/constants/providers/noauth.ts b/src/shared/constants/providers/noauth.ts index f7a0f1082a..42d0b3bfbf 100644 --- a/src/shared/constants/providers/noauth.ts +++ b/src/shared/constants/providers/noauth.ts @@ -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", diff --git a/tests/unit/providers-constants-split.test.ts b/tests/unit/providers-constants-split.test.ts index 9276da2206..3eabac24bb 100644 --- a/tests/unit/providers-constants-split.test.ts +++ b/tests/unit/providers-constants-split.test.ts @@ -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).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", () => { diff --git a/tests/unit/providers/uncloseai-noauth.test.ts b/tests/unit/providers/uncloseai-noauth.test.ts new file mode 100644 index 0000000000..f43aa302a4 --- /dev/null +++ b/tests/unit/providers/uncloseai-noauth.test.ts @@ -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" + ); +});