@juspay/neurolink 12.37.5 → 12.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,192 @@
1
+ import { XorModels } from "../constants/enums.js";
2
+ import { logger } from "../utils/logger.js";
3
+ import { redactUrlForError } from "../utils/logSanitize.js";
4
+ import { getProviderModel } from "../utils/providerConfig.js";
5
+ import { describeValidationErrors, isRecord, redactCredentials, SystemOneDecisionProvider, } from "./systemOneDecision.js";
6
+ const XOR_ROUTE = "/v1/systemone";
7
+ /**
8
+ * XOR_BASE_URL is the origin of a deployment, but a `LITELLM_BASE_URL`-style
9
+ * value ends in `/v1`, which the route already carries. Both spellings reduce
10
+ * to the same base.
11
+ */
12
+ function normalizeBaseURL(raw) {
13
+ return raw.replace(/\/+$/, "").replace(/\/v1$/i, "");
14
+ }
15
+ /** What a 500 says when the prefill was too long for the model to read. */
16
+ const CONTEXT_LENGTH_MESSAGE = /(context|prefill|prompt).{0,40}(length|limit|too long|exceed)|(too long|exceeds?).{0,40}(context|tokens)/i;
17
+ /**
18
+ * A base URL that cannot work is refused up front, and its text is never
19
+ * repeated: `fetch` rejects a URL with userinfo and echoes it in the error, the
20
+ * route is appended after a query string or fragment, and any of them can hold a
21
+ * credential.
22
+ */
23
+ function baseURLProblem(baseURL) {
24
+ 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.";
25
+ try {
26
+ const url = new URL(baseURL);
27
+ // `new URL()` accepts any scheme, so a host:port with no scheme parses as
28
+ // one (`xor.internal:8080` has the scheme `xor.internal:`) and would reach
29
+ // `fetch` as a network error instead of this message.
30
+ if (url.protocol !== "https:" && url.protocol !== "http:") {
31
+ return `The XOR base URL must start with https:// or http://. ${fix}`;
32
+ }
33
+ return url.username || url.password || url.search || url.hash
34
+ ? `The XOR base URL must not carry credentials, a query string or a fragment. ${fix}`
35
+ : undefined;
36
+ }
37
+ catch {
38
+ return `The XOR base URL is not a valid absolute URL. ${fix}`;
39
+ }
40
+ }
41
+ function xorErrorKind(status, message) {
42
+ if (status === 401) {
43
+ return "authentication";
44
+ }
45
+ if (status === 413) {
46
+ return "max_tokens_exceeded";
47
+ }
48
+ if (status === 429) {
49
+ return "rate_limit";
50
+ }
51
+ if (status === 503) {
52
+ return "overloaded";
53
+ }
54
+ if (status >= 500) {
55
+ return CONTEXT_LENGTH_MESSAGE.test(message)
56
+ ? "max_tokens_exceeded"
57
+ : "server";
58
+ }
59
+ return "invalid_request";
60
+ }
61
+ /**
62
+ * Two envelopes reach the client. A LiteLLM proxy answers its own rejections
63
+ * (a bad key, a team without the model, a budget) as
64
+ * `{"error":{"message","type","code"}}`; XOR's own server answers as
65
+ * `{"error":"<string>"}` — a shape that neither TypeSafe's nor Laya's parser
66
+ * recognises, since both require `body.error` to be an object. FastAPI's
67
+ * `{"detail":…}` is read too, in case something in front speaks it.
68
+ */
69
+ function readXorErrorMessage(body) {
70
+ if (!isRecord(body)) {
71
+ return undefined;
72
+ }
73
+ const { error, detail } = body;
74
+ if (isRecord(error) &&
75
+ typeof error.message === "string" &&
76
+ error.message.length > 0) {
77
+ return error.message;
78
+ }
79
+ if (typeof error === "string" && error.length > 0) {
80
+ return error;
81
+ }
82
+ if (Array.isArray(detail)) {
83
+ return `Request failed validation — ${describeValidationErrors(detail)}`;
84
+ }
85
+ return typeof detail === "string" && detail.length > 0 ? detail : undefined;
86
+ }
87
+ /**
88
+ * XOR Provider — the `decide` inference type only.
89
+ *
90
+ * XOR is Juspay's open-weights "System One" decision model, post-trained from
91
+ * Qwen3.6-35B-A3B: the same typed `noul` / `choice` / `score` questions as
92
+ * TypeSafe's Jev, on the same wire, in one batched pass. Unlike Jev and Laya it
93
+ * also reads images and one video. The request loop, retries, auth circuit
94
+ * breaker and answer parsing are shared with every decision provider in
95
+ * {@link SystemOneDecisionProvider}.
96
+ *
97
+ * @see https://huggingface.co/juspay/xor
98
+ */
99
+ export class XorProvider extends SystemOneDecisionProvider {
100
+ apiKey;
101
+ baseURL;
102
+ constructor(modelName, sdk, _region, credentials) {
103
+ super(modelName, "xor", sdk);
104
+ this.apiKey =
105
+ credentials?.apiKey?.trim() || (process.env.XOR_API_KEY?.trim() ?? "");
106
+ // No built-in endpoint: XOR is reached at a deployment or through a proxy
107
+ // route to one, so the base URL comes only from config. `||`, not `??`, so
108
+ // a blank one counts as unset.
109
+ this.baseURL = normalizeBaseURL(credentials?.baseURL?.trim() || process.env.XOR_BASE_URL?.trim() || "");
110
+ // A base URL with `user:pass@` or a `?token=` is refused when a request is
111
+ // made, but it is redacted here as well so a misconfigured value cannot leak
112
+ // through this line either.
113
+ logger.debug("XOR Provider initialized (decide only)", {
114
+ modelName: this.modelName,
115
+ baseURL: this.baseURL ? redactUrlForError(this.baseURL) : "(not set)",
116
+ });
117
+ }
118
+ getDefaultModel() {
119
+ return getProviderModel("XOR_MODEL", XorModels.XOR_1_1);
120
+ }
121
+ vendorLabel() {
122
+ return "XOR";
123
+ }
124
+ vendorDisplayName() {
125
+ return "XOR";
126
+ }
127
+ decisionApiKey() {
128
+ return this.apiKey;
129
+ }
130
+ missingKeyMessage() {
131
+ return "XOR requires an API key. Set XOR_API_KEY or pass credentials.xor.apiKey.";
132
+ }
133
+ missingConfigMessage() {
134
+ return this.baseURL
135
+ ? baseURLProblem(this.baseURL)
136
+ : "XOR requires a base URL. Set XOR_BASE_URL or pass credentials.xor.baseURL.";
137
+ }
138
+ decisionEndpoint() {
139
+ return `${this.baseURL}${XOR_ROUTE}`;
140
+ }
141
+ decisionHeaders() {
142
+ return {
143
+ Authorization: `Bearer ${this.apiKey}`,
144
+ "Content-Type": "application/json",
145
+ };
146
+ }
147
+ /**
148
+ * `model` is always sent. A LiteLLM proxy checks team access against the
149
+ * body's `model`, and refuses a request that names none. `images` is
150
+ * omitted rather than sent empty, which the server rejects.
151
+ */
152
+ buildDecisionBody(state, questions, model, media) {
153
+ return {
154
+ model,
155
+ state,
156
+ questions,
157
+ ...(media && media.images.length > 0 ? { images: media.images } : {}),
158
+ ...(media?.video ? { video: media.video } : {}),
159
+ };
160
+ }
161
+ /**
162
+ * A 403 is deliberately NOT `authentication`: on a LiteLLM proxy it means the
163
+ * team's allow-list lacks the model, which an admin can fix at any time, and
164
+ * `authentication` would trip the per-instance breaker and keep XOR disabled
165
+ * until restart even after access is granted. A 402 (a budget) is the same
166
+ * kind of fixable gap. `DecisionErrorKind` has no billing kind, so both map
167
+ * to `invalid_request`.
168
+ */
169
+ parseDecisionError(status, payload, requestId) {
170
+ const fallback = `XOR request failed with HTTP ${status}`;
171
+ const message = redactCredentials(readXorErrorMessage(payload) ?? fallback, this.apiKey) || fallback;
172
+ const kind = xorErrorKind(status, message);
173
+ return {
174
+ kind,
175
+ message,
176
+ status,
177
+ requestId,
178
+ retryable: kind === "rate_limit" || kind === "overloaded" || kind === "server",
179
+ };
180
+ }
181
+ /**
182
+ * XOR's server sets `x-request-id` and `x-typesafe-request-id` to one value.
183
+ * A proxy in front may replace or drop them, so its own call id is the last
184
+ * resort.
185
+ */
186
+ readRequestId(headers) {
187
+ return (headers.get("x-request-id") ??
188
+ headers.get("x-typesafe-request-id") ??
189
+ headers.get("x-litellm-call-id") ??
190
+ undefined);
191
+ }
192
+ }
@@ -25,8 +25,9 @@ export type ClassifierDifficulty = "trivial" | "simple" | "moderate" | "hard" |
25
25
  * - `jev` — TypeSafe's System One model; one ~400ms round trip that returns a
