/** * ZPL Engine HTTP client. * Talks to engine.zeropointlogic.io (or custom URL). * All computation happens server-side — this MCP never sees the formula. */ export interface ComputeRequest { d: number; bias: number; samples?: number; } /** * `ain_status` and `status` are TWO DIFFERENT FIELDS with different value sets. * They have been mixed up repeatedly in docs — do not treat them as synonyms. * * ain_status — quality of the equilibrium, derived from `ain`: * CERTIFIED_NEUTRAL | HIGHLY_NEUTRAL | NEUTRAL | * MODERATE_BIAS | SIGNIFICANT_BIAS | HIGH_BIAS * * status — stability regime: * STABLE | ACTIVE | INHIBITED_HIGH | INHIBITED_LOW * (there is no plain "INHIBITED" value) */ export interface ComputeResponse { d: number; bias: number; p_output: number; /** Equilibrium score, 0.0–1.0 with 6 decimals. Never round to whole percent — see src/ain-format.ts. */ ain: number; /** Equilibrium quality — see the note above; NOT the same field as `status`. */ ain_status: string; deviation: number; /** Stability regime — see the note above; NOT the same field as `ain_status`. */ status: string; samples: number; tokens_used: number; compute_ms: number; } export interface SweepResult { bias: number; p_output: number; /** Equilibrium score, 0.0–1.0 with 6 decimals. */ ain: number; deviation: number; /** Stability regime (STABLE / ACTIVE / INHIBITED_HIGH / INHIBITED_LOW) — sweep steps carry no ain_status. */ status: string; } /** One operator family's verdict on a supplied matrix. */ export interface FamilyVerdict { family: number; bit: 0 | 1; /** * The fold reached an exact tie and the centre decided it. A tie means no * majority was found at all — weaker than a confident bit, and worth saying * out loud rather than presenting as a settled answer. */ tie_broken: boolean; } /** * Result of analysing one specific matrix. * * Carries no `ain` and no `p_output`, and not by omission: both describe how * output bits distribute across many sampled matrices, and over a single * matrix the proportion is 0 or 1 and says nothing about balance. */ export interface AnalyzeResponse { n: number; families: FamilyVerdict[]; ones: number; unanimous: boolean; /** * Cells set to 1 in the caller's own matrix, and the total. * * AUDIT 2026-07-31: the engine was swept over 3..=100. At every even * dimension the four family bits for an all-zeros matrix are identical to * those for an all-ones matrix - 49 of 49 even dimensions, none of the 49 * odd ones. Every paid ceiling except Pro's 25 is even: 16, 32, 48, 64, 100. * * Optional: an engine older than that sweep does not send them. Absent must * stay absent - an input_ones of 0 is an all-zeros matrix, a real answer. */ input_ones?: number; cells?: number; degenerate?: boolean; tokens_used: number; compute_ms?: number; } export interface SweepResponse { d: number; samples: number; results: SweepResult[]; total_tokens: number; compute_ms: number; } export interface PlanInfo { name: string; max_d: number; tokens_per_month: number; max_keys: number; price_usd: number; unlimited: boolean; } export interface HealthResponse { status: string; version: string; } export interface EngineError { error: string; code: number; } /** * Parse a non-OK fetch response into a clear, actionable error message. * * Engine returns JSON `{error, code}` for its own failures. But when the * request is intercepted by Cloudflare (Bot Fight Mode challenge, rate * limit, "under attack" mode, or origin offline) the body is HTML — the * generic JSON parse falls back to `res.statusText`, which leaves users * staring at "Engine error 403: Forbidden" with no actionable next step. * * v3.7.2: detect HTML/Cloudflare bodies explicitly and return a message * that tells the user what actually happened and how to fix it. * * Exported so unit tests can feed it synthetic Response objects without * hitting the network. */ export declare function parseEngineError(res: Response): Promise; export declare class ZPLEngineClient { private baseUrl; private apiKey; private maxRetries; constructor(apiKey: string, baseUrl?: string); private headers; /** * Retry with exponential backoff for transient failures (5xx, network). * * AUDIT 2026-08-02: this used to take a second argument, `timeoutMs`, and * never read it. Every call site passed one - 15000, 30000, 5000, 10000 - * and the body used `fn`, `maxRetries` and the backoff delay, nothing else. * * The deadlines that do exist live on each fetch, as AbortSignal.timeout. * Four of the five calls had one and were fine. `plans()` did not, and the * argument beside it read exactly like the thing it was missing. Measured * against a stub that accepts the connection and then says nothing: * health() gave up after 5.0s, plans() was still waiting at 45s. * * So the parameter is gone rather than implemented. Adding a second deadline * mechanism here would leave two places to read and two to keep in step with * the engine's ceilings; removing it means the signature stops claiming a * guarantee that the call sites are the ones actually making. */ private withRetry; compute(req: ComputeRequest): Promise; /** * Analyse a specific matrix — the engine sees the caller's data. * * `compute` does not send a matrix. It sends a dimension and a density, and * the engine generates fresh random matrices at that density and reports on * those. Two entirely different inputs of equal density therefore receive * the same answer, which is why domain tools built on `compute` cannot * distinguish a fair distribution from an abusive one. * * This sends the matrix itself and returns each operator family's verdict. */ analyze(matrix: number[][]): Promise; sweep(d: number, samples?: number): Promise; health(): Promise; plans(): Promise; }