/** * Socket.IO payloads are only statically typed at the call site — a * misbehaving or compromised gateway can send anything at runtime. These * guards protect the dereference sites that would otherwise throw (`.map` on * a non-array, `.toFixed` on a non-number) or render an object as a React * child. */ import type { HarnessAuthState, HarnessCatalog, HarnessCatalogEntry, HarnessModelAvailability, HarnessSelection, HarnessSummary, } from '@/lib/types'; export function asString(value: unknown, fallback = ''): string { return typeof value === 'string' ? value : fallback; } /** Like `asString`, but an empty string also falls back — used for guarded * contract-provided reason strings (e.g. a denial or failure message) where * an empty string is not a meaningful value to display in place of the * stable fallback copy. */ export function asNonEmptyString(value: unknown, fallback: string): string { return typeof value === 'string' && value.length > 0 ? value : fallback; } export function asFiniteNumber(value: unknown, fallback = 0): number { return typeof value === 'number' && Number.isFinite(value) ? value : fallback; } /** Like `asFiniteNumber`, but returns `null` on failure instead of a numeric * fallback — callers that must not fabricate a plausible-looking value (e.g. * `0 tokens` / `$0.0000` for genuinely unknown usage) use this to render an * honest "unavailable" label instead. */ export function asFiniteNumberOrNull(value: unknown): number | null { return typeof value === 'number' && Number.isFinite(value) ? value : null; } export function asStringArray(value: unknown): string[] { return Array.isArray(value) && value.every((item) => typeof item === 'string') ? value : []; } export function isRecord(value: unknown): value is Record { return typeof value === 'object' && value !== null; } /** * The HTTP harness/catalog/selection JSON bodies are as untrusted as the socket * payloads above — a misbehaving or compromised gateway can send anything. The * guards below normalize those bodies into the typed client shapes without ever * rendering a raw body, so a 404/422/malformed response can never inject an * object into React or a non-tuple into the selection state. */ /** Normalizes an untrusted `authState` to the closed set, defaulting to the * safest value (`unavailable`) for anything unrecognized. */ export function asHarnessAuthState(value: unknown): HarnessAuthState { return value === 'ready' || value === 'auth_required' || value === 'unavailable' ? value : 'unavailable'; } /** Normalizes an untrusted `availability` to the closed set, defaulting to * `unavailable` so a malformed row can never present as sendable. */ export function asHarnessAvailability(value: unknown): HarnessModelAvailability { return value === 'available' ? 'available' : 'unavailable'; } /** A tuple is valid only when all three ids are non-empty strings — a partial * or malformed selection is rejected (null) rather than half-adopted. */ export function asHarnessSelection(value: unknown): HarnessSelection | null { if (!isRecord(value)) return null; const harnessId = value.harnessId; const providerId = value.providerId; const modelId = value.modelId; if ( typeof harnessId !== 'string' || typeof providerId !== 'string' || typeof modelId !== 'string' || harnessId.length === 0 || providerId.length === 0 || modelId.length === 0 ) { return null; } return { harnessId, providerId, modelId }; } /** Normalizes an untrusted array into typed harness summaries, dropping any row * without a usable id. */ export function asHarnessSummaries(value: unknown): HarnessSummary[] { if (!Array.isArray(value)) return []; const out: HarnessSummary[] = []; for (const item of value) { if (!isRecord(item)) continue; const id = asString(item.id); if (id.length === 0) continue; out.push({ id, displayName: asNonEmptyString(item.displayName, id), capabilities: asStringArray(item.capabilities), }); } return out; } function asHarnessCatalogEntry(value: unknown): HarnessCatalogEntry | null { const selection = asHarnessSelection(value); if (selection === null || !isRecord(value)) return null; return { ...selection, displayName: asNonEmptyString(value.displayName, selection.modelId), reasoningCapability: value.reasoningCapability === true, inputTypes: asStringArray(value.inputTypes), authState: asHarnessAuthState(value.authState), availability: asHarnessAvailability(value.availability), }; } /** Normalizes an untrusted catalog body into the typed client catalog. The * caller supplies `harnessId` (from the request path) so the returned catalog * is scoped to the harness that was actually requested, never a body-echoed id. * Malformed model rows are dropped rather than invalidating the whole catalog. */ export function asHarnessCatalog(value: unknown, harnessId: string): HarnessCatalog { const record = isRecord(value) ? value : {}; const rawModels = Array.isArray(record.models) ? record.models : []; const models: HarnessCatalogEntry[] = []; for (const row of rawModels) { const entry = asHarnessCatalogEntry(row); if (entry !== null) models.push(entry); } return { harnessId, version: asString(record.version), fingerprint: asString(record.fingerprint), models, }; } /** The single point of truth for what counts as a valid conversation ID * anywhere a scoped server event may adopt one into state — a non-empty * string, nothing else. Every site that establishes or compares * `state.conversationId` against a raw socket payload must route through * this guard so a malformed first frame (null/object/number/empty string) * can never be adopted verbatim. */ export function asConversationId(value: unknown): string | null { return typeof value === 'string' && value.length > 0 ? value : null; }