Files
OmniRoute/open-sse/executors/uc/clerkAuth.ts
Armin Anton” ∴ 530096a3be feat(providers): add UC (uncensored.com) — persona (un-metered) + direct (metered) (#11513)
Adds uncensored.com as two OpenAI-compatible providers mirroring UC's own surfaces: uc, the persona/subscription side over WebSocket with a durable Clerk credential minting a short-lived per-connect token (no API key, un-metered), as a full multimodal port — chat, tools, vision, doc-RAG, image, video, TTS; and uc-direct, the metered Developer API over REST with X-api-key. Same underlying models, two billing surfaces.

Reconciled on merge. 57 files conflicted; only seven carried UC content, the rest was drift from the older release line and took the tip's side.

- executors/index.ts: the tip has since refactored the executor map to lazy dynamic imports, so uc is registered in that shape. uc-direct needs no entry — it routes through the default OpenAI-compatible executor.
- imageGeneration.ts: the branch still carried the retired designerWeb import alongside ucImage; kept only the UC one.
- config/providers/index.ts, webSessionCredentials.ts and web-cookie.ts resolved additively against the MaxAI entries #11461 put on the tip an hour earlier.
- web-cookie.ts: the uc entry declared no serviceKinds, required since #11392, so provider validation would have thrown at load. Declared ["llm"]. uc-direct already declared it at the end of its own entry — an earlier pass of mine added a second one after id and TypeScript caught the duplicate (TS1117); the author's placement is what shipped.

Every count was measured against the merged tree rather than taken from the branch, and all three would have been wrong: reserved prefixes are 406, not 399; APIKEY_PROVIDERS is 237, not 234; providers are 355. PROVIDER_REFERENCE.md regenerated, the count updated across README/AGENTS.md/llm.txt and its 42 mirrors, package.json and 6 SVGs — every changed line in the protected surfaces is a digit substitution and nothing else, verified by masking digits and comparing the removed and added sets (90 lines each, identical). The executor-map golden snapshot went 134 -> 135.

The branch's file-size-baseline.json predates #12411's ratchet re-tightening and was discarded rather than merged; imageGeneration.ts (+12 for the uc-image format branch) was entered against the current baseline under a _rebaseline annotation, and no other cap moves.

Verified: typecheck:core clean, check:provider-consistency OK (271 REGISTRY entries, 355 canonical providers), check:docs-counts exit 0, check-file-size OK, check:cycles OK, 119/119 across the PR's test files, and 2/2 executor-map-golden.

Thanks @arminanton — two providers for two real billing surfaces, rather than one entry pretending to be both, is the right modelling.
2026-09-02 02:23:33 -03:00

184 lines
5.6 KiB
TypeScript

/**
* UC (uncensored.com) Clerk auth — mint the short-lived `__session` JWT that
* authenticates the persona WebSocket.
*
* UC uses Clerk. The socket URL carries a `?token=<jwt>` that is a 60-second
* Clerk session JWT (RS256, `iss: clerk.uncensored.com`, `exp - iat = 60`). It is
* minted from the durable `__client` cookie:
*
* POST https://clerk.uncensored.com/v1/client/sessions/{sid}/tokens
* ?_clerk_js_version=5.x
* Origin: https://uncensored.com
* Referer: https://uncensored.com/
* Cookie: <full jar incl. __client>
* Content-Type: application/x-www-form-urlencoded
* body: (empty)
* -> 200 { "object": "token", "jwt": "<RS256 60s JWT>" }
*
* The token is only needed at the WS handshake (the socket outlives the 60s
* expiry — the backend does not re-check mid-stream). We cache the minted JWT per
* session id and re-mint ~8s before expiry, exactly like the reference client.
*
* A mint call rotates only Cloudflare cookies (`__cf_bm`), never `__client`, so
* the durable credential is stable; we still capture any `Set-Cookie` rotation so
* the caller can persist a refreshed jar.
*/
import {
UC_CLERK_FAPI,
UC_CLERK_JS_VERSION,
UC_ORIGIN,
UC_TOKEN_REFRESH_SKEW_S,
} from "./constants.ts";
import { cookieHeader, sessionJwtExpiry } from "./credentials.ts";
/** A minted session token plus metadata. */
export interface UcSessionToken {
jwt: string;
/** epoch seconds of the JWT `exp` (0 when undecodable). */
expiresAt: number;
}
export interface UcMintInput {
sid: string;
/** Full cookie jar (must include `__client`). */
cookies: Record<string, string>;
signal?: AbortSignal | null;
/** Injectable fetch for tests (defaults to the ambient patched fetch). */
fetchImpl?: typeof fetch;
}
export interface UcMintResult {
ok: boolean;
token?: UcSessionToken;
/** Cookies observed rotating in the response `Set-Cookie` (name → value). */
rotatedCookies?: Record<string, string>;
status: number;
error?: string;
}
/** Cookie directive attributes we never treat as an actual cookie name/value. */
const COOKIE_ATTRS = new Set([
"expires",
"path",
"domain",
"samesite",
"secure",
"httponly",
"max-age",
]);
/** Parse rotated cookie name=value pairs out of a raw `Set-Cookie` header. */
export function parseSetCookie(setCookie: string): Record<string, string> {
const out: Record<string, string> = {};
if (!setCookie) return out;
for (const m of setCookie.matchAll(/(?:^|,\s*)([A-Za-z0-9_]+)=([^;,\s]+)/g)) {
const name = m[1];
const val = m[2];
if (COOKIE_ATTRS.has(name.toLowerCase())) continue;
out[name] = val;
}
return out;
}
/**
* Mint a fresh 60s Clerk session JWT from the durable cookie jar. Never throws;
* returns a structured result the caller branches on. A 401/403 means the durable
* login is invalid (the ~30-day window lapsed or the cookie was revoked) — the
* caller should surface a re-login prompt.
*/
export async function mintUcSessionToken(input: UcMintInput): Promise<UcMintResult> {
const doFetch = input.fetchImpl ?? fetch;
if (!input.sid || !input.cookies?.__client) {
return { ok: false, status: 0, error: "missing sid or __client cookie" };
}
const url = `${UC_CLERK_FAPI}/v1/client/sessions/${input.sid}/tokens?_clerk_js_version=${UC_CLERK_JS_VERSION}`;
const headers: Record<string, string> = {
Origin: UC_ORIGIN,
Referer: UC_ORIGIN + "/",
Cookie: cookieHeader(input.cookies),
"Content-Type": "application/x-www-form-urlencoded",
};
let res: Response;
try {
res = await doFetch(url, {
method: "POST",
headers,
body: "",
signal: input.signal ?? undefined,
});
} catch (err) {
return { ok: false, status: 0, error: err instanceof Error ? err.message : String(err) };
}
const rotatedCookies = parseSetCookie(res.headers.get("set-cookie") ?? "");
const raw = await res.text().catch(() => "");
if (res.status !== 200) {
return {
ok: false,
status: res.status,
error: raw.slice(0, 200) || `Clerk mint HTTP ${res.status}`,
rotatedCookies,
};
}
let jwt = "";
try {
const parsed = JSON.parse(raw) as { jwt?: unknown; token?: unknown };
if (typeof parsed?.jwt === "string") jwt = parsed.jwt;
else if (typeof parsed?.token === "string") jwt = parsed.token;
} catch {
return {
ok: false,
status: res.status,
error: "unparseable Clerk token response",
rotatedCookies,
};
}
if (!jwt) {
return {
ok: false,
status: res.status,
error: "Clerk token response had no jwt",
rotatedCookies,
};
}
return {
ok: true,
status: 200,
token: { jwt, expiresAt: sessionJwtExpiry(jwt) },
rotatedCookies,
};
}
/**
* A tiny per-session token cache. UC mints a 60s JWT per connect; caching it and
* re-minting ~8s early avoids a mint on every single turn while never handing out
* a token within the skew window of expiry. Keyed by Clerk session id.
*/
export class UcTokenCache {
private cache = new Map<string, UcSessionToken>();
/** Return a still-fresh cached token for `sid`, or null when a mint is needed. */
get(sid: string, now: () => number = Date.now): string | null {
const tok = this.cache.get(sid);
if (!tok) return null;
if (tok.expiresAt - now() / 1000 > UC_TOKEN_REFRESH_SKEW_S) return tok.jwt;
return null;
}
set(sid: string, token: UcSessionToken): void {
this.cache.set(sid, token);
}
clear(sid?: string): void {
if (sid) this.cache.delete(sid);
else this.cache.clear();
}
}
/** Process-wide token cache (mirrors the reference client's per-adapter cache). */
export const ucTokenCache = new UcTokenCache();