26
26
  * *calibrated* confidence rather than a self-reported one.
27
27
  * - `auto` — `jev` when a decision provider is configured, in the environment
28
- * or in SDK credentials (`TYPESAFE_API_KEY`, or `LAYA_API_KEY` with
29
- * `LAYA_BASE_URL`), otherwise `heuristic`.
28
+ * or in SDK credentials (`TYPESAFE_API_KEY`, `LAYA_API_KEY` with
29
+ * `LAYA_BASE_URL`, or `XOR_API_KEY` with `XOR_BASE_URL`), otherwise
30
+ * `heuristic`.
30
31
  */
31
32
  export type ClassifierStrategyKind = "heuristic" | "llm" | "jev" | "auto";
32
33
  /**
@@ -186,8 +187,8 @@ export type ClassifierRouterConfig = {
186
187
  /**
187
188
  * Classification strategy. Default: "auto" — which resolves to "jev" when a
188
189
  * decision provider is configured, in the environment or in SDK credentials
189
- * (`TYPESAFE_API_KEY`, or `LAYA_API_KEY` with `LAYA_BASE_URL`) and
190
- * "heuristic" otherwise, so configuring one
190
+ * (`TYPESAFE_API_KEY`, `LAYA_API_KEY` with `LAYA_BASE_URL`, or
191
+ * `XOR_API_KEY` with `XOR_BASE_URL`) and "heuristic" otherwise, so configuring one
191
192
  * upgrades routing without any code change. Behaviour for callers with no
192
193
  * key is unchanged.
193
194
  */
