@juspay/neurolink 12.37.4 → 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.
Files changed (49) hide show
  1. package/CHANGELOG.md +3 -3
  2. package/README.md +60 -50
  3. package/dist/browser/neurolink.min.js +423 -423
  4. package/dist/cli/commands/decide.d.ts +2 -2
  5. package/dist/cli/commands/decide.js +22 -3
  6. package/dist/cli/commands/setup.js +2 -1
  7. package/dist/cli/factories/commandFactory.js +1 -1
  8. package/dist/constants/enums.d.ts +9 -0
  9. package/dist/constants/enums.js +10 -0
  10. package/dist/core/baseProvider.d.ts +10 -1
  11. package/dist/core/baseProvider.js +35 -7
  12. package/dist/factories/providerDescriptors.d.ts +2 -0
  13. package/dist/factories/providerDescriptors.js +52 -6
  14. package/dist/factories/providerRegistry.js +9 -1
  15. package/dist/models/manifestRegistry.js +2 -0
  16. package/dist/models/manifests/anthropic.js +2 -2
  17. package/dist/models/manifests/xor.d.ts +12 -0
  18. package/dist/models/manifests/xor.js +30 -0
  19. package/dist/models/modelRegistry.d.ts +12 -1
  20. package/dist/models/modelRegistry.js +57 -5
  21. package/dist/neurolink.js +6 -0
  22. package/dist/providers/amazonSagemaker.js +1 -1
  23. package/dist/providers/anthropic/client.js +29 -5
  24. package/dist/providers/anthropic/structuredOutput.d.ts +4 -1
  25. package/dist/providers/anthropic/structuredOutput.js +13 -5
  26. package/dist/providers/catalog/cerebras.json +7 -8
  27. package/dist/providers/catalog/fireworks.json +10 -12
  28. package/dist/providers/googleVertex/client.d.ts +3 -3
  29. package/dist/providers/googleVertex/client.js +79 -26
  30. package/dist/providers/laya.js +1 -19
  31. package/dist/providers/openaiChatCompletionsBase.js +1 -1
  32. package/dist/providers/systemOneDecision.d.ts +21 -2
  33. package/dist/providers/systemOneDecision.js +64 -4
  34. package/dist/providers/xor.d.ts +48 -0
  35. package/dist/providers/xor.js +192 -0
  36. package/dist/proxy/vertexAnthropicFallback.d.ts +1 -12
  37. package/dist/proxy/vertexAnthropicFallback.js +43 -2
  38. package/dist/types/classifierRouter.d.ts +5 -4
  39. package/dist/types/cli.d.ts +6 -2
  40. package/dist/types/decision.d.ts +60 -1
  41. package/dist/types/model.d.ts +6 -0
  42. package/dist/types/providers.d.ts +10 -0
  43. package/dist/utils/decisionMedia.d.ts +9 -0
  44. package/dist/utils/decisionMedia.js +160 -0
  45. package/dist/utils/modelChoices.js +9 -1
  46. package/dist/utils/providerConfig.d.ts +5 -0
  47. package/dist/utils/providerConfig.js +17 -0
  48. package/docs-site/static/search-index.json +63 -46
  49. package/package.json +2 -1
@@ -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
+ }