@juspay/neurolink 12.56.1 → 12.58.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 +7 -2
- package/README.md +90 -55
- package/dist/browser/neurolink.min.js +419 -416
- package/dist/cli/commands/decide.d.ts +4 -1
- package/dist/cli/commands/decide.js +6 -3
- package/dist/cli/commands/setup.d.ts +1 -1
- package/dist/cli/commands/setup.js +1 -1
- package/dist/cli/factories/commandFactory.js +1 -1
- package/dist/constants/enums.d.ts +8 -0
- package/dist/constants/enums.js +10 -0
- package/dist/core/factory.d.ts +7 -0
- package/dist/core/factory.js +20 -0
- package/dist/factories/providerDescriptors.js +103 -9
- package/dist/providers/catalog/index.generated.d.ts +1 -1
- package/dist/providers/catalog/index.generated.js +10 -1
- package/dist/providers/catalog/openai-decisions.json +75 -0
- package/dist/providers/catalog/provider-catalog.schema.json +56 -3
- package/dist/providers/catalog/schema.js +92 -11
- package/dist/providers/catalog/subq.json +59 -0
- package/dist/providers/configuredDecision.js +9 -14
- package/dist/providers/decide/dialects/index.js +2 -0
- package/dist/providers/decide/dialects/openaiDecisions.d.ts +2 -0
- package/dist/providers/decide/dialects/openaiDecisions.js +228 -0
- package/dist/providers/decide/dialects/systemOne.js +17 -0
- package/dist/providers/systemOneDecision.js +6 -1
- package/dist/telemetry/attributes.d.ts +10 -3
- package/dist/telemetry/attributes.js +142 -10
- package/dist/telemetry/index.d.ts +1 -1
- package/dist/telemetry/index.js +1 -1
- package/dist/types/classifierRouter.d.ts +21 -9
- package/dist/types/cli.d.ts +4 -2
- package/dist/types/decision.d.ts +67 -3
- package/dist/types/providerCatalog.d.ts +7 -11
- package/dist/types/providerCatalog.generated.d.ts +2 -2
- package/dist/types/providers.d.ts +16 -1
- package/dist/utils/pricing.js +9 -0
- package/docs-site/static/search-index.json +105 -64
- package/package.json +3 -1
|
@@ -9,7 +9,10 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Credentials are env-only here, exactly as every other CLI command: the
|
|
11
11
|
* decision provider reads its own settings (TYPESAFE_API_KEY, LAYA_API_KEY and
|
|
12
|
-
* LAYA_BASE_URL, XOR_API_KEY and XOR_BASE_URL,
|
|
12
|
+
* LAYA_BASE_URL, XOR_API_KEY and XOR_BASE_URL, SUBQ_API_KEY, PERPLEXITY_API_KEY,
|
|
13
|
+
* CLOUDFLARE_API_KEY and CLOUDFLARE_ACCOUNT_ID, or OPENAI_API_KEY) itself. An
|
|
14
|
+
* OPENAI_API_KEY beside an OPENAI_BASE_URL, OPENAI_API_BASE or credentials.openai.baseURL that names a host
|
|
15
|
+
* other than https://api.openai.com is a gateway's and is not used by default.
|
|
13
16
|
*/
|
|
14
17
|
import type { CommandModule } from "yargs";
|
|
15
18
|
import type { CliDecideArgs } from "../../types/index.js";
|
|
@@ -9,7 +9,10 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Credentials are env-only here, exactly as every other CLI command: the
|
|
11
11
|
* decision provider reads its own settings (TYPESAFE_API_KEY, LAYA_API_KEY and
|
|
12
|
-
* LAYA_BASE_URL, XOR_API_KEY and XOR_BASE_URL,
|
|
12
|
+
* LAYA_BASE_URL, XOR_API_KEY and XOR_BASE_URL, SUBQ_API_KEY, PERPLEXITY_API_KEY,
|
|
13
|
+
* CLOUDFLARE_API_KEY and CLOUDFLARE_ACCOUNT_ID, or OPENAI_API_KEY) itself. An
|
|
14
|
+
* OPENAI_API_KEY beside an OPENAI_BASE_URL, OPENAI_API_BASE or credentials.openai.baseURL that names a host
|
|
15
|
+
* other than https://api.openai.com is a gateway's and is not used by default.
|
|
13
16
|
*/
|
|
14
17
|
import chalk from "chalk";
|
|
15
18
|
import ora from "ora";
|
|
@@ -191,7 +194,7 @@ export const decideCommand = {
|
|
|
191
194
|
type: "string",
|
|
192
195
|
array: true,
|
|
193
196
|
nargs: 1,
|
|
194
|
-
describe: "Image for the model to read: a file path or a data: URL. Repeat for several (XOR and
|
|
197
|
+
describe: "Image for the model to read: a file path or a data: URL. Repeat for several (XOR, Perplexity and OpenAI Decisions take up to 8, Cloudflare Clef up to 4)",
|
|
195
198
|
})
|
|
196
199
|
.option("video", {
|
|
197
200
|
type: "string",
|
|
@@ -217,7 +220,7 @@ export const decideCommand = {
|
|
|
217
220
|
})
|
|
218
221
|
.example('$0 decide "Refund request for a damaged item" --questions \'{"urgent":{"type":"boolean","instructions":"Is this urgent?"}}\'', "Ask a single yes/no question")
|
|
219
222
|
.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, Perplexity
|
|
223
|
+
.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, Perplexity, Cloudflare Clef and OpenAI Decisions read images)"),
|
|
221
224
|
handler: async (argv) => {
|
|
222
225
|
const outputFormat = argv.format ?? "text";
|
|
223
226
|
// --- Validate before any provider work ---------------------------------
|
|
@@ -13,7 +13,7 @@ import type { ProviderConfigOptions, SetupArgs } from "../../types/index.js";
|
|
|
13
13
|
* perplexity, sambanova, together-ai, xai — mistral is the 9th catalog
|
|
14
14
|
* provider but keeps its own handleMistralSetup() flow above, so it's
|
|
15
15
|
* excluded here), spread directly from the catalog JSON via
|
|
16
|
-
* buildCatalogConfigOptions(); decide-kind catalog entries (laya, xor) are
|
|
16
|
+
* buildCatalogConfigOptions(); decide-kind catalog entries (laya, xor, subq, openai-decisions) are
|
|
17
17
|
* spread the same way from getDecideCatalogEntries(). The hand rows listed
|
|
18
18
|
* first reuse the existing createXConfig() factories in providerConfig.ts; the
|
|
19
19
|
* remaining 5 (ollama, litellm, sagemaker, lm-studio, llamacpp) don't have a
|
|
@@ -150,7 +150,7 @@ const PROVIDERS = [
|
|
|
150
150
|
* perplexity, sambanova, together-ai, xai — mistral is the 9th catalog
|
|
151
151
|
* provider but keeps its own handleMistralSetup() flow above, so it's
|
|
152
152
|
* excluded here), spread directly from the catalog JSON via
|
|
153
|
-
* buildCatalogConfigOptions(); decide-kind catalog entries (laya, xor) are
|
|
153
|
+
* buildCatalogConfigOptions(); decide-kind catalog entries (laya, xor, subq, openai-decisions) are
|
|
154
154
|
* spread the same way from getDecideCatalogEntries(). The hand rows listed
|
|
155
155
|
* first reuse the existing createXConfig() factories in providerConfig.ts; the
|
|
156
156
|
* remaining 5 (ollama, litellm, sagemaker, lm-studio, llamacpp) don't have a
|
|
@@ -649,7 +649,7 @@ export class CLICommandFactory {
|
|
|
649
649
|
},
|
|
650
650
|
classifierStrategy: {
|
|
651
651
|
type: "string",
|
|
652
|
-
description: "Classifier strategy: 'auto' (default — 'jev' when a decision provider is configured, such as TYPESAFE_API_KEY, LAYA_API_KEY with LAYA_BASE_URL, XOR_API_KEY with XOR_BASE_URL, PERPLEXITY_API_KEY,
|
|
652
|
+
description: "Classifier strategy: 'auto' (default — 'jev' when a decision provider is configured, such as TYPESAFE_API_KEY, LAYA_API_KEY with LAYA_BASE_URL, XOR_API_KEY with XOR_BASE_URL, SUBQ_API_KEY, PERPLEXITY_API_KEY, CLOUDFLARE_API_KEY with CLOUDFLARE_ACCOUNT_ID, or OPENAI_API_KEY unless OPENAI_BASE_URL, OPENAI_API_BASE or credentials.openai.baseURL names a host other than https://api.openai.com, else 'heuristic'), 'heuristic' (no LLM), 'llm' (a cheap model picks per prompt), or 'jev' (a decision model — whichever decision provider is configured: TypeSafe Jev, Laya, XOR, SubQ, Perplexity, Cloudflare Clef or OpenAI Decisions — with the confidence it reports).",
|
|
653
653
|
choices: ["auto", "heuristic", "llm", "jev"],
|
|
654
654
|
alias: "classifier-strategy",
|
|
655
655
|
},
|
|
@@ -73,6 +73,7 @@ export declare enum AIProviderName {
|
|
|
73
73
|
NEBIUS = "nebius",
|
|
74
74
|
NEURALWATT = "neuralwatt",
|
|
75
75
|
NOVITA = "novita",
|
|
76
|
+
OPENAI_DECISIONS = "openai-decisions",
|
|
76
77
|
OVHCLOUD = "ovhcloud",
|
|
77
78
|
PARASAIL = "parasail",
|
|
78
79
|
PARETO_INFERENCE = "pareto-inference",
|
|
@@ -89,6 +90,7 @@ export declare enum AIProviderName {
|
|
|
89
90
|
STACKIT = "stackit",
|
|
90
91
|
STEPFUN = "stepfun",
|
|
91
92
|
SUBCONSCIOUS = "subconscious",
|
|
93
|
+
SUBQ = "subq",
|
|
92
94
|
SYNTHETIC = "synthetic",
|
|
93
95
|
TELNYX = "telnyx",
|
|
94
96
|
THINKING_MACHINES = "thinking-machines",
|
|
@@ -1475,6 +1477,9 @@ export declare enum NovitaModels {
|
|
|
1475
1477
|
QWEN_QWEN_2_5_72B_INSTRUCT = "qwen/qwen-2.5-72b-instruct",
|
|
1476
1478
|
GOOGLE_GEMMA_3_27B_IT = "google/gemma-3-27b-it"
|
|
1477
1479
|
}
|
|
1480
|
+
export declare enum OpenAIDecisionsModels {
|
|
1481
|
+
GPT_6_LUNA = "gpt-6-luna"
|
|
1482
|
+
}
|
|
1478
1483
|
export declare enum OvhcloudModels {
|
|
1479
1484
|
GPT_OSS_120B = "gpt-oss-120b",
|
|
1480
1485
|
GPT_OSS_20B = "gpt-oss-20b",
|
|
@@ -1611,6 +1616,9 @@ export declare enum SubconsciousModels {
|
|
|
1611
1616
|
SUBCONSCIOUS_DEEPSEEK_V4_1_FLASH_MARATHON = "subconscious/deepseek-v4.1-flash-marathon",
|
|
1612
1617
|
SUBCONSCIOUS_TIM_QWEN3_6_27B = "subconscious/tim-qwen3.6-27b"
|
|
1613
1618
|
}
|
|
1619
|
+
export declare enum SubqModels {
|
|
1620
|
+
SUBQ_SNAP_PREVIEW = "subq-snap-preview"
|
|
1621
|
+
}
|
|
1614
1622
|
export declare enum SyntheticModels {
|
|
1615
1623
|
SYN_LARGE_TEXT = "syn:large:text",
|
|
1616
1624
|
SYN_SMALL_TEXT = "syn:small:text",
|
package/dist/constants/enums.js
CHANGED
|
@@ -78,6 +78,7 @@ export var AIProviderName;
|
|
|
78
78
|
AIProviderName["NEBIUS"] = "nebius";
|
|
79
79
|
AIProviderName["NEURALWATT"] = "neuralwatt";
|
|
80
80
|
AIProviderName["NOVITA"] = "novita";
|
|
81
|
+
AIProviderName["OPENAI_DECISIONS"] = "openai-decisions";
|
|
81
82
|
AIProviderName["OVHCLOUD"] = "ovhcloud";
|
|
82
83
|
AIProviderName["PARASAIL"] = "parasail";
|
|
83
84
|
AIProviderName["PARETO_INFERENCE"] = "pareto-inference";
|
|
@@ -94,6 +95,7 @@ export var AIProviderName;
|
|
|
94
95
|
AIProviderName["STACKIT"] = "stackit";
|
|
95
96
|
AIProviderName["STEPFUN"] = "stepfun";
|
|
96
97
|
AIProviderName["SUBCONSCIOUS"] = "subconscious";
|
|
98
|
+
AIProviderName["SUBQ"] = "subq";
|
|
97
99
|
AIProviderName["SYNTHETIC"] = "synthetic";
|
|
98
100
|
AIProviderName["TELNYX"] = "telnyx";
|
|
99
101
|
AIProviderName["THINKING_MACHINES"] = "thinking-machines";
|
|
@@ -1768,6 +1770,10 @@ export var NovitaModels;
|
|
|
1768
1770
|
NovitaModels["QWEN_QWEN_2_5_72B_INSTRUCT"] = "qwen/qwen-2.5-72b-instruct";
|
|
1769
1771
|
NovitaModels["GOOGLE_GEMMA_3_27B_IT"] = "google/gemma-3-27b-it";
|
|
1770
1772
|
})(NovitaModels || (NovitaModels = {}));
|
|
1773
|
+
export var OpenAIDecisionsModels;
|
|
1774
|
+
(function (OpenAIDecisionsModels) {
|
|
1775
|
+
OpenAIDecisionsModels["GPT_6_LUNA"] = "gpt-6-luna";
|
|
1776
|
+
})(OpenAIDecisionsModels || (OpenAIDecisionsModels = {}));
|
|
1771
1777
|
export var OvhcloudModels;
|
|
1772
1778
|
(function (OvhcloudModels) {
|
|
1773
1779
|
OvhcloudModels["GPT_OSS_120B"] = "gpt-oss-120b";
|
|
@@ -1920,6 +1926,10 @@ export var SubconsciousModels;
|
|
|
1920
1926
|
SubconsciousModels["SUBCONSCIOUS_DEEPSEEK_V4_1_FLASH_MARATHON"] = "subconscious/deepseek-v4.1-flash-marathon";
|
|
1921
1927
|
SubconsciousModels["SUBCONSCIOUS_TIM_QWEN3_6_27B"] = "subconscious/tim-qwen3.6-27b";
|
|
1922
1928
|
})(SubconsciousModels || (SubconsciousModels = {}));
|
|
1929
|
+
export var SubqModels;
|
|
1930
|
+
(function (SubqModels) {
|
|
1931
|
+
SubqModels["SUBQ_SNAP_PREVIEW"] = "subq-snap-preview";
|
|
1932
|
+
})(SubqModels || (SubqModels = {}));
|
|
1923
1933
|
export var SyntheticModels;
|
|
1924
1934
|
(function (SyntheticModels) {
|
|
1925
1935
|
SyntheticModels["SYN_LARGE_TEXT"] = "syn:large:text";
|
package/dist/core/factory.d.ts
CHANGED
|
@@ -14,6 +14,13 @@ export declare class AIProviderFactory {
|
|
|
14
14
|
* Prevents hanging on non-responsive endpoints
|
|
15
15
|
*/
|
|
16
16
|
private static initializeDynamicProviderWithTimeout;
|
|
17
|
+
/**
|
|
18
|
+
* A provider whose descriptor declares `inferenceKinds` of exactly `["decide"]`
|
|
19
|
+
* names its own model (its registered default reads its own variable), so a
|
|
20
|
+
* chat provider's model variable has no claim on it. Exactly `["decide"]`: a
|
|
21
|
+
* provider that also serves text keeps its chat-model fallbacks.
|
|
22
|
+
*/
|
|
23
|
+
private static isDecisionOnly;
|
|
17
24
|
private static resolveModelFromEnvironment;
|
|
18
25
|
private static resolveDynamicModelName;
|
|
19
26
|
private static createResolvedProvider;
|
package/dist/core/factory.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { SpanKind, SpanStatusCode } from "@opentelemetry/api";
|
|
4
4
|
import { tracers } from "../telemetry/tracers.js";
|
|
5
5
|
import { ProviderFactory } from "../factories/providerFactory.js";
|
|
6
|
+
import { PROVIDER_ALIAS_INDEX, PROVIDER_DESCRIPTORS_BY_NAME, } from "../factories/providerDescriptors.js";
|
|
6
7
|
import { ProviderRegistry } from "../factories/providerRegistry.js";
|
|
7
8
|
import { getBestProvider } from "../utils/providerUtils.js";
|
|
8
9
|
import { logger } from "../utils/logger.js";
|
|
@@ -47,11 +48,30 @@ export class AIProviderFactory {
|
|
|
47
48
|
// This ensures the factory continues to work even if dynamic models fail
|
|
48
49
|
}
|
|
49
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* A provider whose descriptor declares `inferenceKinds` of exactly `["decide"]`
|
|
53
|
+
* names its own model (its registered default reads its own variable), so a
|
|
54
|
+
* chat provider's model variable has no claim on it. Exactly `["decide"]`: a
|
|
55
|
+
* provider that also serves text keeps its chat-model fallbacks.
|
|
56
|
+
*/
|
|
57
|
+
static isDecisionOnly(providerName) {
|
|
58
|
+
const canonical = PROVIDER_ALIAS_INDEX.get(providerName.toLowerCase());
|
|
59
|
+
const kinds = canonical === undefined
|
|
60
|
+
? undefined
|
|
61
|
+
: PROVIDER_DESCRIPTORS_BY_NAME.get(canonical)?.inferenceKinds;
|
|
62
|
+
return kinds?.length === 1 && kinds[0] === "decide";
|
|
63
|
+
}
|
|
50
64
|
static resolveModelFromEnvironment(providerName, modelName, functionTag) {
|
|
51
65
|
if (modelName && modelName !== "default") {
|
|
52
66
|
logger.debug(`[${functionTag}] Skipping environment variable check - explicit model provided: ${modelName}`);
|
|
53
67
|
return modelName;
|
|
54
68
|
}
|
|
69
|
+
// The group below is chosen by `includes`, so "openai-decisions" would be
|
|
70
|
+
// handed OPENAI_MODEL, the chat provider's variable, ahead of its own.
|
|
71
|
+
if (this.isDecisionOnly(providerName)) {
|
|
72
|
+
logger.debug(`[${functionTag}] Skipping chat-model environment variables - ${providerName} serves decisions only and reads its own model setting`);
|
|
73
|
+
return modelName;
|
|
74
|
+
}
|
|
55
75
|
logger.debug(`[${functionTag}] Checking environment variables for provider: ${providerName}`);
|
|
56
76
|
const normalizedProvider = providerName.toLowerCase();
|
|
57
77
|
const envConfigs = [
|
|
@@ -468,22 +468,29 @@ const HAND_DESCRIPTORS = [
|
|
|
468
468
|
setupUrl: "https://console.typesafe.ai/keys",
|
|
469
469
|
},
|
|
470
470
|
// Decision precedence is decideOrder, not position: typesafe 1, laya 2 and
|
|
471
|
-
// xor 3 (from catalog/laya.json and catalog/xor.json),
|
|
472
|
-
// cloudflare-clef
|
|
471
|
+
// xor 3 (from catalog/laya.json and catalog/xor.json), subq 4 (from
|
|
472
|
+
// catalog/subq.json), perplexity-decider 5, cloudflare-clef 6 and
|
|
473
|
+
// openai-decisions 7 (catalog/openai-decisions.json).
|
|
473
474
|
// resolveDefaultDecisionProvider() returns the first configured
|
|
474
475
|
// DECISION_PROVIDERS entry in that order, so a host configured for several
|
|
475
476
|
// keeps Jev for every built-in consumer and reaches the others only by
|
|
476
477
|
// naming them. Renumbering silently changes which model routes, compacts
|
|
477
478
|
// and plans for every such host.
|
|
479
|
+
// perplexity-decider, cloudflare-clef and openai-decisions come last because
|
|
480
|
+
// each shares its environment key with a text provider: a key set only for
|
|
481
|
+
// that text provider must not displace a decision provider a host set up on
|
|
482
|
+
// purpose. subq is ahead of them only because SUBQ_API_KEY belongs to no other
|
|
483
|
+
// provider.
|
|
478
484
|
{
|
|
479
485
|
name: AIProviderName.PERPLEXITY_DECIDER,
|
|
480
486
|
aliases: [],
|
|
481
487
|
credentialsKey: "perplexityDecider",
|
|
482
488
|
envVars: {
|
|
483
489
|
// The same key the `perplexity` text provider reads, so an ambient
|
|
484
|
-
// PERPLEXITY_API_KEY configures this provider too. Its decideOrder (
|
|
485
|
-
// sits after XOR's (3), which
|
|
486
|
-
// decision provider a host
|
|
490
|
+
// PERPLEXITY_API_KEY configures this provider too. Its decideOrder (5)
|
|
491
|
+
// sits after XOR's (3) and SubQ's (4), which have keys of their own, which
|
|
492
|
+
// is what keeps that from displacing any other decision provider a host
|
|
493
|
+
// has configured.
|
|
487
494
|
apiKey: "PERPLEXITY_API_KEY",
|
|
488
495
|
// Optional: the API is hosted at a public endpoint, so a key alone is
|
|
489
496
|
// enough and there is no extraRequired.
|
|
@@ -495,7 +502,7 @@ const HAND_DESCRIPTORS = [
|
|
|
495
502
|
// declaration that keeps it out of every generation code path, and out of
|
|
496
503
|
// the way of the `perplexity` text provider.
|
|
497
504
|
inferenceKinds: ["decide"],
|
|
498
|
-
decideOrder:
|
|
505
|
+
decideOrder: 5,
|
|
499
506
|
toolSupport: "none",
|
|
500
507
|
localRuntime: false,
|
|
501
508
|
healthCheck: "env-only",
|
|
@@ -563,7 +570,7 @@ const HAND_DESCRIPTORS = [
|
|
|
563
570
|
// declaration that keeps it out of every generation code path, and out of
|
|
564
571
|
// the way of the `cloudflare` text provider.
|
|
565
572
|
inferenceKinds: ["decide"],
|
|
566
|
-
decideOrder:
|
|
573
|
+
decideOrder: 6,
|
|
567
574
|
toolSupport: "none",
|
|
568
575
|
localRuntime: false,
|
|
569
576
|
healthCheck: "env-only",
|
|
@@ -722,6 +729,19 @@ export function buildDecideDescriptor(entry) {
|
|
|
722
729
|
defaultModel: entry.models.default,
|
|
723
730
|
inferenceKinds: ["decide"],
|
|
724
731
|
decideOrder: entry.decide.order,
|
|
732
|
+
...(entry.decide.ambientKeyGuard
|
|
733
|
+
? {
|
|
734
|
+
ambientKeyGuard: {
|
|
735
|
+
baseURLEnvVars: [...entry.decide.ambientKeyGuard.baseURLEnvVars],
|
|
736
|
+
trustedOrigins: [...entry.decide.ambientKeyGuard.trustedOrigins],
|
|
737
|
+
...(entry.decide.ambientKeyGuard.credentialBaseURLs
|
|
738
|
+
? {
|
|
739
|
+
credentialBaseURLs: entry.decide.ambientKeyGuard.credentialBaseURLs.map((credentialField) => ({ ...credentialField })),
|
|
740
|
+
}
|
|
741
|
+
: {}),
|
|
742
|
+
},
|
|
743
|
+
}
|
|
744
|
+
: {}),
|
|
725
745
|
toolSupport: "none",
|
|
726
746
|
localRuntime: false,
|
|
727
747
|
healthCheck: "env-only",
|
|
@@ -791,11 +811,61 @@ assertUniqueDecideOrder(PROVIDER_DESCRIPTORS);
|
|
|
791
811
|
* provider that declares nothing is a text provider, so it is not here.
|
|
792
812
|
*/
|
|
793
813
|
export const DECISION_PROVIDERS = orderDecisionProviders(PROVIDER_DESCRIPTORS);
|
|
814
|
+
/**
|
|
815
|
+
* The scheme, host and port of a base URL, lower-cased and without the default
|
|
816
|
+
* port, or undefined when the text is not an http(s) URL. The path, query and a
|
|
817
|
+
* trailing slash do not matter, and userinfo is not part of an origin, so
|
|
818
|
+
* `https://api.openai.com@evil.test/v1` reads as `https://evil.test`.
|
|
819
|
+
*/
|
|
820
|
+
function originOf(text) {
|
|
821
|
+
try {
|
|
822
|
+
const url = new URL(text.trim());
|
|
823
|
+
return url.protocol === "http:" || url.protocol === "https:"
|
|
824
|
+
? url.origin
|
|
825
|
+
: undefined;
|
|
826
|
+
}
|
|
827
|
+
catch {
|
|
828
|
+
return undefined;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
/**
|
|
832
|
+
* Whether the guard lets a key found in the environment stand. It does not
|
|
833
|
+
* when a listed base-URL variable, or a listed credentials field, is set to
|
|
834
|
+
* something other than a trusted origin; an unparseable value is untrusted, a
|
|
835
|
+
* blank or absent one is unset. A credentials field that is not text cannot be
|
|
836
|
+
* read as a URL, so it is untrusted too.
|
|
837
|
+
*/
|
|
838
|
+
function ambientKeyTrusted(guard, credentials) {
|
|
839
|
+
const trusted = new Set(guard.trustedOrigins
|
|
840
|
+
.map(originOf)
|
|
841
|
+
.filter((origin) => origin !== undefined));
|
|
842
|
+
const standsFor = (value) => {
|
|
843
|
+
if (value === undefined || value === null) {
|
|
844
|
+
return true;
|
|
845
|
+
}
|
|
846
|
+
if (typeof value !== "string") {
|
|
847
|
+
return false;
|
|
848
|
+
}
|
|
849
|
+
if (value.trim() === "") {
|
|
850
|
+
return true;
|
|
851
|
+
}
|
|
852
|
+
const origin = originOf(value);
|
|
853
|
+
return origin !== undefined && trusted.has(origin);
|
|
854
|
+
};
|
|
855
|
+
const slices = credentials;
|
|
856
|
+
return (guard.baseURLEnvVars.every((name) => standsFor(process.env[name])) &&
|
|
857
|
+
(guard.credentialBaseURLs ?? []).every(({ key, field }) => standsFor(slices?.[key]?.[field])));
|
|
858
|
+
}
|
|
794
859
|
/**
|
|
795
860
|
* Whether a decision provider is fully configured: its key and every
|
|
796
861
|
* extraRequired variable are set, each either in the environment or in
|
|
797
862
|
* `credentials` — the same config a caller passes to
|
|
798
863
|
* `new NeuroLink({ credentials })` or per call. Whitespace counts as unset.
|
|
864
|
+
*
|
|
865
|
+
* A key found only in the environment does not count while the descriptor's
|
|
866
|
+
* `ambientKeyGuard` finds that it belongs to something else, unless the host
|
|
867
|
+
* routed the provider on purpose with its own base-URL variable or a
|
|
868
|
+
* `credentials` base URL. A key in `credentials` always counts.
|
|
799
869
|
*/
|
|
800
870
|
function isDecisionProviderConfigured(descriptor, credentials) {
|
|
801
871
|
const isSet = (value) => typeof value === "string" && value.trim() !== "";
|
|
@@ -803,8 +873,13 @@ function isDecisionProviderConfigured(descriptor, credentials) {
|
|
|
803
873
|
const slice = descriptor.credentialsKey
|
|
804
874
|
? credentials?.[descriptor.credentialsKey]
|
|
805
875
|
: undefined;
|
|
876
|
+
const routedOnPurpose = inEnv(descriptor.envVars.baseURL) || isSet(slice?.baseURL);
|
|
877
|
+
const envKeyStands = descriptor.ambientKeyGuard === undefined ||
|
|
878
|
+
routedOnPurpose ||
|
|
879
|
+
ambientKeyTrusted(descriptor.ambientKeyGuard, credentials);
|
|
806
880
|
// `gatewayApiKey` is TypeSafe's config form of AI_GATEWAY_API_KEY.
|
|
807
|
-
const hasKey =
|
|
881
|
+
const hasKey = (envKeyStands &&
|
|
882
|
+
[descriptor.envVars.apiKey, ...(descriptor.envVars.fallbacks ?? [])].some(inEnv)) ||
|
|
808
883
|
isSet(slice?.apiKey) ||
|
|
809
884
|
isSet(slice?.gatewayApiKey);
|
|
810
885
|
// A required base URL can also come from credentials.<key>.baseURL, and any
|
|
@@ -840,6 +915,12 @@ export function listMediaDecisionProviders(needs) {
|
|
|
840
915
|
return media !== undefined && (!needs?.video || media.video);
|
|
841
916
|
}).map((descriptor) => descriptor.name);
|
|
842
917
|
}
|
|
918
|
+
/** "a", "a or b", "a, b or c". */
|
|
919
|
+
function listOr(items) {
|
|
920
|
+
return items.length < 2
|
|
921
|
+
? items.join("")
|
|
922
|
+
: `${items.slice(0, -1).join(", ")} or ${items[items.length - 1]}`;
|
|
923
|
+
}
|
|
843
924
|
/**
|
|
844
925
|
* The variables that would configure each decision provider, one clause per
|
|
845
926
|
* provider in precedence order, e.g. "TYPESAFE_API_KEY or AI_GATEWAY_API_KEY
|
|
@@ -854,6 +935,19 @@ export function describeDecisionProviderKeys() {
|
|
|
854
935
|
...(descriptor.envVars.fallbacks ?? []),
|
|
855
936
|
].filter((name) => typeof name === "string" && name !== "");
|
|
856
937
|
const required = descriptor.envVars.extraRequired ?? [];
|
|
857
|
-
|
|
938
|
+
const guard = descriptor.ambientKeyGuard;
|
|
939
|
+
const unlessRouted = descriptor.envVars.baseURL
|
|
940
|
+
? `, unless ${descriptor.envVars.baseURL} is set`
|
|
941
|
+
: "";
|
|
942
|
+
const named = guard
|
|
943
|
+
? [
|
|
944
|
+
...guard.baseURLEnvVars,
|
|
945
|
+
...(guard.credentialBaseURLs ?? []).map(({ key, field }) => `credentials.${key}.${field}`),
|
|
946
|
+
]
|
|
947
|
+
: [];
|
|
948
|
+
const caveat = guard
|
|
949
|
+
? ` (ignored while ${listOr(named)} names a host outside ${listOr(guard.trustedOrigins)}${unlessRouted})`
|
|
950
|
+
: "";
|
|
951
|
+
return `${[keys.join(" or "), ...required].join(" and ")} for ${descriptor.name}${caveat}`;
|
|
858
952
|
}).join(", or ");
|
|
859
953
|
}
|
|
@@ -2,4 +2,4 @@ import type { DecideCatalogJson, ProviderCatalogJson } from "../../types/index.j
|
|
|
2
2
|
export declare const CATALOG_JSON_ENTRIES: ProviderCatalogJson[];
|
|
3
3
|
export declare const CATALOG_PROVIDER_IDS: readonly ["a2agent", "abliteration", "above-dev", "ai21", "aiand", "aihubmix", "aionlabs", "ambient", "api-route", "arcee", "atlas-cloud", "avian-io", "baidu-qianfan", "baseten", "bee-heossi", "byteplus-modelark", "bytez", "cerebras", "charm-hyper", "chutes", "cloudflare", "crusoe", "dashscope", "deepinfra", "deepseek", "empiriolabs", "featherless-ai", "fireworks", "friendli", "gmicloud", "gradientai", "groq", "hetzner-inference", "huggingface", "inception-labs", "inceptron", "inco", "inference-net", "io-intelligence", "koscompute", "lemonfox-ai", "lilac", "llmtech", "longcat", "mancer", "meta-model-api", "minimax", "mistral", "moark", "modelscope", "moonshot-ai", "morph", "naver-clova-studio", "nebius", "neuralwatt", "novita", "ovhcloud", "parasail", "pareto-inference", "perplexity", "poolside", "prime-intellect", "reka", "runinfra", "sakana-ai", "sambanova", "sarvam", "scaleway", "siliconflow", "stackit", "stepfun", "subconscious", "synthetic", "telnyx", "thinking-machines", "tinfoil", "together-ai", "umans-ai", "upstage", "venice-ai", "vispark", "vultr-inference", "wafer", "wandb-inference", "xai", "xiaomi-mimo", "z-ai"];
|
|
4
4
|
export declare const DECIDE_CATALOG_JSON_ENTRIES: DecideCatalogJson[];
|
|
5
|
-
export declare const DECIDE_CATALOG_PROVIDER_IDS: readonly ["laya", "xor"];
|
|
5
|
+
export declare const DECIDE_CATALOG_PROVIDER_IDS: readonly ["laya", "openai-decisions", "subq", "xor"];
|
|
@@ -57,6 +57,7 @@ import naverClovaStudioJson from "./naver-clova-studio.json" with { type: "json"
|
|
|
57
57
|
import nebiusJson from "./nebius.json" with { type: "json" };
|
|
58
58
|
import neuralwattJson from "./neuralwatt.json" with { type: "json" };
|
|
59
59
|
import novitaJson from "./novita.json" with { type: "json" };
|
|
60
|
+
import openaiDecisionsJson from "./openai-decisions.json" with { type: "json" };
|
|
60
61
|
import ovhcloudJson from "./ovhcloud.json" with { type: "json" };
|
|
61
62
|
import parasailJson from "./parasail.json" with { type: "json" };
|
|
62
63
|
import paretoInferenceJson from "./pareto-inference.json" with { type: "json" };
|
|
@@ -73,6 +74,7 @@ import siliconflowJson from "./siliconflow.json" with { type: "json" };
|
|
|
73
74
|
import stackitJson from "./stackit.json" with { type: "json" };
|
|
74
75
|
import stepfunJson from "./stepfun.json" with { type: "json" };
|
|
75
76
|
import subconsciousJson from "./subconscious.json" with { type: "json" };
|
|
77
|
+
import subqJson from "./subq.json" with { type: "json" };
|
|
76
78
|
import syntheticJson from "./synthetic.json" with { type: "json" };
|
|
77
79
|
import telnyxJson from "./telnyx.json" with { type: "json" };
|
|
78
80
|
import thinkingMachinesJson from "./thinking-machines.json" with { type: "json" };
|
|
@@ -277,6 +279,13 @@ export const CATALOG_PROVIDER_IDS = [
|
|
|
277
279
|
];
|
|
278
280
|
export const DECIDE_CATALOG_JSON_ENTRIES = [
|
|
279
281
|
layaJson,
|
|
282
|
+
openaiDecisionsJson,
|
|
283
|
+
subqJson,
|
|
280
284
|
xorJson,
|
|
281
285
|
].map(asDecideEntry);
|
|
282
|
-
export const DECIDE_CATALOG_PROVIDER_IDS = [
|
|
286
|
+
export const DECIDE_CATALOG_PROVIDER_IDS = [
|
|
287
|
+
"laya",
|
|
288
|
+
"openai-decisions",
|
|
289
|
+
"subq",
|
|
290
|
+
"xor",
|
|
291
|
+
];
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./provider-catalog.schema.json",
|
|
3
|
+
"id": "openai-decisions",
|
|
4
|
+
"displayName": "OpenAI Decisions",
|
|
5
|
+
"enumTypeName": "OpenAIDecisionsModels",
|
|
6
|
+
"aliases": [],
|
|
7
|
+
"inferenceKinds": ["decide"],
|
|
8
|
+
"wire": {
|
|
9
|
+
"baseURL": "https://api.openai.com",
|
|
10
|
+
"envOverrides": { "apiKey": "OPENAI_API_KEY" }
|
|
11
|
+
},
|
|
12
|
+
"models": {
|
|
13
|
+
"default": "gpt-6-luna",
|
|
14
|
+
"topModels": ["gpt-6-luna"],
|
|
15
|
+
"catalog": {
|
|
16
|
+
"gpt-6-luna": {
|
|
17
|
+
"contextWindow": 1050000,
|
|
18
|
+
"maxOutputTokens": 256,
|
|
19
|
+
"vision": true,
|
|
20
|
+
"status": "preview",
|
|
21
|
+
"description": "GPT-6 Luna on OpenAI's Decisions API (public beta): text and images"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"timeouts": { "decideMs": 10000 },
|
|
26
|
+
"decisionLimits": {
|
|
27
|
+
"maxStateTokens": 922000,
|
|
28
|
+
"advisory": true,
|
|
29
|
+
"media": { "maxImages": 8, "video": false, "maxRequestBytes": 33554432 }
|
|
30
|
+
},
|
|
31
|
+
"decide": {
|
|
32
|
+
"dialect": "openai-decisions",
|
|
33
|
+
"order": 7,
|
|
34
|
+
"baseURLNormalize": ["strip-trailing-slash", "strip-v1-suffix"],
|
|
35
|
+
"baseURLShape": "origin",
|
|
36
|
+
"ambientKeyGuard": {
|
|
37
|
+
"baseURLEnvVars": ["OPENAI_BASE_URL", "OPENAI_API_BASE"],
|
|
38
|
+
"trustedOrigins": ["https://api.openai.com"],
|
|
39
|
+
"credentialBaseURLs": [{ "key": "openai", "field": "baseURL" }]
|
|
40
|
+
},
|
|
41
|
+
"endpoint": "{baseURL}/v1/decisions",
|
|
42
|
+
"model": { "placement": "body" },
|
|
43
|
+
"requestIdHeaders": ["x-request-id"],
|
|
44
|
+
"errors": {
|
|
45
|
+
"rules": [
|
|
46
|
+
{ "status": 401, "kind": "authentication" },
|
|
47
|
+
{ "status": 429, "kind": "rate_limit", "retryable": false },
|
|
48
|
+
{ "status": 503, "kind": "overloaded" },
|
|
49
|
+
{ "statusMin": 500, "kind": "server" }
|
|
50
|
+
],
|
|
51
|
+
"default": "invalid_request",
|
|
52
|
+
"bodyShapes": [{ "path": "error.message" }]
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"setup": {
|
|
56
|
+
"url": "https://platform.openai.com/api-keys",
|
|
57
|
+
"apiKeyFormat": null,
|
|
58
|
+
"billingPolicy": "no-free-tier",
|
|
59
|
+
"description": "OpenAI API key (the same OPENAI_API_KEY the openai chat provider reads)",
|
|
60
|
+
"instructions": [
|
|
61
|
+
"1. Create an API key at https://platform.openai.com/api-keys",
|
|
62
|
+
"2. Set {apiKeyEnvVar}; it is shared with the openai chat provider",
|
|
63
|
+
"3. With no other decision provider configured, {apiKeyEnvVar} alone makes openai-decisions the default decision provider (the last resort, after every other one, as PERPLEXITY_API_KEY does for perplexity-decider), unless OPENAI_BASE_URL, OPENAI_API_BASE or credentials.openai.baseURL names a host other than https://api.openai.com: the key is then taken to be a gateway's and is not used by default. To use it while another provider is configured, name it in a call (provider: \"openai-decisions\" or --provider openai-decisions), but only if the key is OpenAI's, because a named call sends the environment key to api.openai.com. Behind a gateway, route it on purpose with OPENAI_DECISIONS_BASE_URL or credentials.openaiDecisions, or do not use it",
|
|
64
|
+
"4. Optional: OPENAI_DECISIONS_BASE_URL (an origin) and OPENAI_DECISIONS_MODEL (default gpt-6-luna)"
|
|
65
|
+
]
|
|
66
|
+
},
|
|
67
|
+
"evidence": {
|
|
68
|
+
"rosterVerified": {
|
|
69
|
+
"date": "2026-10-07",
|
|
70
|
+
"method": "OpenAI's Decisions guide and gpt-6-luna model page saved 2026-10-07: gpt-6-luna is the only model the endpoint supports. Read from documentation; no live call"
|
|
71
|
+
},
|
|
72
|
+
"liveMatrix": null,
|
|
73
|
+
"addedInPR": "https://github.com/juspay/neurolink/pull/1996"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"type": "object",
|
|
65
65
|
"additionalProperties": false,
|
|
66
66
|
"required": ["maxImages", "video", "maxRequestBytes"],
|
|
67
|
-
"description": "What a request may carry besides text.
|
|
67
|
+
"description": "What a request may carry besides text. With dialect system-one it requires decide.media, which names the body keys the prepared media is written under; with openai-decisions it stands alone and video must be false.",
|
|
68
68
|
"properties": {
|
|
69
69
|
"maxImages": { "type": "integer", "minimum": 1 },
|
|
70
70
|
"video": {
|
|
@@ -102,7 +102,10 @@
|
|
|
102
102
|
"required": ["dialect", "order", "endpoint", "model", "errors"],
|
|
103
103
|
"description": "Decide entries only: how a decision provider is called and how its errors are classified.",
|
|
104
104
|
"properties": {
|
|
105
|
-
"dialect": {
|
|
105
|
+
"dialect": {
|
|
106
|
+
"enum": ["system-one", "openai-decisions"],
|
|
107
|
+
"description": "The wire family. openai-decisions places images in the request input itself, so it refuses decide.media, a video allowance and any model placement other than body (enforced by the zod schema)."
|
|
108
|
+
},
|
|
106
109
|
"order": {
|
|
107
110
|
"type": "integer",
|
|
108
111
|
"minimum": 1,
|
|
@@ -143,12 +146,62 @@
|
|
|
143
146
|
"type": "object",
|
|
144
147
|
"additionalProperties": false,
|
|
145
148
|
"required": ["imagesKey"],
|
|
146
|
-
"description": "Body keys prepared media is written under, after model, state and questions: imagesKey only when the request carries an image, videoKey only when it carries a video, and the two differ.
|
|
149
|
+
"description": "Body keys prepared media is written under, after model, state and questions: imagesKey only when the request carries an image, videoKey only when it carries a video, and the two differ. With dialect system-one it requires decisionLimits.media, and videoKey is required exactly when that allowance's video is true. Not valid with openai-decisions.",
|
|
147
150
|
"properties": {
|
|
148
151
|
"imagesKey": { "$ref": "#/definitions/decideMediaKey" },
|
|
149
152
|
"videoKey": { "$ref": "#/definitions/decideMediaKey" }
|
|
150
153
|
}
|
|
151
154
|
},
|
|
155
|
+
"ambientKeyGuard": {
|
|
156
|
+
"type": "object",
|
|
157
|
+
"additionalProperties": false,
|
|
158
|
+
"required": ["baseURLEnvVars", "trustedOrigins"],
|
|
159
|
+
"description": "Keeps a key found only in the environment from becoming the default decision provider while another host is named. The key is skipped for default resolution when any baseURLEnvVars variable, or any credentials field in credentialBaseURLs (read from the credentials the resolver is given: the instance's merged with the call's), holds a URL whose origin (scheme, host and port, compared case-insensitively; path and trailing slash ignored) is not in trustedOrigins; a value that does not parse as an http(s) URL counts as untrusted and a blank one as unset. It does not apply when the provider's own base-URL variable is set or credentials supply a key or a base URL, and never to a call that names the provider. The zod schema also refuses what JSON Schema cannot say: an origin without a valid host (such as 999.999.999.999) or with a port above 65535, a variable, an origin or a credentials field listed twice, and the provider's own base-URL variable in baseURLEnvVars.",
|
|
160
|
+
"properties": {
|
|
161
|
+
"baseURLEnvVars": {
|
|
162
|
+
"type": "array",
|
|
163
|
+
"minItems": 1,
|
|
164
|
+
"maxItems": 8,
|
|
165
|
+
"uniqueItems": true,
|
|
166
|
+
"items": {
|
|
167
|
+
"type": "string",
|
|
168
|
+
"maxLength": 64,
|
|
169
|
+
"pattern": "^[A-Z][A-Z0-9_]*$"
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
"trustedOrigins": {
|
|
173
|
+
"type": "array",
|
|
174
|
+
"minItems": 1,
|
|
175
|
+
"maxItems": 8,
|
|
176
|
+
"uniqueItems": true,
|
|
177
|
+
"items": {
|
|
178
|
+
"type": "string",
|
|
179
|
+
"maxLength": 100,
|
|
180
|
+
"pattern": "^https?://[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*(:[0-9]{1,5})?$"
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
"credentialBaseURLs": {
|
|
184
|
+
"type": "array",
|
|
185
|
+
"minItems": 1,
|
|
186
|
+
"maxItems": 8,
|
|
187
|
+
"uniqueItems": true,
|
|
188
|
+
"description": "Credentials fields that move the key's other users off the vendor's host, such as the chat provider's credentials.openai.baseURL. Omitted means only the environment is read.",
|
|
189
|
+
"items": {
|
|
190
|
+
"type": "object",
|
|
191
|
+
"additionalProperties": false,
|
|
192
|
+
"required": ["key", "field"],
|
|
193
|
+
"properties": {
|
|
194
|
+
"key": {
|
|
195
|
+
"type": "string",
|
|
196
|
+
"maxLength": 64,
|
|
197
|
+
"pattern": "^[a-zA-Z][a-zA-Z0-9]*$"
|
|
198
|
+
},
|
|
199
|
+
"field": { "enum": ["baseURL"] }
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
},
|
|
152
205
|
"requestIdHeaders": {
|
|
153
206
|
"type": "array",
|
|
154
207
|
"items": {
|