UNPKG

@juspay/neurolink

Version:

The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002

193 lines (192 loc) • 7.75 kB
import { XorModels } from "../constants/enums.js"; import { logger } from "../utils/logger.js"; import { redactUrlForError } from "../utils/logSanitize.js"; import { getProviderModel } from "../utils/providerConfig.js"; import { describeValidationErrors, isRecord, redactCredentials, SystemOneDecisionProvider, } from "./systemOneDecision.js"; const XOR_ROUTE = "/v1/systemone"; /** * XOR_BASE_URL is the origin of a deployment, but a `LITELLM_BASE_URL`-style * value ends in `/v1`, which the route already carries. Both spellings reduce * to the same base. */ function normalizeBaseURL(raw) { return raw.replace(/\/+$/, "").replace(/\/v1$/i, ""); } /** What a 500 says when the prefill was too long for the model to read. */ const CONTEXT_LENGTH_MESSAGE = /(context|prefill|prompt).{0,40}(length|limit|too long|exceed)|(too long|exceeds?).{0,40}(context|tokens)/i; /** * A base URL that cannot work is refused up front, and its text is never * repeated: `fetch` rejects a URL with userinfo and echoes it in the error, the * route is appended after a query string or fragment, and any of them can hold a * credential. */ function baseURLProblem(baseURL) { const fix = "Set XOR_BASE_URL or pass credentials.xor.baseURL to the origin, and give the key through XOR_API_KEY or credentials.xor.apiKey."; try { const url = new URL(baseURL); // `new URL()` accepts any scheme, so a host:port with no scheme parses as // one (`xor.internal:8080` has the scheme `xor.internal:`) and would reach // `fetch` as a network error instead of this message. if (url.protocol !== "https:" && url.protocol !== "http:") { return `The XOR base URL must start with https:// or http://. ${fix}`; } return url.username || url.password || url.search || url.hash ? `The XOR base URL must not carry credentials, a query string or a fragment. ${fix}` : undefined; } catch { return `The XOR base URL is not a valid absolute URL. ${fix}`; } } function xorErrorKind(status, message) { if (status === 401) { return "authentication"; } if (status === 413) { return "max_tokens_exceeded"; } if (status === 429) { return "rate_limit"; } if (status === 503) { return "overloaded"; } if (status >= 500) { return CONTEXT_LENGTH_MESSAGE.test(message) ? "max_tokens_exceeded" : "server"; } return "invalid_request"; } /** * Two envelopes reach the client. A LiteLLM proxy answers its own rejections * (a bad key, a team without the model, a budget) as * `{"error":{"message","type","code"}}`; XOR's own server answers as * `{"error":"<string>"}` — a shape that neither TypeSafe's nor Laya's parser * recognises, since both require `body.error` to be an object. FastAPI's * `{"detail":…}` is read too, in case something in front speaks it. */ function readXorErrorMessage(body) { if (!isRecord(body)) { return undefined; } const { error, detail } = body; if (isRecord(error) && typeof error.message === "string" && error.message.length > 0) { return error.message; } if (typeof error === "string" && error.length > 0) { return error; } if (Array.isArray(detail)) { return `Request failed validation — ${describeValidationErrors(detail)}`; } return typeof detail === "string" && detail.length > 0 ? detail : undefined; } /** * XOR Provider — the `decide` inference type only. * * XOR is Juspay's open-weights "System One" decision model, post-trained from * Qwen3.6-35B-A3B: the same typed `noul` / `choice` / `score` questions as * TypeSafe's Jev, on the same wire, in one batched pass. Unlike Jev and Laya it * also reads images and one video. The request loop, retries, auth circuit * breaker and answer parsing are shared with every decision provider in * {@link SystemOneDecisionProvider}. * * @see https://huggingface.co/juspay/xor */ export class XorProvider extends SystemOneDecisionProvider { apiKey; baseURL; constructor(modelName, sdk, _region, credentials) { super(modelName, "xor", sdk); this.apiKey = credentials?.apiKey?.trim() || (process.env.XOR_API_KEY?.trim() ?? ""); // No built-in endpoint: XOR is reached at a deployment or through a proxy // route to one, so the base URL comes only from config. `||`, not `??`, so // a blank one counts as unset. this.baseURL = normalizeBaseURL(credentials?.baseURL?.trim() || process.env.XOR_BASE_URL?.trim() || ""); // A base URL with `user:pass@` or a `?token=` is refused when a request is // made, but it is redacted here as well so a misconfigured value cannot leak // through this line either. logger.debug("XOR Provider initialized (decide only)", { modelName: this.modelName, baseURL: this.baseURL ? redactUrlForError(this.baseURL) : "(not set)", }); } getDefaultModel() { return getProviderModel("XOR_MODEL", XorModels.XOR_1_1); } vendorLabel() { return "XOR"; } vendorDisplayName() { return "XOR"; } decisionApiKey() { return this.apiKey; } missingKeyMessage() { return "XOR requires an API key. Set XOR_API_KEY or pass credentials.xor.apiKey."; } missingConfigMessage() { return this.baseURL ? baseURLProblem(this.baseURL) : "XOR requires a base URL. Set XOR_BASE_URL or pass credentials.xor.baseURL."; } decisionEndpoint() { return `${this.baseURL}${XOR_ROUTE}`; } decisionHeaders() { return { Authorization: `Bearer ${this.apiKey}`, "Content-Type": "application/json", }; } /** * `model` is always sent. A LiteLLM proxy checks team access against the * body's `model`, and refuses a request that names none. `images` is * omitted rather than sent empty, which the server rejects. */ buildDecisionBody(state, questions, model, media) { return { model, state, questions, ...(media && media.images.length > 0 ? { images: media.images } : {}), ...(media?.video ? { video: media.video } : {}), }; } /** * A 403 is deliberately NOT `authentication`: on a LiteLLM proxy it means the * team's allow-list lacks the model, which an admin can fix at any time, and * `authentication` would trip the per-instance breaker and keep XOR disabled * until restart even after access is granted. A 402 (a budget) is the same * kind of fixable gap. `DecisionErrorKind` has no billing kind, so both map * to `invalid_request`. */ parseDecisionError(status, payload, requestId) { const fallback = `XOR request failed with HTTP ${status}`; const message = redactCredentials(readXorErrorMessage(payload) ?? fallback, this.apiKey) || fallback; const kind = xorErrorKind(status, message); return { kind, message, status, requestId, retryable: kind === "rate_limit" || kind === "overloaded" || kind === "server", }; } /** * XOR's server sets `x-request-id` and `x-typesafe-request-id` to one value. * A proxy in front may replace or drop them, so its own call id is the last * resort. */ readRequestId(headers) { return (headers.get("x-request-id") ?? headers.get("x-typesafe-request-id") ?? headers.get("x-litellm-call-id") ?? undefined); } }