@@ -1310,6 +1310,10 @@ export type CliDecideArgs = {
1310
1310
  stateFile?: string;
1311
1311
  questions?: string;
1312
1312
  questionsFile?: string;
1313
+ /** Image files (or data: URLs) for a provider that reads images. */
1314
+ image?: string[];
1315
+ /** One video file (or data: URL) for a provider that reads video. */
1316
+ video?: string;
1313
1317
  provider?: string;
1314
1318
  model?: string;
1315
1319
  timeout?: number;
@@ -1722,8 +1726,8 @@ export type CliClassifierRouterFlags = {
1722
1726
  /**
1723
1727
  * Strategy: "auto" (default), "heuristic", "llm" or "jev"
1724
1728
  * (--classifier-strategy). "auto" resolves to "jev" when a decision
1725
- * provider is configured (TYPESAFE_API_KEY, or LAYA_API_KEY with
1726
- * LAYA_BASE_URL) and "heuristic" otherwise.
1729
+ * provider is configured (TYPESAFE_API_KEY, LAYA_API_KEY with
1730
+ * LAYA_BASE_URL, or XOR_API_KEY with XOR_BASE_URL) and "heuristic" otherwise.
1727
1731
  */
1728
1732
  classifierStrategy?: string;
1729
1733
  /**
@@ -95,9 +95,19 @@ export type DecisionUsage = {
95
95
  inputTokens: number;
96
96
  outputTokens: number;
97
97
  };
98
+ /** An image or video as the caller supplies it: raw bytes, a file path, or a data URL. */
99
+ export type DecisionMediaSource = Buffer | string;
98
100
  export type DecisionRequest = {
99
101
  state: DecisionState;
100
102
  questions: DecisionQuestionMap;
103
+ /**
104
+ * Images the model reads alongside `state`. Each is a Buffer, a local file
105
+ * path or a `data:image/…;base64,` URL; an http(s) URL is refused. Only a
106
+ * provider whose descriptor declares `decisionLimits.media` accepts them.
107
+ */
108
+ images?: readonly DecisionMediaSource[];
109
+ /** One video, in the same forms. Sending images as well is allowed. */
110
+ video?: DecisionMediaSource;
101
111
  /** Overrides the provider's configured model for this call only. */
102
112
  model?: string;
103
113
  signal?: AbortSignal;
@@ -116,6 +126,8 @@ export type DecisionResult = {
116
126
  latencyMs: number;
117
127
  /** Server-side time behind the edge; isolates model time from network. */
118
128
  upstreamMs?: number;
129
+ /** Encoded size of the images and video sent. Absent for a text-only request. */
130
+ mediaBytes?: number;
119
131
  };
120
132
  /**
121
133
  * Input a decision provider can actually read, declared on its descriptor.
@@ -132,7 +144,8 @@ export type DecisionLimits = {
132
144
  * name — so it should be the tightest window the provider has.
133
145
  */
134
146
  maxStateTokens: number;
135
- maxQuestions: number;
147
+ /** Absent when the provider has no cap on questions per request. */
148
+ maxQuestions?: number;
136
149
  /**
137
150
  * Tokens charged per non-ASCII character. The default estimate assumes ~4
138
151
  * characters per token, which holds for English and is several times too
@@ -145,6 +158,52 @@ export type DecisionLimits = {
145
158
  maxStateTokens: number;
146
159
  nonAsciiTokensPerChar?: number;
147
160
  }>>;
161
+ /** What the provider accepts besides text. Absent means text only. */
162
+ media?: DecisionMediaLimits;
163
+ };
164
+ export type DecisionMediaLimits = {
165
+ /** Most images one request may carry. */
166
+ maxImages: number;
167
+ /** Whether a video is accepted (at most one). */
168
+ video: boolean;
169
+ /** Ceiling on the encoded request body, in bytes. */
170
+ maxRequestBytes: number;
171
+ };
172
+ /** Media after preparation: everything is a `data:` URL, ready for the wire. */
173
+ export type DecisionPreparedMedia = {
174
+ images: string[];
175
+ video?: string;
176
+ /** Encoded size of every URL above. */
177
+ bytes: number;
178
+ };
179
+ /**
180
+ * Discriminated by a string literal, not a boolean: the package is also
181
+ * compiled without strictNullChecks, where a boolean discriminant does not
182
+ * narrow.
183
+ */
184
+ export type DecisionMediaResult = {
185
+ status: "prepared";
186
+ media: DecisionPreparedMedia | undefined;
187
+ } | {
188
+ status: "refused";
189
+ message: string;
190
+ };
191
+ export type DecisionMediaKind = "image" | "video";
192
+ /** One image or video turned into a data URL, or the reason it could not be. Same literal-discriminant rule as above. */
193
+ export type DecisionMediaConversion = {
194
+ status: "ok";
195
+ dataUrl: string;
196
+ } | {
197
+ status: "error";
198
+ message: string;
199
+ };
200
+ /** A media file read from disk, or the reason it could not be. */
201
+ export type DecisionMediaFileRead = {
202
+ status: "ok";
203
+ buffer: Buffer;
204
+ } | {
205
+ status: "error";
206
+ message: string;
148
207
  };
149
208
  /**
150
209
  * Why a decision call failed, normalised across vendors. TypeSafe alone
@@ -322,6 +322,16 @@ export type NeurolinkCredentials = {
322
322
  apiKey?: string;
323
323
  baseURL?: string;
324
324
  };
325
+ /**
326
+ * XOR (Juspay, open weights) — the `decide` inference type. There is no
327
+ * built-in endpoint: `baseURL` (or XOR_BASE_URL) is required, pointing at the
328
+ * origin of an XOR deployment or a proxy route to one; requests go to
329
+ * `<baseURL>/v1/systemone`.
330
+ */
331
+ xor?: {
332
+ apiKey?: string;
333
+ baseURL?: string;
334
+ };
325
335
  };
326
336
  /**
327
337
  * Voyage AI /embeddings response shape.
@@ -0,0 +1,9 @@
1
+ import type { DecisionMediaLimits, DecisionMediaResult, DecisionMediaSource } from "../types/index.js";
2
+ /**
3
+ * Turn the caller's images and video into `data:` URLs, or say why not.
4
+ * Runs before any request, so every refusal costs nothing and none is retried.
5
+ */
6
+ export declare function prepareDecisionMedia(request: {
7
+ images?: readonly DecisionMediaSource[];
8
+ video?: DecisionMediaSource;
9
+ }, limits: DecisionMediaLimits | undefined, vendor: string, alternatives: readonly string[]): Promise<DecisionMediaResult>;
@@ -0,0 +1,160 @@
1
+ import { promises as fs } from "node:fs";
2
+ import { sniffImageMimeType } from "./imageDetection.js";
3
+ import { detectIsoBmffImageMimeType, hasFtypBoxSignature } from "./isoBmff.js";
4
+ const REMOTE_URL = /^https?:\/\//i;
5
+ const URL_SCHEME = /^[a-z][a-z0-9+.-]*:\/\//i;
6
+ const MAX_ECHOED_PATH_CHARS = 200;
7
+ const DATA_URL = {
8
+ image: /^data:image\/[a-z0-9.+-]+;base64,/i,
9
+ video: /^data:video\/[a-z0-9.+-]+;base64,/i,
10
+ };
11
+ const WEBM_HEADER = [0x1a, 0x45, 0xdf, 0xa3];
12
+ const NOT_RECOGNISED = {
13
+ image: "is not a recognised image format",
14
+ video: "is not a recognised video format (MP4, MOV or WebM)",
15
+ };
16
+ /**
17
+ * MP4 and MOV share the ISO-BMFF box. An AVIF or HEIC image shares it too, and
18
+ * is not a video.
19
+ */
20
+ function sniffVideoMimeType(buffer) {
21
+ if (hasFtypBoxSignature(buffer) &&
22
+ detectIsoBmffImageMimeType(buffer) === null) {
23
+ return "video/mp4";
24
+ }
25
+ return WEBM_HEADER.every((byte, i) => buffer[i] === byte)
26
+ ? "video/webm"
27
+ : null;
28
+ }
29
+ function encode(buffer, kind, label) {
30
+ const mime = kind === "image" ? sniffImageMimeType(buffer) : sniffVideoMimeType(buffer);
31
+ return mime === null
32
+ ? { status: "error", message: `${label} ${NOT_RECOGNISED[kind]}.` }
33
+ : {
34
+ status: "ok",
35
+ dataUrl: `data:${mime};base64,${buffer.toString("base64")}`,
36
+ };
37
+ }
38
+ /**
39
+ * A path can be echoed in an error. A stray base64 blob passed as a path cannot,
40
+ * so past a short length only its size is shown.
41
+ */
42
+ function describePath(path) {
43
+ return path.length <= MAX_ECHOED_PATH_CHARS
44
+ ? `(${path})`
45
+ : `(a ${path.length}-character string)`;
46
+ }
47
+ /** A file over the limit is refused from `stat`, before it is read into memory. */
48
+ async function readMediaFile(path, label, maxBytes) {
49
+ try {
50
+ const stats = await fs.stat(path);
51
+ if (!stats.isFile()) {
52
+ return {
53
+ status: "error",
54
+ message: `${label} ${describePath(path)} is not a file.`,
55
+ };
56
+ }
57
+ if (stats.size > maxBytes) {
58
+ return {
59
+ status: "error",
60
+ message: `${label} ${describePath(path)} is ${stats.size} bytes; a request may carry at most ${maxBytes}.`,
61
+ };
62
+ }
63
+ return { status: "ok", buffer: await fs.readFile(path) };
64
+ }
65
+ catch (error) {
66
+ const reason = error instanceof Error && "code" in error
67
+ ? String(error.code)
68
+ : "unreadable";
69
+ return {
70
+ status: "error",
71
+ message: `Could not read ${label} ${describePath(path)}: ${reason}.`,
72
+ };
73
+ }
74
+ }
75
+ /**
76
+ * Nothing here echoes a `data:` URL or a remote URL: either can carry a
77
+ * credential or megabytes of base64, and neither belongs in an error message.
78
+ */
79
+ async function toDataUrl(source, kind, label, maxBytes) {
80
+ if (typeof source !== "string") {
81
+ return source.length === 0
82
+ ? { status: "error", message: `${label} is an empty Buffer.` }
83
+ : encode(source, kind, label);
84
+ }
85
+ const value = source.trim();
86
+ if (value === "") {
87
+ return { status: "error", message: `${label} is an empty string.` };
88
+ }
89
+ if (REMOTE_URL.test(value)) {
90
+ return {
91
+ status: "error",
92
+ message: `${label} is a remote URL. Media is not fetched for you; pass a Buffer, a file path or a data: URL.`,
93
+ };
94
+ }
95
+ if (/^data:/i.test(value)) {
96
+ return DATA_URL[kind].test(value)
97
+ ? { status: "ok", dataUrl: value }
98
+ : {
99
+ status: "error",
100
+ message: `${label} is a data: URL that is not base64 ${kind} content.`,
101
+ };
102
+ }
103
+ if (URL_SCHEME.test(value)) {
104
+ return {
105
+ status: "error",
106
+ message: `${label} uses an unsupported URL scheme. Pass a Buffer, a file path or a data: URL.`,
107
+ };
108
+ }
109
+ const file = await readMediaFile(value, label, maxBytes);
110
+ return file.status === "ok" ? encode(file.buffer, kind, label) : file;
111
+ }
112
+ /**
113
+ * Turn the caller's images and video into `data:` URLs, or say why not.
114
+ * Runs before any request, so every refusal costs nothing and none is retried.
115
+ */
116
+ export async function prepareDecisionMedia(request, limits, vendor, alternatives) {
117
+ const images = request.images ?? [];
118
+ if (images.length === 0 && request.video === undefined) {
119
+ return { status: "prepared", media: undefined };
120
+ }
121
+ if (!limits) {
122
+ const use = alternatives.length > 0
123
+ ? ` Use a decision provider that does: ${alternatives.join(", ")}.`
124
+ : "";
125
+ return {
126
+ status: "refused",
127
+ message: `${vendor} does not accept images or video.${use}`,
128
+ };
129
+ }
130
+ if (images.length > limits.maxImages) {
131
+ return {
132
+ status: "refused",
133
+ message: `${vendor} accepts at most ${limits.maxImages} images per request; this one has ${images.length}.`,
134
+ };
135
+ }
136
+ if (request.video !== undefined && !limits.video) {
137
+ return { status: "refused", message: `${vendor} does not accept video.` };
138
+ }
139
+ const prepared = [];
140
+ for (const [index, image] of images.entries()) {
141
+ const converted = await toDataUrl(image, "image", `Image ${index + 1}`, limits.maxRequestBytes);
142
+ if (converted.status === "error") {
143
+ return { status: "refused", message: converted.message };
144
+ }
145
+ prepared.push(converted.dataUrl);
146
+ }
147
+ let video;
148
+ if (request.video !== undefined) {
149
+ const converted = await toDataUrl(request.video, "video", "The video", limits.maxRequestBytes);
150
+ if (converted.status === "error") {
151
+ return { status: "refused", message: converted.message };
152
+ }
153
+ video = converted.dataUrl;
154
+ }
155
+ const bytes = prepared.reduce((sum, url) => sum + url.length, video?.length ?? 0);
156
+ return {
157
+ status: "prepared",
158
+ media: { images: prepared, ...(video ? { video } : {}), bytes },
159
+ };
160
+ }
@@ -2,7 +2,7 @@
2
2
  * Centralized model choices for CLI commands
3
3
  * Derives choices from model enums to ensure consistency
4
4
  */
5
- import { AIProviderName, OpenAIModels, AnthropicModels, GoogleAIModels, BedrockModels, VertexModels, OllamaModels, AzureOpenAIModels, LiteLLMModels, SageMakerModels, OpenRouterModels, NvidiaNimModels, CohereModels, VoyageModels, JinaModels, StabilityModels, IdeogramModels, RecraftModels, TypeSafeModels, LayaModels, ReplicateModels, } from "../constants/enums.js";
5
+ import { AIProviderName, OpenAIModels, AnthropicModels, GoogleAIModels, BedrockModels, VertexModels, OllamaModels, AzureOpenAIModels, LiteLLMModels, SageMakerModels, OpenRouterModels, NvidiaNimModels, CohereModels, VoyageModels, JinaModels, StabilityModels, IdeogramModels, RecraftModels, TypeSafeModels, LayaModels, XorModels, ReplicateModels, } from "../constants/enums.js";
6
6
  import { getCatalogJsonEntries } from "../providers/catalog/loader.js";
7
7
  /**
8
8
  * Looks up the JSON catalog entry for a provider, if it is one of the 11
@@ -387,6 +387,12 @@ const TOP_MODELS_CONFIG = {
387
387
  description: "Let Laya pick a checkpoint by language",
388
388
  },
389
389
  ],
390
+ [AIProviderName.XOR]: [
391
+ {
392
+ model: XorModels.XOR_1_1,
393
+ description: "Recommended - XOR 1.1, decision model that also reads images and video",
394
+ },
395
+ ],
390
396
  [AIProviderName.AUTO]: [],
391
397
  };
392
398
  /**
@@ -427,6 +433,7 @@ export const DEFAULT_MODELS = {
427
433
  [AIProviderName.RECRAFT]: RecraftModels.RECRAFT_V3,
428
434
  [AIProviderName.TYPESAFE]: TypeSafeModels.JEV_LATEST,
429
435
  [AIProviderName.LAYA]: LayaModels.TYPED_DECISIONS,
436
+ [AIProviderName.XOR]: XorModels.XOR_1_1,
430
437
  };
431
438
  /**
432
439
  * Model enum mappings for getAllModels.
@@ -460,6 +467,7 @@ const MODEL_ENUMS = {
460
467
  [AIProviderName.RECRAFT]: RecraftModels,
461
468
  [AIProviderName.TYPESAFE]: TypeSafeModels,
462
469
  [AIProviderName.LAYA]: LayaModels,
470
+ [AIProviderName.XOR]: XorModels,
463
471
  [AIProviderName.AUTO]: null,
464
472
  };
465
473
  /**
@@ -405,3 +405,8 @@ export declare function createTypeSafeConfig(): ProviderConfigOptions;
405
405
  * built-in endpoint: LAYA_BASE_URL is required alongside the key.
406
406
  */
407
407
  export declare function createLayaConfig(): ProviderConfigOptions;
408
+ /**
409
+ * XOR — the `decide` inference type. Not a text provider, and it has no
410
+ * built-in endpoint: XOR_BASE_URL is required alongside the key.
411
+ */
412
+ export declare function createXorConfig(): ProviderConfigOptions;
@@ -1341,3 +1341,20 @@ export function createLayaConfig() {
1341
1341
  ],
1342
1342
  };
1343
1343
  }
1344
+ /**
1345
+ * XOR — the `decide` inference type. Not a text provider, and it has no
1346
+ * built-in endpoint: XOR_BASE_URL is required alongside the key.
1347
+ */
1348
+ export function createXorConfig() {
1349
+ return {
1350
+ providerName: "XOR",
1351
+ envVarName: "XOR_API_KEY",
1352
+ setupUrl: "https://huggingface.co/juspay/xor",
1353
+ description: "API key for your XOR endpoint",
1354
+ instructions: [
1355
+ "1. Reach an XOR deployment, or use a LiteLLM proxy with a pass-through route to one",
1356
+ "2. Set XOR_BASE_URL to its origin (NeuroLink calls <XOR_BASE_URL>/v1/systemone)",
1357
+ "3. Set XOR_API_KEY to the key it accepts (on LiteLLM, a virtual key whose team allows xor-1.1)",
1358
+ ],
1359
+ };
1360
+ }