@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.
- package/CHANGELOG.md +3 -3
- package/README.md +60 -50
- package/dist/browser/neurolink.min.js +423 -423
- package/dist/cli/commands/decide.d.ts +2 -2
- package/dist/cli/commands/decide.js +22 -3
- package/dist/cli/commands/setup.js +2 -1
- package/dist/cli/factories/commandFactory.js +1 -1
- package/dist/constants/enums.d.ts +9 -0
- package/dist/constants/enums.js +10 -0
- package/dist/core/baseProvider.d.ts +10 -1
- package/dist/core/baseProvider.js +35 -7
- package/dist/factories/providerDescriptors.d.ts +2 -0
- package/dist/factories/providerDescriptors.js +52 -6
- package/dist/factories/providerRegistry.js +9 -1
- package/dist/models/manifestRegistry.js +2 -0
- package/dist/models/manifests/anthropic.js +2 -2
- package/dist/models/manifests/xor.d.ts +12 -0
- package/dist/models/manifests/xor.js +30 -0
- package/dist/models/modelRegistry.d.ts +12 -1
- package/dist/models/modelRegistry.js +57 -5
- package/dist/neurolink.js +6 -0
- package/dist/providers/amazonSagemaker.js +1 -1
- package/dist/providers/anthropic/client.js +29 -5
- package/dist/providers/anthropic/structuredOutput.d.ts +4 -1
- package/dist/providers/anthropic/structuredOutput.js +13 -5
- package/dist/providers/catalog/cerebras.json +7 -8
- package/dist/providers/catalog/fireworks.json +10 -12
- package/dist/providers/googleVertex/client.d.ts +3 -3
- package/dist/providers/googleVertex/client.js +79 -26
- package/dist/providers/laya.js +1 -19
- package/dist/providers/openaiChatCompletionsBase.js +1 -1
- package/dist/providers/systemOneDecision.d.ts +21 -2
- package/dist/providers/systemOneDecision.js +64 -4
- package/dist/providers/xor.d.ts +48 -0
- package/dist/providers/xor.js +192 -0
- package/dist/proxy/vertexAnthropicFallback.d.ts +1 -12
- package/dist/proxy/vertexAnthropicFallback.js +43 -2
- package/dist/types/classifierRouter.d.ts +5 -4
- package/dist/types/cli.d.ts +6 -2
- package/dist/types/decision.d.ts +60 -1
- package/dist/types/model.d.ts +6 -0
- package/dist/types/providers.d.ts +10 -0
- package/dist/utils/decisionMedia.d.ts +9 -0
- package/dist/utils/decisionMedia.js +160 -0
- package/dist/utils/modelChoices.js +9 -1
- package/dist/utils/providerConfig.d.ts +5 -0
- package/dist/utils/providerConfig.js +17 -0
- package/docs-site/static/search-index.json +63 -46
- package/package.json +2 -1
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
* for the full vocabulary.
|
|
9
9
|
*
|
|
10
10
|
* Credentials are env-only here, exactly as every other CLI command: the
|
|
11
|
-
* decision provider reads its own settings (TYPESAFE_API_KEY,
|
|
12
|
-
* and
|
|
11
|
+
* decision provider reads its own settings (TYPESAFE_API_KEY, LAYA_API_KEY and
|
|
12
|
+
* LAYA_BASE_URL, or XOR_API_KEY and XOR_BASE_URL) itself.
|
|
13
13
|
*/
|
|
14
14
|
import type { CommandModule } from "yargs";
|
|
15
15
|
import type { CliDecideArgs } from "../../types/index.js";
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
* for the full vocabulary.
|
|
9
9
|
*
|
|
10
10
|
* Credentials are env-only here, exactly as every other CLI command: the
|
|
11
|
-
* decision provider reads its own settings (TYPESAFE_API_KEY,
|
|
12
|
-
* and
|
|
11
|
+
* decision provider reads its own settings (TYPESAFE_API_KEY, LAYA_API_KEY and
|
|
12
|
+
* LAYA_BASE_URL, or XOR_API_KEY and XOR_BASE_URL) itself.
|
|
13
13
|
*/
|
|
14
14
|
import chalk from "chalk";
|
|
15
15
|
import ora from "ora";
|
|
@@ -186,6 +186,17 @@ export const decideCommand = {
|
|
|
186
186
|
.option("model", {
|
|
187
187
|
type: "string",
|
|
188
188
|
describe: "Overrides the provider's configured model for this call",
|
|
189
|
+
})
|
|
190
|
+
.option("image", {
|
|
191
|
+
type: "string",
|
|
192
|
+
array: true,
|
|
193
|
+
nargs: 1,
|
|
194
|
+
describe: "Image for the model to read: a file path or a data: URL. Repeat for several (XOR takes up to 8)",
|
|
195
|
+
})
|
|
196
|
+
.option("video", {
|
|
197
|
+
type: "string",
|
|
198
|
+
nargs: 1,
|
|
199
|
+
describe: "One video for the model to read: a file path or a data: URL",
|
|
189
200
|
})
|
|
190
201
|
.option("timeout", {
|
|
191
202
|
type: "number",
|
|
@@ -205,7 +216,8 @@ export const decideCommand = {
|
|
|
205
216
|
describe: "Enable debug logging (written to stderr when --format json is used)",
|
|
206
217
|
})
|
|
207
218
|
.example('$0 decide "Refund request for a damaged item" --questions \'{"urgent":{"type":"boolean","instructions":"Is this urgent?"}}\'', "Ask a single yes/no question")
|
|
208
|
-
.example("$0 decide --state-file ticket.json --questions-file questions.json --format json", "Read state and questions from files, emit raw JSON")
|
|
219
|
+
.example("$0 decide --state-file ticket.json --questions-file questions.json --format json", "Read state and questions from files, emit raw JSON")
|
|
220
|
+
.example('$0 decide "What color is this?" --provider xor --image ./photo.png --questions \'{"color":{"type":"choice","instructions":"What color is the image?","criteria":{"red":"red","blue":"blue"}}}\'', "Ask about an image (XOR only)"),
|
|
209
221
|
handler: async (argv) => {
|
|
210
222
|
const outputFormat = argv.format ?? "text";
|
|
211
223
|
// --- Validate before any provider work ---------------------------------
|
|
@@ -235,6 +247,11 @@ export const decideCommand = {
|
|
|
235
247
|
logger.error(chalk.red("Error: --timeout must be a positive number of milliseconds."));
|
|
236
248
|
process.exit(1);
|
|
237
249
|
}
|
|
250
|
+
const videos = [].concat(argv.video ?? []);
|
|
251
|
+
if (videos.length > 1) {
|
|
252
|
+
logger.error(chalk.red("Error: pass at most one --video."));
|
|
253
|
+
process.exit(1);
|
|
254
|
+
}
|
|
238
255
|
if (!isDecisionQuestionMap(parsedQuestions)) {
|
|
239
256
|
logger.error(chalk.red('Error: questions must be a non-empty object of { type: "boolean" | "choice" | "score", ... }.'));
|
|
240
257
|
process.exit(1);
|
|
@@ -261,6 +278,8 @@ export const decideCommand = {
|
|
|
261
278
|
provider: argv.provider,
|
|
262
279
|
model: argv.model,
|
|
263
280
|
timeoutMs: argv.timeout,
|
|
281
|
+
...(argv.image && argv.image.length > 0 ? { images: argv.image } : {}),
|
|
282
|
+
...(videos.length === 1 ? { video: videos[0] } : {}),
|
|
264
283
|
});
|
|
265
284
|
spinner?.succeed("Decision complete");
|
|
266
285
|
if (outputFormat === "json") {
|
|
@@ -20,7 +20,7 @@ import { handleGCPSetup } from "./setup-gcp.js";
|
|
|
20
20
|
import { handleHuggingFaceSetup } from "./setup-huggingface.js";
|
|
21
21
|
import { handleMistralSetup } from "./setup-mistral.js";
|
|
22
22
|
import { PROVIDER_DESCRIPTORS_BY_NAME } from "../../factories/providerDescriptors.js";
|
|
23
|
-
import { createCohereConfig, createDeepSeekConfig, createIdeogramConfig, createJinaConfig, createNvidiaNimConfig, createOpenAICompatibleConfig, createRecraftConfig, createReplicateConfig, createStabilityConfig, createVoyageConfig, createTypeSafeConfig, createLayaConfig, satisfiesFallbacks, } from "../../utils/providerConfig.js";
|
|
23
|
+
import { createCohereConfig, createDeepSeekConfig, createIdeogramConfig, createJinaConfig, createNvidiaNimConfig, createOpenAICompatibleConfig, createRecraftConfig, createReplicateConfig, createStabilityConfig, createVoyageConfig, createTypeSafeConfig, createLayaConfig, createXorConfig, satisfiesFallbacks, } from "../../utils/providerConfig.js";
|
|
24
24
|
import { getCatalogJsonEntries, buildCatalogConfigOptions, } from "../../providers/catalog/loader.js";
|
|
25
25
|
// Provider information database
|
|
26
26
|
const PROVIDERS = [
|
|
@@ -170,6 +170,7 @@ export const EXTRA_PROVIDER_CONFIGS = {
|
|
|
170
170
|
recraft: createRecraftConfig(),
|
|
171
171
|
typesafe: createTypeSafeConfig(),
|
|
172
172
|
laya: createLayaConfig(),
|
|
173
|
+
xor: createXorConfig(),
|
|
173
174
|
...Object.fromEntries(getCatalogJsonEntries()
|
|
174
175
|
.filter((e) => e.id !== "mistral")
|
|
175
176
|
.map((e) => [e.id, buildCatalogConfigOptions(e)])),
|
|
@@ -616,7 +616,7 @@ export class CLICommandFactory {
|
|
|
616
616
|
},
|
|
617
617
|
classifierStrategy: {
|
|
618
618
|
type: "string",
|
|
619
|
-
description: "Classifier strategy: 'auto' (default — 'jev' when a decision provider is configured, such as TYPESAFE_API_KEY,
|
|
619
|
+
description: "Classifier strategy: 'auto' (default — 'jev' when a decision provider is configured, such as TYPESAFE_API_KEY, LAYA_API_KEY with LAYA_BASE_URL, or XOR_API_KEY with XOR_BASE_URL, else 'heuristic'), 'heuristic' (no LLM), 'llm' (a cheap model picks per prompt), or 'jev' (a System One decision model — TypeSafe Jev, Laya or XOR — with calibrated confidence).",
|
|
620
620
|
choices: ["auto", "heuristic", "llm", "jev"],
|
|
621
621
|
alias: "classifier-strategy",
|
|
622
622
|
},
|
|
@@ -48,6 +48,8 @@ export declare enum AIProviderName {
|
|
|
48
48
|
TYPESAFE = "typesafe",
|
|
49
49
|
/** Laya (Convai, open weights) — serves the `decide` inference type only. */
|
|
50
50
|
LAYA = "laya",
|
|
51
|
+
/** XOR (Juspay, open weights) — serves the `decide` inference type only. */
|
|
52
|
+
XOR = "xor",
|
|
51
53
|
AUTO = "auto"
|
|
52
54
|
}
|
|
53
55
|
/**
|
|
@@ -1097,3 +1099,10 @@ export declare enum LayaModels {
|
|
|
1097
1099
|
MULTILINGUAL = "multilingual",
|
|
1098
1100
|
AUTO = "auto"
|
|
1099
1101
|
}
|
|
1102
|
+
/**
|
|
1103
|
+
* XOR decision models. Hand-written: XOR is a Tier-3 provider, so it is not in
|
|
1104
|
+
* the provider catalog and codegen never touches this.
|
|
1105
|
+
*/
|
|
1106
|
+
export declare enum XorModels {
|
|
1107
|
+
XOR_1_1 = "xor-1.1"
|
|
1108
|
+
}
|
package/dist/constants/enums.js
CHANGED
|
@@ -54,6 +54,8 @@ export var AIProviderName;
|
|
|
54
54
|
AIProviderName["TYPESAFE"] = "typesafe";
|
|
55
55
|
/** Laya (Convai, open weights) — serves the `decide` inference type only. */
|
|
56
56
|
AIProviderName["LAYA"] = "laya";
|
|
57
|
+
/** XOR (Juspay, open weights) — serves the `decide` inference type only. */
|
|
58
|
+
AIProviderName["XOR"] = "xor";
|
|
57
59
|
AIProviderName["AUTO"] = "auto";
|
|
58
60
|
})(AIProviderName || (AIProviderName = {}));
|
|
59
61
|
/**
|
|
@@ -1357,3 +1359,11 @@ export var LayaModels;
|
|
|
1357
1359
|
LayaModels["MULTILINGUAL"] = "multilingual";
|
|
1358
1360
|
LayaModels["AUTO"] = "auto";
|
|
1359
1361
|
})(LayaModels || (LayaModels = {}));
|
|
1362
|
+
/**
|
|
1363
|
+
* XOR decision models. Hand-written: XOR is a Tier-3 provider, so it is not in
|
|
1364
|
+
* the provider catalog and codegen never touches this.
|
|
1365
|
+
*/
|
|
1366
|
+
export var XorModels;
|
|
1367
|
+
(function (XorModels) {
|
|
1368
|
+
XorModels["XOR_1_1"] = "xor-1.1";
|
|
1369
|
+
})(XorModels || (XorModels = {}));
|
|
@@ -262,12 +262,21 @@ export declare abstract class BaseProvider implements AIProvider {
|
|
|
262
262
|
* and a retired default model stops degrading to the next live one in the
|
|
263
263
|
* catalog — which is exactly what the provider contract gate checks.
|
|
264
264
|
*/
|
|
265
|
-
protected runGenerateWithModelFallback(attempt: () => Promise<EnhancedGenerateResult | null>, callerOwnsFallback: boolean): Promise<EnhancedGenerateResult | null>;
|
|
265
|
+
protected runGenerateWithModelFallback(attempt: () => Promise<EnhancedGenerateResult | null>, callerOwnsFallback: boolean, options?: TextGenerationOptions): Promise<EnhancedGenerateResult | null>;
|
|
266
266
|
/**
|
|
267
267
|
* Model ids to try when this provider rejects its current model as invalid.
|
|
268
268
|
* Empty by default; catalog-driven providers return their `fallbacks`.
|
|
269
269
|
*/
|
|
270
270
|
protected getModelFallbacks(): string[];
|
|
271
|
+
/**
|
|
272
|
+
* The fallbacks an invalid-model retry may try for this request. A catalog
|
|
273
|
+
* orders `fallbacks` for text, so a request carrying an image keeps only the
|
|
274
|
+
* vision-capable ones: a text-only candidate fails the vision guard, and
|
|
275
|
+
* that error names the fallback instead of the retired model the caller
|
|
276
|
+
* asked for. With no vision-capable candidate left, the caller gets the
|
|
277
|
+
* original invalid-model error.
|
|
278
|
+
*/
|
|
279
|
+
protected modelFallbacksFor(options: TextGenerationOptions | StreamOptions, requestedModel: string): string[];
|
|
271
280
|
/**
|
|
272
281
|
* Alias for generate method - implements AIProvider interface
|
|
273
282
|
*/
|
|
@@ -46,6 +46,7 @@ import { StreamHandler } from "./modules/StreamHandler.js";
|
|
|
46
46
|
import { TelemetryHandler } from "./modules/TelemetryHandler.js";
|
|
47
47
|
import { ToolsManager } from "./modules/ToolsManager.js";
|
|
48
48
|
import { Utilities } from "./modules/Utilities.js";
|
|
49
|
+
import { ProviderImageAdapter } from "../adapters/providerImageAdapter.js";
|
|
49
50
|
import { generateOnceNative } from "../utils/nativeSingleShot.js";
|
|
50
51
|
import { validateExecutionControl } from "../utils/parameterValidation.js";
|
|
51
52
|
import { extractTokenUsage } from "../utils/tokenUtils.js";
|
|
@@ -107,6 +108,18 @@ function isProviderErrorClassified(error) {
|
|
|
107
108
|
}
|
|
108
109
|
return error[PROVIDER_ERROR_CLASSIFIED] === true;
|
|
109
110
|
}
|
|
111
|
+
function requestCarriesImages(options) {
|
|
112
|
+
const input = options.input;
|
|
113
|
+
if (!input) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
if ((input.images?.length ?? 0) > 0) {
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
return ("content" in input &&
|
|
120
|
+
Array.isArray(input.content) &&
|
|
121
|
+
input.content.some((part) => part.type === "image"));
|
|
122
|
+
}
|
|
110
123
|
export class BaseProvider {
|
|
111
124
|
// Not `readonly` because providers that auto-discover the model from a
|
|
112
125
|
// /v1/models endpoint (lm-studio, llamacpp) need to update modelName after
|
|
@@ -523,9 +536,7 @@ export class BaseProvider {
|
|
|
523
536
|
throw error;
|
|
524
537
|
}
|
|
525
538
|
const requestedModel = provider.modelName;
|
|
526
|
-
const candidates = provider
|
|
527
|
-
.getModelFallbacks()
|
|
528
|
-
.filter((model) => model !== requestedModel);
|
|
539
|
+
const candidates = provider.modelFallbacksFor(options, requestedModel);
|
|
529
540
|
for (const candidate of candidates) {
|
|
530
541
|
logger.warn(`[${provider.providerName}] model "${requestedModel}" was rejected as invalid — retrying stream with fallback "${candidate}". This provider's catalog entry is stale; run "pnpm run check:models".`);
|
|
531
542
|
provider.refreshHandlersForModel(candidate);
|
|
@@ -671,7 +682,7 @@ export class BaseProvider {
|
|
|
671
682
|
return undefined;
|
|
672
683
|
}
|
|
673
684
|
const requestedModel = this.modelName;
|
|
674
|
-
const candidates = this.
|
|
685
|
+
const candidates = this.modelFallbacksFor(options, requestedModel);
|
|
675
686
|
for (const candidate of candidates) {
|
|
676
687
|
logger.warn(`[${this.providerName}] model "${requestedModel}" was rejected as invalid — retrying stream with fallback "${candidate}". This provider's catalog entry is stale; run "pnpm run check:models".`);
|
|
677
688
|
this.refreshHandlersForModel(candidate);
|
|
@@ -1493,7 +1504,7 @@ export class BaseProvider {
|
|
|
1493
1504
|
// or a router that retries on its own) pass the flag on both paths;
|
|
1494
1505
|
// TextGenerationOptions declares it, so a plain read is enough here.
|
|
1495
1506
|
const callerOwnsFallback = options.disableInternalFallback === true;
|
|
1496
|
-
return await this.runGenerateWithModelFallback(attempt, callerOwnsFallback);
|
|
1507
|
+
return await this.runGenerateWithModelFallback(attempt, callerOwnsFallback, options);
|
|
1497
1508
|
}
|
|
1498
1509
|
/**
|
|
1499
1510
|
* Models the catalog names are the models a vendor served when the entry was
|
|
@@ -1520,7 +1531,7 @@ export class BaseProvider {
|
|
|
1520
1531
|
* and a retired default model stops degrading to the next live one in the
|
|
1521
1532
|
* catalog — which is exactly what the provider contract gate checks.
|
|
1522
1533
|
*/
|
|
1523
|
-
async runGenerateWithModelFallback(attempt, callerOwnsFallback) {
|
|
1534
|
+
async runGenerateWithModelFallback(attempt, callerOwnsFallback, options) {
|
|
1524
1535
|
const requestedModel = this.modelName;
|
|
1525
1536
|
try {
|
|
1526
1537
|
return await attempt();
|
|
@@ -1529,7 +1540,9 @@ export class BaseProvider {
|
|
|
1529
1540
|
if (callerOwnsFallback || !isInvalidModelError(error)) {
|
|
1530
1541
|
throw error;
|
|
1531
1542
|
}
|
|
1532
|
-
const candidates =
|
|
1543
|
+
const candidates = options
|
|
1544
|
+
? this.modelFallbacksFor(options, requestedModel)
|
|
1545
|
+
: this.getModelFallbacks().filter((model) => model !== requestedModel);
|
|
1533
1546
|
if (candidates.length === 0) {
|
|
1534
1547
|
throw error;
|
|
1535
1548
|
}
|
|
@@ -1563,6 +1576,21 @@ export class BaseProvider {
|
|
|
1563
1576
|
getModelFallbacks() {
|
|
1564
1577
|
return [];
|
|
1565
1578
|
}
|
|
1579
|
+
/**
|
|
1580
|
+
* The fallbacks an invalid-model retry may try for this request. A catalog
|
|
1581
|
+
* orders `fallbacks` for text, so a request carrying an image keeps only the
|
|
1582
|
+
* vision-capable ones: a text-only candidate fails the vision guard, and
|
|
1583
|
+
* that error names the fallback instead of the retired model the caller
|
|
1584
|
+
* asked for. With no vision-capable candidate left, the caller gets the
|
|
1585
|
+
* original invalid-model error.
|
|
1586
|
+
*/
|
|
1587
|
+
modelFallbacksFor(options, requestedModel) {
|
|
1588
|
+
const candidates = this.getModelFallbacks().filter((model) => model !== requestedModel);
|
|
1589
|
+
if (!requestCarriesImages(options)) {
|
|
1590
|
+
return candidates;
|
|
1591
|
+
}
|
|
1592
|
+
return candidates.filter((model) => ProviderImageAdapter.supportsVision(this.providerName, model));
|
|
1593
|
+
}
|
|
1566
1594
|
/**
|
|
1567
1595
|
* Alias for generate method - implements AIProvider interface
|
|
1568
1596
|
*/
|
|
@@ -30,6 +30,8 @@ export declare const DECISION_PROVIDERS: readonly ProviderDescriptor[];
|
|
|
30
30
|
* internal consumer treats as "carry on exactly as before".
|
|
31
31
|
*/
|
|
32
32
|
export declare function resolveDefaultDecisionProvider(credentials?: NeurolinkCredentials): string | undefined;
|
|
33
|
+
/** Names of the decision providers that accept images or video. */
|
|
34
|
+
export declare function listMediaDecisionProviders(): string[];
|
|
33
35
|
/**
|
|
34
36
|
* The variables that would configure each decision provider, one clause per
|
|
35
37
|
* provider in precedence order, e.g. "TYPESAFE_API_KEY or AI_GATEWAY_API_KEY
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AIProviderName } from "../constants/enums.js";
|
|
2
|
-
import { GoogleAIModels, OpenAIModels, AnthropicModels, VertexModels, OllamaModels, LiteLLMModels, NvidiaNimModels, OpenRouterModels, CohereModels, VoyageModels, TypeSafeModels, LayaModels, JinaModels, StabilityModels, IdeogramModels, RecraftModels, ReplicateModels, } from "../constants/enums.js";
|
|
2
|
+
import { GoogleAIModels, OpenAIModels, AnthropicModels, VertexModels, OllamaModels, LiteLLMModels, NvidiaNimModels, OpenRouterModels, CohereModels, VoyageModels, TypeSafeModels, LayaModels, XorModels, JinaModels, StabilityModels, IdeogramModels, RecraftModels, ReplicateModels, } from "../constants/enums.js";
|
|
3
3
|
import { API_KEY_FORMATS } from "../utils/providerConfig.js";
|
|
4
4
|
import { getCatalogJsonEntries, catalogCredentialsKey, catalogEnvVar, } from "../providers/catalog/loader.js";
|
|
5
5
|
import { DEFAULT_INFERENCE_KINDS } from "../types/index.js";
|
|
@@ -451,11 +451,12 @@ const HAND_DESCRIPTORS = [
|
|
|
451
451
|
timeouts: { decideMs: 5000 },
|
|
452
452
|
setupUrl: "https://console.typesafe.ai/keys",
|
|
453
453
|
},
|
|
454
|
-
// Laya MUST stay after TypeSafe
|
|
455
|
-
// the first configured
|
|
456
|
-
//
|
|
457
|
-
//
|
|
458
|
-
//
|
|
454
|
+
// Laya MUST stay after TypeSafe, and XOR after Laya.
|
|
455
|
+
// resolveDefaultDecisionProvider() returns the first configured
|
|
456
|
+
// DECISION_PROVIDERS entry, in this order, so a host configured for several
|
|
457
|
+
// keeps Jev for every built-in consumer and reaches the others only by
|
|
458
|
+
// naming them. Reordering these silently changes which model routes,
|
|
459
|
+
// compacts and plans for every such host.
|
|
459
460
|
{
|
|
460
461
|
name: AIProviderName.LAYA,
|
|
461
462
|
aliases: [],
|
|
@@ -501,6 +502,47 @@ const HAND_DESCRIPTORS = [
|
|
|
501
502
|
},
|
|
502
503
|
setupUrl: "https://github.com/NandhaKishorM/laya",
|
|
503
504
|
},
|
|
505
|
+
{
|
|
506
|
+
name: AIProviderName.XOR,
|
|
507
|
+
aliases: [],
|
|
508
|
+
credentialsKey: "xor",
|
|
509
|
+
envVars: {
|
|
510
|
+
apiKey: "XOR_API_KEY",
|
|
511
|
+
baseURL: "XOR_BASE_URL",
|
|
512
|
+
// XOR has no built-in endpoint (it is reached at a deployment or behind a
|
|
513
|
+
// proxy), so a key is useless without a base URL. Both are required for
|
|
514
|
+
// it to count as configured, from the environment or credentials.xor.
|
|
515
|
+
extraRequired: ["XOR_BASE_URL"],
|
|
516
|
+
model: "XOR_MODEL",
|
|
517
|
+
},
|
|
518
|
+
defaultModel: XorModels.XOR_1_1,
|
|
519
|
+
// Serves only `decide`, like TypeSafe and Laya — the one declaration that
|
|
520
|
+
// keeps it out of every generation code path.
|
|
521
|
+
inferenceKinds: ["decide"],
|
|
522
|
+
toolSupport: "none",
|
|
523
|
+
localRuntime: false,
|
|
524
|
+
healthCheck: "env-only",
|
|
525
|
+
// Deliberately NO autoSelectPriority / autoSelectPreference /
|
|
526
|
+
// defaultHealthSweepPriority, for the same reason as TypeSafe above.
|
|
527
|
+
timeouts: { decideMs: 5000 },
|
|
528
|
+
// The deployment's prefill is 250,000 tokens, shared by the state, the
|
|
529
|
+
// questions and any media. 200,000 leaves 20% for those, and a state
|
|
530
|
+
// between this and the server's 4 MB would otherwise pass validation and
|
|
531
|
+
// fail inside the engine as a 500, which the base class would retry. There
|
|
532
|
+
// is no question cap (the server only limits a choice to 2..255 options).
|
|
533
|
+
// Non-ASCII text is charged per character because the four-characters-
|
|
534
|
+
// per-token estimate is several times too generous for CJK on this
|
|
535
|
+
// tokenizer. One token per character is a deliberate upper bound, to be
|
|
536
|
+
// replaced by a live measurement.
|
|
537
|
+
decisionLimits: {
|
|
538
|
+
maxStateTokens: 200_000,
|
|
539
|
+
nonAsciiTokensPerChar: 1,
|
|
540
|
+
// The server takes 1..8 images and one video, and refuses a body over
|
|
541
|
+
// 8 MB.
|
|
542
|
+
media: { maxImages: 8, video: true, maxRequestBytes: 8 * 1024 * 1024 },
|
|
543
|
+
},
|
|
544
|
+
setupUrl: "https://huggingface.co/juspay/xor",
|
|
545
|
+
},
|
|
504
546
|
];
|
|
505
547
|
/**
|
|
506
548
|
* Builds a ProviderDescriptor for every JSON-catalog provider. Every field
|
|
@@ -636,6 +678,10 @@ function isDecisionProviderConfigured(descriptor, credentials) {
|
|
|
636
678
|
export function resolveDefaultDecisionProvider(credentials) {
|
|
637
679
|
return DECISION_PROVIDERS.find((descriptor) => isDecisionProviderConfigured(descriptor, credentials))?.name;
|
|
638
680
|
}
|
|
681
|
+
/** Names of the decision providers that accept images or video. */
|
|
682
|
+
export function listMediaDecisionProviders() {
|
|
683
|
+
return DECISION_PROVIDERS.filter((descriptor) => descriptor.decisionLimits?.media !== undefined).map((descriptor) => descriptor.name);
|
|
684
|
+
}
|
|
639
685
|
/**
|
|
640
686
|
* The variables that would configure each decision provider, one clause per
|
|
641
687
|
* provider in precedence order, e.g. "TYPESAFE_API_KEY or AI_GATEWAY_API_KEY
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ProviderFactory } from "./providerFactory.js";
|
|
2
2
|
import { logger } from "../utils/logger.js";
|
|
3
|
-
import { AIProviderName, GoogleAIModels, OpenAIModels, AnthropicModels, VertexModels, OllamaModels, LiteLLMModels, NvidiaNimModels, OpenRouterModels, CohereModels, VoyageModels, JinaModels, StabilityModels, IdeogramModels, RecraftModels, TypeSafeModels, LayaModels, ReplicateModels, } from "../constants/enums.js";
|
|
3
|
+
import { AIProviderName, GoogleAIModels, OpenAIModels, AnthropicModels, VertexModels, OllamaModels, LiteLLMModels, NvidiaNimModels, OpenRouterModels, CohereModels, VoyageModels, JinaModels, StabilityModels, IdeogramModels, RecraftModels, TypeSafeModels, LayaModels, XorModels, ReplicateModels, } from "../constants/enums.js";
|
|
4
4
|
import { PROVIDER_DESCRIPTORS_BY_NAME } from "./providerDescriptors.js";
|
|
5
5
|
import { OPENAI_COMPAT_CATALOG } from "../providers/openaiCompatCatalog.js";
|
|
6
6
|
import { providerChoicesFor } from "./mediaHandlerCatalog.js";
|
|
@@ -242,6 +242,14 @@ export class ProviderRegistry {
|
|
|
242
242
|
const { LayaProvider } = await import("../providers/laya.js");
|
|
243
243
|
return new LayaProvider(modelName, sdk, undefined, layaCreds);
|
|
244
244
|
}, process.env.LAYA_MODEL || LayaModels.TYPED_DECISIONS, [], PROVIDER_DESCRIPTORS_BY_NAME.get(AIProviderName.LAYA));
|
|
245
|
+
// Register XOR — the third `decide` provider, reached at whatever base
|
|
246
|
+
// URL its config names. Its descriptor declares inferenceKinds:
|
|
247
|
+
// ["decide"], so nothing in the generation fallback chain can reach it.
|
|
248
|
+
ProviderFactory.registerProvider(AIProviderName.XOR, async (modelName, _providerName, sdk, _region, credentials) => {
|
|
249
|
+
const xorCreds = credentials;
|
|
250
|
+
const { XorProvider } = await import("../providers/xor.js");
|
|
251
|
+
return new XorProvider(modelName, sdk, undefined, xorCreds);
|
|
252
|
+
}, process.env.XOR_MODEL || XorModels.XOR_1_1, [], PROVIDER_DESCRIPTORS_BY_NAME.get(AIProviderName.XOR));
|
|
245
253
|
logger.debug("All AI providers registered successfully");
|
|
246
254
|
// ===== MEDIA HANDLER REGISTRATION =====
|
|
247
255
|
// Single registration path (Task 11): each ecosystem barrel (voice,
|
|
@@ -21,6 +21,7 @@ import { xaiManifest } from "./manifests/xai.js";
|
|
|
21
21
|
import { groqManifest } from "./manifests/groq.js";
|
|
22
22
|
import { typesafeManifest } from "./manifests/typesafe.js";
|
|
23
23
|
import { layaManifest } from "./manifests/laya.js";
|
|
24
|
+
import { xorManifest } from "./manifests/xor.js";
|
|
24
25
|
import { cohereManifest } from "./manifests/cohere.js";
|
|
25
26
|
import { togetherAiManifest } from "./manifests/together-ai.js";
|
|
26
27
|
import { fireworksManifest } from "./manifests/fireworks.js";
|
|
@@ -120,6 +121,7 @@ export const MANIFEST_REGISTRY = {
|
|
|
120
121
|
groq: groqManifest,
|
|
121
122
|
typesafe: typesafeManifest,
|
|
122
123
|
laya: layaManifest,
|
|
124
|
+
xor: xorManifest,
|
|
123
125
|
cerebras: catalogManifest("cerebras"),
|
|
124
126
|
sambanova: catalogManifest("sambanova"),
|
|
125
127
|
cohere: cohereManifest,
|
|
@@ -38,7 +38,7 @@ export const anthropicManifest = {
|
|
|
38
38
|
samplingParams: false, // matches SAMPLING_PARAM_REJECTING_FAMILIES /opus[-_.]?5(?![0-9])/i
|
|
39
39
|
},
|
|
40
40
|
"claude-sonnet-5-5": {
|
|
41
|
-
aliases: ["sonnet-5.5"],
|
|
41
|
+
aliases: ["sonnet-5.5", "claude-sonnet"],
|
|
42
42
|
displayName: "Claude Sonnet 5.5",
|
|
43
43
|
contextWindow: 1_000_000,
|
|
44
44
|
maxOutputTokens: 128_000,
|
|
@@ -66,7 +66,7 @@ export const anthropicManifest = {
|
|
|
66
66
|
samplingParams: false, // matches SAMPLING_PARAM_REJECTING_FAMILIES /fable/i
|
|
67
67
|
},
|
|
68
68
|
"claude-sonnet-5": {
|
|
69
|
-
aliases: ["sonnet-5"
|
|
69
|
+
aliases: ["sonnet-5"],
|
|
70
70
|
displayName: "Claude Sonnet 5",
|
|
71
71
|
contextWindow: 1_000_000,
|
|
72
72
|
// Was 64_000; corrected to 128_000 to match the official spec
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ProviderModelManifest } from "../../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* XOR — the `decide` inference type, not text generation.
|
|
4
|
+
*
|
|
5
|
+
* `contextWindow` is the deployment's maximum prefill, shared by the state,
|
|
6
|
+
* the questions and any images or video. `vision` describes `generate()`
|
|
7
|
+
* input, which XOR does not serve; its image input is `DecisionRequest.images`.
|
|
8
|
+
*
|
|
9
|
+
* `maxOutputTokens` is required by the manifest type but has no honest value
|
|
10
|
+
* for a model that emits no text, so a small non-zero figure is used.
|
|
11
|
+
*/
|
|
12
|
+
export declare const xorManifest: ProviderModelManifest;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XOR — the `decide` inference type, not text generation.
|
|
3
|
+
*
|
|
4
|
+
* `contextWindow` is the deployment's maximum prefill, shared by the state,
|
|
5
|
+
* the questions and any images or video. `vision` describes `generate()`
|
|
6
|
+
* input, which XOR does not serve; its image input is `DecisionRequest.images`.
|
|
7
|
+
*
|
|
8
|
+
* `maxOutputTokens` is required by the manifest type but has no honest value
|
|
9
|
+
* for a model that emits no text, so a small non-zero figure is used.
|
|
10
|
+
*/
|
|
11
|
+
export const xorManifest = {
|
|
12
|
+
defaultContextWindow: 250_000,
|
|
13
|
+
models: {
|
|
14
|
+
_default: {
|
|
15
|
+
aliases: [],
|
|
16
|
+
contextWindow: 250_000,
|
|
17
|
+
maxOutputTokens: 256,
|
|
18
|
+
vision: false,
|
|
19
|
+
functionCalling: false,
|
|
20
|
+
},
|
|
21
|
+
"xor-1.1": {
|
|
22
|
+
aliases: [],
|
|
23
|
+
displayName: "XOR 1.1",
|
|
24
|
+
contextWindow: 250_000,
|
|
25
|
+
maxOutputTokens: 256,
|
|
26
|
+
vision: false,
|
|
27
|
+
functionCalling: false,
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
};
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Part of Phase 4.1 - Models Command System
|
|
5
5
|
*/
|
|
6
6
|
import { AIProviderName } from "../constants/enums.js";
|
|
7
|
-
import type { JsonValue, ModelCapabilities, ModelInfo } from "../types/index.js";
|
|
7
|
+
import type { ClaudeDisabledThinkingReplacement, JsonValue, ModelCapabilities, ModelInfo } from "../types/index.js";
|
|
8
8
|
/**
|
|
9
9
|
* Public MODEL_REGISTRY: the manifest-derived entries merged over the
|
|
10
10
|
* hand-authored LEGACY_MODEL_REGISTRY base. Spread order matters — manifest
|
|
@@ -90,6 +90,17 @@ export declare function resolveSamplingParams(provider: string, model: string |
|
|
|
90
90
|
temperature?: number;
|
|
91
91
|
topP?: number;
|
|
92
92
|
};
|
|
93
|
+
/**
|
|
94
|
+
* Whether a Claude model accepts a forced `tool_choice` (`any` or a named
|
|
95
|
+
* `tool`). Unknown models default to accepting it.
|
|
96
|
+
*/
|
|
97
|
+
export declare function modelSupportsForcedToolChoice(model: string | undefined): boolean;
|
|
98
|
+
/**
|
|
99
|
+
* What to send instead of `thinking: {type: "disabled"}` to a Claude model.
|
|
100
|
+
* `between_tools` is refused at `xhigh` and `max` effort, so those fall back
|
|
101
|
+
* to adaptive thinking (`omit`).
|
|
102
|
+
*/
|
|
103
|
+
export declare function claudeDisabledThinkingReplacement(model: string | undefined, effort: string | undefined): ClaudeDisabledThinkingReplacement;
|
|
93
104
|
/**
|
|
94
105
|
* Get models by provider
|
|
95
106
|
*/
|
|
@@ -1168,14 +1168,14 @@ const LEGACY_MODEL_REGISTRY = {
|
|
|
1168
1168
|
translation: 9,
|
|
1169
1169
|
summarization: 9,
|
|
1170
1170
|
},
|
|
1171
|
-
aliases: ["sonnet-5"
|
|
1171
|
+
aliases: ["sonnet-5"],
|
|
1172
1172
|
deprecated: false,
|
|
1173
1173
|
isLocal: false,
|
|
1174
1174
|
releaseDate: "2026-06-30",
|
|
1175
1175
|
category: "general",
|
|
1176
1176
|
},
|
|
1177
|
-
// "claude-sonnet-latest"
|
|
1178
|
-
//
|
|
1177
|
+
// Owns "claude-sonnet-latest", but sits after CLAUDE_SONNET_5 so resolver
|
|
1178
|
+
// score ties still go to Sonnet 5.
|
|
1179
1179
|
[AnthropicModels.CLAUDE_SONNET_5_5]: {
|
|
1180
1180
|
id: AnthropicModels.CLAUDE_SONNET_5_5,
|
|
1181
1181
|
name: "Claude Sonnet 5.5",
|
|
@@ -1210,7 +1210,7 @@ const LEGACY_MODEL_REGISTRY = {
|
|
|
1210
1210
|
translation: 9,
|
|
1211
1211
|
summarization: 9,
|
|
1212
1212
|
},
|
|
1213
|
-
aliases: ["sonnet-5.5"],
|
|
1213
|
+
aliases: ["sonnet-5.5", "claude-sonnet-latest"],
|
|
1214
1214
|
deprecated: false,
|
|
1215
1215
|
isLocal: false,
|
|
1216
1216
|
releaseDate: "2026-09-28",
|
|
@@ -1374,7 +1374,7 @@ const LEGACY_MODEL_REGISTRY = {
|
|
|
1374
1374
|
translation: 8,
|
|
1375
1375
|
summarization: 8,
|
|
1376
1376
|
},
|
|
1377
|
-
// "claude-sonnet-latest" now belongs to
|
|
1377
|
+
// "claude-sonnet-latest" now belongs to CLAUDE_SONNET_5_5 — see the note
|
|
1378
1378
|
// on CLAUDE_OPUS_4_5's aliases.
|
|
1379
1379
|
aliases: ["claude-4.5-sonnet", "sonnet-4.5"],
|
|
1380
1380
|
deprecated: false,
|
|
@@ -2846,6 +2846,58 @@ export function resolveSamplingParams(provider, model, params, site) {
|
|
|
2846
2846
|
}
|
|
2847
2847
|
return {};
|
|
2848
2848
|
}
|
|
2849
|
+
/**
|
|
2850
|
+
* Claude models that answer a forced `tool_choice` (`any` or `tool`) with a
|
|
2851
|
+
* 400: "tool_choice: type "tool" and "any" are not supported for this model."
|
|
2852
|
+
* Sonnet 5.5 and Opus 5.5 were probed live on Vertex; the Fable 5.1 docs state
|
|
2853
|
+
* the same, and Mythos 5.1 shares Fable 5.1's capabilities. The same families
|
|
2854
|
+
* also refuse `thinking: {type: "disabled"}`.
|
|
2855
|
+
*/
|
|
2856
|
+
const FORCED_TOOL_CHOICE_REJECTING_FAMILIES = [
|
|
2857
|
+
/sonnet[-_.]?5[-_.]5(?![0-9])/i,
|
|
2858
|
+
/opus[-_.]?5[-_.]5(?![0-9])/i,
|
|
2859
|
+
/(?:fable|mythos)[-_.]?5[-_.]1(?![0-9])/i,
|
|
2860
|
+
];
|
|
2861
|
+
/**
|
|
2862
|
+
* Of those, the ones that accept `thinking: {type: "between_tools"}` as their
|
|
2863
|
+
* lowest setting. The rest think adaptively, always on, and take no
|
|
2864
|
+
* `thinking` field in its place.
|
|
2865
|
+
*/
|
|
2866
|
+
const BETWEEN_TOOLS_THINKING_FAMILIES = [
|
|
2867
|
+
/sonnet[-_.]?5[-_.]5(?![0-9])/i,
|
|
2868
|
+
];
|
|
2869
|
+
function matchesClaudeFamily(families, model) {
|
|
2870
|
+
const normalizedModel = (model ?? "").toLowerCase();
|
|
2871
|
+
if (!normalizedModel) {
|
|
2872
|
+
return false;
|
|
2873
|
+
}
|
|
2874
|
+
const modelId = MODEL_ALIASES[normalizedModel] ?? normalizedModel;
|
|
2875
|
+
return families.some((family) => family.test(modelId) || family.test(normalizedModel));
|
|
2876
|
+
}
|
|
2877
|
+
/**
|
|
2878
|
+
* Whether a Claude model accepts a forced `tool_choice` (`any` or a named
|
|
2879
|
+
* `tool`). Unknown models default to accepting it.
|
|
2880
|
+
*/
|
|
2881
|
+
export function modelSupportsForcedToolChoice(model) {
|
|
2882
|
+
return !matchesClaudeFamily(FORCED_TOOL_CHOICE_REJECTING_FAMILIES, model);
|
|
2883
|
+
}
|
|
2884
|
+
/**
|
|
2885
|
+
* What to send instead of `thinking: {type: "disabled"}` to a Claude model.
|
|
2886
|
+
* `between_tools` is refused at `xhigh` and `max` effort, so those fall back
|
|
2887
|
+
* to adaptive thinking (`omit`).
|
|
2888
|
+
*/
|
|
2889
|
+
export function claudeDisabledThinkingReplacement(model, effort) {
|
|
2890
|
+
if (!matchesClaudeFamily(FORCED_TOOL_CHOICE_REJECTING_FAMILIES, model)) {
|
|
2891
|
+
return "keep";
|
|
2892
|
+
}
|
|
2893
|
+
const level = effort?.toLowerCase();
|
|
2894
|
+
if (matchesClaudeFamily(BETWEEN_TOOLS_THINKING_FAMILIES, model) &&
|
|
2895
|
+
level !== "xhigh" &&
|
|
2896
|
+
level !== "max") {
|
|
2897
|
+
return "between_tools";
|
|
2898
|
+
}
|
|
2899
|
+
return "omit";
|
|
2900
|
+
}
|
|
2849
2901
|
/**
|
|
2850
2902
|
* Get models by provider
|
|
2851
2903
|
*/
|
package/dist/neurolink.js
CHANGED
|
@@ -12956,6 +12956,7 @@ Current user's request: ${currentInput}`;
|
|
|
12956
12956
|
"gen_ai.operation.name": "decide",
|
|
12957
12957
|
"ai.provider": providerName,
|
|
12958
12958
|
"decision.question_count": questionCount,
|
|
12959
|
+
"decision.images.count": options.images?.length ?? 0,
|
|
12959
12960
|
},
|
|
12960
12961
|
}, async (otelSpan) => {
|
|
12961
12962
|
const { traceId, parentSpanId } = getActiveTraceContext();
|
|
@@ -12964,6 +12965,7 @@ Current user's request: ${currentInput}`;
|
|
|
12964
12965
|
"ai.provider": providerName,
|
|
12965
12966
|
"ai.model": options.model ?? "",
|
|
12966
12967
|
"decision.question_count": questionCount,
|
|
12968
|
+
"decision.images.count": options.images?.length ?? 0,
|
|
12967
12969
|
}, parentSpanId, traceId);
|
|
12968
12970
|
try {
|
|
12969
12971
|
// enableMCP: false — a decision model has no tools and never will.
|
|
@@ -12974,6 +12976,8 @@ Current user's request: ${currentInput}`;
|
|
|
12974
12976
|
const result = await provider.decide({
|
|
12975
12977
|
state: options.state,
|
|
12976
12978
|
questions: options.questions,
|
|
12979
|
+
images: options.images,
|
|
12980
|
+
video: options.video,
|
|
12977
12981
|
model: options.model,
|
|
12978
12982
|
signal: options.signal,
|
|
12979
12983
|
timeoutMs: options.timeoutMs,
|
|
@@ -12988,6 +12992,7 @@ Current user's request: ${currentInput}`;
|
|
|
12988
12992
|
otelSpan.setAttribute("gen_ai.usage.input_tokens", result.usage.inputTokens);
|
|
12989
12993
|
otelSpan.setAttribute("gen_ai.usage.output_tokens", result.usage.outputTokens);
|
|
12990
12994
|
otelSpan.setAttribute("decision.answer_count", answerCount);
|
|
12995
|
+
otelSpan.setAttribute("decision.media.bytes", result.mediaBytes ?? 0);
|
|
12991
12996
|
otelSpan.setAttribute("ai.cost.total", cost);
|
|
12992
12997
|
if (result.upstreamMs !== undefined) {
|
|
12993
12998
|
otelSpan.setAttribute("decision.upstream_ms", result.upstreamMs);
|
|
@@ -13008,6 +13013,7 @@ Current user's request: ${currentInput}`;
|
|
|
13008
13013
|
"ai.cost.output": 0,
|
|
13009
13014
|
"ai.cost.total": cost,
|
|
13010
13015
|
"decision.answer_count": answerCount,
|
|
13016
|
+
"decision.media.bytes": result.mediaBytes ?? 0,
|
|
13011
13017
|
"decision.latency_ms": result.latencyMs,
|
|
13012
13018
|
...(result.upstreamMs !== undefined
|
|
13013
13019
|
? { "decision.upstream_ms": result.upstreamMs }
|
|
@@ -126,7 +126,7 @@ export class AmazonSageMakerProvider extends BaseProvider {
|
|
|
126
126
|
options.disableInternalFallback === true;
|
|
127
127
|
// The native loop bypasses BaseProvider.executeGeneration, so the turn
|
|
128
128
|
// budget has to be composed here or it stops existing for this provider.
|
|
129
|
-
return this.runGenerateWithModelFallback(() => this.withTurnTimeout({ ...options, tools: mergedTools }, this.getDescriptorGenerateMs(), (timedOptions) => this.executeNativeGenerate(timedOptions)), callerOwnsFallback);
|
|
129
|
+
return this.runGenerateWithModelFallback(() => this.withTurnTimeout({ ...options, tools: mergedTools }, this.getDescriptorGenerateMs(), (timedOptions) => this.executeNativeGenerate(timedOptions)), callerOwnsFallback, options);
|
|
130
130
|
}
|
|
131
131
|
async executeNativeGenerate(options) {
|
|
132
132
|
const startTime = Date.now();
|