mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-18 05:02:15 +03:00
Remove the Puter provider (id `puter`, alias `pu`) entirely, at the request of Puter's owner, Nariman Jelveh: - registry entry (open-sse/config/providers/registry/puter/) and PuterExecutor (open-sse/executors/puter.ts), with their registrations - API-key preset card (gateways.ts), provider icon and public SVG asset - 33 free-model catalog entries (pool `puter`) - authHint i18n key across all 43 UI locales - credential-requirement frozen-list entry and related comments - docs: ARCHITECTURE, CODEBASE_DOCUMENTATION, FREE_TIERS (removal note), PROVIDER_REFERENCE regenerated (337 providers), translated doc mirrors, llm.txt + its 42 i18n mirrors, README/AGENTS/package.json counts (338→337 providers, 144→145 migrations) and the 5 canonical SVGs - migration 152 cleans up stored puter connections/keys/custom models; historical usage records are preserved (same principle as migration 151) - regression guard: tests/unit/puter-provider-removed.test.ts; puter fixtures in shared tests swapped for neutral providers; translate-path golden snapshot regenerated Historical CHANGELOG mentions are intentionally preserved; the removal carries its own CHANGELOG entry. Co-authored-by: backryun <bakryun0718@proton.me>
120 lines
5.2 KiB
TypeScript
120 lines
5.2 KiB
TypeScript
/**
|
|
* Single answer to "does this provider need a credential?".
|
|
*
|
|
* Two different questions had been collapsed into the word "keyless":
|
|
*
|
|
* 1. **Is the free access quantifiable in tokens?** That is what
|
|
* `FreeModelBudget.freeType === "keyless"` means — it sits next to `oauth`
|
|
* in the docs as "not token-quantifiable", which is why those rows never
|
|
* reach the headline. It says nothing about credentials.
|
|
* 2. **Can the user call it with nothing configured?** That is this module.
|
|
*
|
|
* Reading (1) as (2) is not academic: probing the endpoints on 2026-07-20 showed
|
|
* blackbox, friendliai, iflytek and sparkdesk answering 401, and
|
|
* muse-spark-web 403, with no credential — yet all are `freeType: "keyless"`.
|
|
* A UI section built on (1) would invite users to call providers that reject
|
|
* them. So anything user-facing about API keys must come from here.
|
|
*
|
|
* The answer is derived from the two registries that describe real behaviour —
|
|
* `NOAUTH_PROVIDERS` (does the connect form ask for a key?) and `RegistryEntry`
|
|
* (what does the executor actually send?) — so there is no new list to keep in
|
|
* sync: registering a provider the usual way is enough.
|
|
*/
|
|
import { NOAUTH_PROVIDERS } from "../constants/providers/noauth.ts";
|
|
import { REGISTRY } from "@omniroute/open-sse/config/providerRegistry.ts";
|
|
|
|
export type CredentialRequirement =
|
|
/** Never needs a credential — the connect form does not even ask for one. */
|
|
| "none"
|
|
/** Works anonymously; a credential is accepted and usually raises the limits. */
|
|
| "optional"
|
|
/** No API key to paste, but the user still signs in (OAuth/device flow). */
|
|
| "oauth"
|
|
/** Unusable without a credential (API key, cookie or session token). */
|
|
| "required";
|
|
|
|
/** True when the user can call the provider with nothing configured. */
|
|
export function worksWithoutCredential(req: CredentialRequirement): boolean {
|
|
return req === "none" || req === "optional";
|
|
}
|
|
|
|
export function getCredentialRequirement(providerId: string): CredentialRequirement {
|
|
const entry = REGISTRY[providerId];
|
|
|
|
// Checked before `noAuth`: a literal anonymous token means the executor can
|
|
// call upstream with no user credential (Kilo's `anonymous`, AI Horde's
|
|
// `0000000000`) *and* that a real key is still honoured — AI Horde trades one
|
|
// for higher queue priority. That is "optional", not "none", even though the
|
|
// connect form hides the field.
|
|
if (entry?.anonymousApiKey) return "optional";
|
|
|
|
const noAuth = (NOAUTH_PROVIDERS as Record<string, { noAuth?: boolean }>)[providerId];
|
|
if (noAuth?.noAuth === true) return "none";
|
|
|
|
if (!entry) return "required";
|
|
if (entry.authType === "none") return "none";
|
|
if (entry.authType === "optional") return "optional";
|
|
if (entry.authType === "oauth") return "oauth";
|
|
return "required";
|
|
}
|
|
|
|
/**
|
|
* Every provider usable with nothing configured, sorted for stable output.
|
|
* This — not `freeType === "keyless"` — is what a "no API key required" list
|
|
* must be built from.
|
|
*/
|
|
export function listNoCredentialProviders(): string[] {
|
|
const ids = new Set([...Object.keys(NOAUTH_PROVIDERS), ...Object.keys(REGISTRY)]);
|
|
return [...ids].filter((id) => worksWithoutCredential(getCredentialRequirement(id))).sort();
|
|
}
|
|
|
|
/**
|
|
* Providers whose catalog rows are `freeType: "keyless"` while routing still
|
|
* needs a credential.
|
|
*
|
|
* This is NOT a bug list — the two fields answer different questions (see the
|
|
* module header). It exists so the mismatch stays visible and measured: each of
|
|
* these is a provider whose free access is real but reached through a web/session
|
|
* path, and each was confirmed by probing the endpoint. Kept frozen so a genuine
|
|
* new mistake still trips the gate.
|
|
*/
|
|
export const NOT_TOKEN_QUANTIFIABLE_BUT_CREDENTIALED: readonly string[] = [
|
|
"agy", // OAuth sign-in; nothing to paste, but still an account
|
|
"blackbox", // probed 2026-07-20 -> 401 "No api key passed in"
|
|
"friendliai", // probed -> 401 "no authorization info provided"
|
|
"iflytek", // probed -> 401 Unauthorized
|
|
"liquid", // probed -> 404: endpoint moved; config needs a separate audit
|
|
"muse-spark-web", // probed -> 403; authHeader is a session cookie, not a key
|
|
"qwen-web", // probed -> 200 but serves the WAF HTML page, not the API
|
|
"sparkdesk", // probed -> 401 Unauthorized
|
|
];
|
|
|
|
export interface KeylessConsistencyReport {
|
|
/** Catalog says keyless, routing needs a credential, and it is not recorded. */
|
|
unexpected: string[];
|
|
/** Recorded entries that now work without a credential — drop them. */
|
|
stale: string[];
|
|
}
|
|
|
|
/**
|
|
* Compare the catalog's `keyless` rows against real routing behaviour.
|
|
* Pure function: callers pass the catalog so tests and gates can reuse it.
|
|
*/
|
|
export function checkKeylessCatalogConsistency(
|
|
catalog: readonly { provider: string; freeType: string }[]
|
|
): KeylessConsistencyReport {
|
|
const labelledKeyless = [
|
|
...new Set(catalog.filter((m) => m.freeType === "keyless").map((m) => m.provider)),
|
|
].sort();
|
|
|
|
const credentialed = labelledKeyless.filter(
|
|
(id) => !worksWithoutCredential(getCredentialRequirement(id))
|
|
);
|
|
|
|
const recorded = new Set(NOT_TOKEN_QUANTIFIABLE_BUT_CREDENTIALED);
|
|
return {
|
|
unexpected: credentialed.filter((id) => !recorded.has(id)),
|
|
stale: [...recorded].filter((id) => !credentialed.includes(id)).sort(),
|
|
};
|
|
}
|