Files
OmniRoute/open-sse/executors/uc/ws.ts
Diego Rodrigues de Sa e Souza 7802f6ea16 fix(uc): route UC error strings through sanitizeErrorMessage; allowlist the retired codex id (#12437)
Drains the two remaining Fast Quality Gates reds the #11513 (UC) merge left
on the tip:

- error-helper: ucTts.ts and uc/ws.ts built error payloads from raw
  err.message (Hard Rule #12) — now wrapped in sanitizeErrorMessage(),
  behavior otherwise identical (uc suites 51/51).
- model-lifecycle: the UC catalog registers the vendor-retired gpt-5.2-codex
  (bare id; only the prefixed openai/gpt-5.2-codex was allowlisted). Added to
  allowedRetiredInCatalog per its policy — forwarding globally would rewrite
  the just-approved provider's model. Tracking: Refs #12436.

file-size, the third red of this window, was already drained by #12434.
2026-09-02 05:12:11 -03:00

181 lines
6.2 KiB
TypeScript

/**
* UC (uncensored.com) PERSONA WebSocket driver.
*
* Opens one socket per turn (connect → send the persona frame → stream frames →
* close), mirroring the reference client and the muse-spark-web WS executor. Auth
* is 100% the `?token=` query param (a 60s Clerk JWT); the ONLY required
* handshake header is `Origin: https://uncensored.com` (the backend checks it —
* NO Cookie, NO Authorization on the upgrade).
*
* The driver is transport-only: it classifies frames via UcFrameParser and hands
* each event to an `onEvent` callback, so the executor can drive both a live
* OpenAI SSE stream and a buffered non-streaming response from the same path. The
* module-level constructor + `__setUcWebSocketForTesting` hook let tests inject a
* fake socket (same pattern as muse-spark-web).
*/
import WebSocket from "ws";
import { sanitizeErrorMessage } from "../../utils/error.ts";
import { UC_ORIGIN, UC_WS_HOST, UC_WS_TIMEOUT_MS } from "./constants.ts";
import { buildPersonaFrame, type UcHistoryEntry } from "./protocol.ts";
import { UcFrameParser, type UcEvent } from "./stream.ts";
let WebSocketCtor: typeof WebSocket = WebSocket;
/** Inject a fake WebSocket constructor for tests. Returns a restore fn. */
export function __setUcWebSocketForTesting(ctor: typeof WebSocket): () => void {
const previous = WebSocketCtor;
WebSocketCtor = ctor;
return () => {
WebSocketCtor = previous;
};
}
/** Build the persona WS URL: wss://.../ws/{uid}?token={jwt}&_t={epochms}. */
export function buildUcWsUrl(uid: string, jwt: string): string {
return `${UC_WS_HOST}/${encodeURIComponent(uid)}?token=${encodeURIComponent(jwt)}&_t=${Date.now()}`;
}
export interface UcTurnInput {
jwt: string;
uid: string;
model: string;
text: string;
history: UcHistoryEntry[];
/** Uploaded input-media blobs (images/docs) for the current turn. */
media?: Array<{ blobName: string; contentType: string }>;
timeoutMs?: number;
signal?: AbortSignal | null;
/** Called for each classified event (delta/reasoning/status/done/error). */
onEvent?: (evt: UcEvent) => void;
}
export interface UcTurnResult {
/** The final answer text (raw_text authoritative, else concatenated deltas). */
content: string;
/** Reasoning text accumulated from intermediary_message frames. */
reasoning: string;
/** Set when the turn failed (error frame, transport failure, or timeout). */
error?: string;
}
/**
* Drive one persona turn to completion. Never rejects — a transport/timeout/error
* failure resolves with `{ error }` set (and any partial content). The caller
* decides whether a partial is usable or should surface the error.
*/
export function runUcTurn(input: UcTurnInput): Promise<UcTurnResult> {
const timeoutMs = input.timeoutMs ?? UC_WS_TIMEOUT_MS;
const url = buildUcWsUrl(input.uid, input.jwt);
const parser = new UcFrameParser();
const reasoningParts: string[] = [];
return new Promise<UcTurnResult>((resolve) => {
let ws: WebSocket;
try {
ws = new WebSocketCtor(url, {
headers: { Origin: UC_ORIGIN },
// The persona frame + long answers can exceed the default 100MB cap only
// in pathological cases; leave the library default. permessage-deflate is
// negotiated by the server and handled by `ws` transparently.
});
} catch (err) {
resolve({
content: "",
reasoning: "",
error: `ws connect failed: ${sanitizeErrorMessage(err instanceof Error ? err.message : String(err))}`,
});
return;
}
let settled = false;
let errorText: string | undefined;
let timeout: ReturnType<typeof setTimeout> | null = null;
let abortHandler: (() => void) | null = null;
const finish = (result: UcTurnResult) => {
if (settled) return;
settled = true;
if (timeout) clearTimeout(timeout);
if (input.signal && abortHandler) input.signal.removeEventListener("abort", abortHandler);
try {
ws.close();
} catch {
/* ignore */
}
resolve(result);
};
const fail = (error: string) =>
finish({ content: parser.accumulated.trim(), reasoning: reasoningParts.join(""), error });
timeout = setTimeout(
() => fail(`UC persona WS timed out (readyState=${ws.readyState})`),
timeoutMs
);
abortHandler = () => fail("Request aborted");
input.signal?.addEventListener("abort", abortHandler, { once: true });
ws.onopen = () => {
try {
const frame = buildPersonaFrame({
model: input.model,
text: input.text,
history: input.history,
uid: input.uid,
media: input.media,
});
ws.send(JSON.stringify(frame));
} catch (err) {
fail(`ws send failed: ${sanitizeErrorMessage(err instanceof Error ? err.message : String(err))}`);
}
};
ws.onmessage = (event: WebSocket.MessageEvent) => {
let raw = "";
const data = event.data as unknown;
if (typeof data === "string") {
raw = data;
} else if (Buffer.isBuffer(data)) {
raw = data.toString("utf-8");
} else if (data instanceof ArrayBuffer) {
raw = new TextDecoder().decode(data);
} else if (ArrayBuffer.isView(data as ArrayBufferView)) {
raw = new TextDecoder().decode(data as ArrayBufferView);
}
if (!raw) return;
for (const evt of parser.feed(raw)) {
input.onEvent?.(evt);
if (evt.kind === "reasoning") {
reasoningParts.push(evt.text);
} else if (evt.kind === "error") {
errorText = evt.text;
} else if (evt.kind === "done") {
finish({ content: evt.text, reasoning: reasoningParts.join("") });
return;
}
}
if (parser.done) {
// Terminal error frame consumed by the parser.
finish({
content: parser.accumulated.trim(),
reasoning: reasoningParts.join(""),
error: errorText,
});
}
};
ws.onerror = () => fail("UC persona WebSocket connection error");
ws.onclose = () => {
if (settled) return;
// Closed without an explicit end_of_stream: use whatever we accumulated.
finish({
content: parser.finalText(),
reasoning: reasoningParts.join(""),
error: errorText,
});
};
});
}