mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-16 11:52:26 +03:00
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.
126 lines
4.9 KiB
TypeScript
126 lines
4.9 KiB
TypeScript
/**
|
|
* UC (uncensored.com) connection credential resolution.
|
|
*
|
|
* UC's persona WebSocket authenticates with a short-lived Clerk `__session` JWT
|
|
* (60s) that the executor mints per-connect from a DURABLE credential set stored
|
|
* in the connection's `providerSpecificData`:
|
|
*
|
|
* • `clientCookie` — the Clerk `__client` cookie (a JWT with NO `exp`; the
|
|
* real long-lived credential, secured by a rotating_token
|
|
* that only changes on genuine security events).
|
|
* • `sid` — the Clerk session id (`sess_...`); the mint path is
|
|
* `POST /v1/client/sessions/{sid}/tokens`.
|
|
* • `uid` — the account UID (uuid v4); it is the WS URL path segment
|
|
* AND the frame's `user_identifier`, and equals the JWT
|
|
* `uid` claim (so it can be recovered from a minted token).
|
|
* • `cookies` — the full cookie jar (Cloudflare `__cf_bm`/`_cfuvid`,
|
|
* `__client_uat`, etc.) sent on the mint call. Persisting
|
|
* the whole jar lets the executor follow cookie rotation.
|
|
*
|
|
* These are minted by OmniRoute's own browserless email-code login (see
|
|
* ./emailLogin.ts), so the router is self-contained and never reads any external
|
|
* (Hermes) token file.
|
|
*/
|
|
|
|
type ProviderSpecificData = Record<string, unknown> | null | undefined;
|
|
|
|
export interface UcCredential {
|
|
/** Clerk `__client` durable cookie (JWT, no exp). */
|
|
clientCookie: string;
|
|
/** Clerk session id (`sess_...`). */
|
|
sid: string;
|
|
/** Account UID (uuid) — WS path + user_identifier + JWT `uid` claim. */
|
|
uid: string;
|
|
/** Full cookie jar to send on the Clerk mint call (name → value). */
|
|
cookies: Record<string, string>;
|
|
}
|
|
|
|
function firstString(...values: unknown[]): string | null {
|
|
for (const v of values) {
|
|
if (typeof v === "string") {
|
|
// Raw browser LocalStorage/cookie dumps sometimes wrap the value in quotes.
|
|
const trimmed = v.trim().replace(/^"|"$/g, "");
|
|
if (trimmed.length > 0) return trimmed;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/** Decode a Clerk JWT payload without verifying (base64url middle segment). */
|
|
function decodeJwtClaims(jwt: string): Record<string, unknown> | null {
|
|
try {
|
|
const seg = jwt.split(".")[1];
|
|
if (!seg) return null;
|
|
const b64 = seg.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - (seg.length % 4)) % 4);
|
|
return JSON.parse(Buffer.from(b64, "base64").toString("utf8")) as Record<string, unknown>;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/** The `uid` claim from a Clerk `__session` JWT (== WS user_identifier), or null. */
|
|
export function uidFromSessionJwt(jwt: string): string | null {
|
|
const claims = decodeJwtClaims(jwt);
|
|
const uid = claims?.uid;
|
|
return typeof uid === "string" && uid.length > 0 ? uid : null;
|
|
}
|
|
|
|
/** Epoch seconds of a Clerk JWT `exp`, or 0 when undecodable. */
|
|
export function sessionJwtExpiry(jwt: string): number {
|
|
const claims = decodeJwtClaims(jwt);
|
|
return typeof claims?.exp === "number" ? claims.exp : 0;
|
|
}
|
|
|
|
/**
|
|
* Normalize a stored cookie jar into a flat `{name: value}` map. Accepts either
|
|
* a raw CDP dump shape `{name: {value: "..."}}` (what the capture/login persists)
|
|
* or an already-flat `{name: "value"}` map. Non-string/garbage entries are skipped.
|
|
*/
|
|
export function normalizeCookieJar(raw: unknown): Record<string, string> {
|
|
const out: Record<string, string> = {};
|
|
if (!raw || typeof raw !== "object") return out;
|
|
for (const [name, val] of Object.entries(raw as Record<string, unknown>)) {
|
|
if (typeof val === "string") {
|
|
out[name] = val;
|
|
} else if (
|
|
val &&
|
|
typeof val === "object" &&
|
|
typeof (val as { value?: unknown }).value === "string"
|
|
) {
|
|
out[name] = (val as { value: string }).value;
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
/** Serialize a cookie jar into a `Cookie:` header value (`k=v; k=v`). */
|
|
export function cookieHeader(cookies: Record<string, string>): string {
|
|
return Object.entries(cookies)
|
|
.map(([k, v]) => `${k}=${v}`)
|
|
.join("; ");
|
|
}
|
|
|
|
/**
|
|
* Resolve the UC credential from a connection's providerSpecificData. Returns
|
|
* null when not fully configured (clientCookie + sid required; uid may be
|
|
* recovered from a minted token later, but we require it here for a clean
|
|
* WS URL). The `__client` cookie is folded into the jar if absent so the mint
|
|
* call always carries it.
|
|
*/
|
|
export function resolveUcCredential(psd: ProviderSpecificData): UcCredential | null {
|
|
const clientCookie = firstString(psd?.ucClientCookie, psd?.clientCookie, psd?.__client);
|
|
if (!clientCookie) return null;
|
|
|
|
const sid = firstString(psd?.ucSid, psd?.sid);
|
|
if (!sid) return null;
|
|
|
|
const cookies = normalizeCookieJar(psd?.ucCookies ?? psd?.cookies);
|
|
// Ensure the durable cookie is present in the jar sent to Clerk.
|
|
if (!cookies.__client) cookies.__client = clientCookie;
|
|
|
|
const uid = firstString(psd?.ucUid, psd?.uid);
|
|
if (!uid) return null;
|
|
|
|
return { clientCookie, sid, uid, cookies };
|
|
}
|