@juspay/neurolink 12.57.0 → 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 +75 -51
- 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 +4 -0
- package/dist/constants/enums.js +5 -0
- package/dist/core/factory.d.ts +7 -0
- package/dist/core/factory.js +20 -0
- package/dist/factories/providerDescriptors.js +96 -7
- package/dist/providers/catalog/index.generated.d.ts +1 -1
- package/dist/providers/catalog/index.generated.js +8 -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/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 +12 -1
- package/dist/utils/pricing.js +9 -0
- package/docs-site/static/search-index.json +84 -59
- 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, subq) 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, subq) 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, SUBQ_API_KEY, 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",
|
|
@@ -1476,6 +1477,9 @@ export declare enum NovitaModels {
|
|
|
1476
1477
|
QWEN_QWEN_2_5_72B_INSTRUCT = "qwen/qwen-2.5-72b-instruct",
|
|
1477
1478
|
GOOGLE_GEMMA_3_27B_IT = "google/gemma-3-27b-it"
|
|
1478
1479
|
}
|
|
1480
|
+
export declare enum OpenAIDecisionsModels {
|
|
1481
|
+
GPT_6_LUNA = "gpt-6-luna"
|
|
1482
|
+
}
|
|
1479
1483
|
export declare enum OvhcloudModels {
|
|
1480
1484
|
GPT_OSS_120B = "gpt-oss-120b",
|
|
1481
1485
|
GPT_OSS_20B = "gpt-oss-20b",
|
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";
|
|
@@ -1769,6 +1770,10 @@ export var NovitaModels;
|
|
|
1769
1770
|
NovitaModels["QWEN_QWEN_2_5_72B_INSTRUCT"] = "qwen/qwen-2.5-72b-instruct";
|
|
1770
1771
|
NovitaModels["GOOGLE_GEMMA_3_27B_IT"] = "google/gemma-3-27b-it";
|
|
1771
1772
|
})(NovitaModels || (NovitaModels = {}));
|
|
1773
|
+
export var OpenAIDecisionsModels;
|
|
1774
|
+
(function (OpenAIDecisionsModels) {
|
|
1775
|
+
OpenAIDecisionsModels["GPT_6_LUNA"] = "gpt-6-luna";
|
|
1776
|
+
})(OpenAIDecisionsModels || (OpenAIDecisionsModels = {}));
|
|
1772
1777
|
export var OvhcloudModels;
|
|
1773
1778
|
(function (OvhcloudModels) {
|
|
1774
1779
|
OvhcloudModels["GPT_OSS_120B"] = "gpt-oss-120b";
|
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 = [
|
|
@@ -469,16 +469,18 @@ const HAND_DESCRIPTORS = [
|
|
|
469
469
|
},
|
|
470
470
|
// Decision precedence is decideOrder, not position: typesafe 1, laya 2 and
|
|
471
471
|
// xor 3 (from catalog/laya.json and catalog/xor.json), subq 4 (from
|
|
472
|
-
// catalog/subq.json), perplexity-decider 5, cloudflare-clef 6
|
|
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.
|
|
478
|
-
// perplexity-decider
|
|
479
|
-
// environment key with a text provider: a key set only for
|
|
480
|
-
// must not displace a decision provider a host set up on
|
|
481
|
-
// ahead of them only because SUBQ_API_KEY belongs to no other
|
|
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.
|
|
482
484
|
{
|
|
483
485
|
name: AIProviderName.PERPLEXITY_DECIDER,
|
|
484
486
|
aliases: [],
|
|
@@ -727,6 +729,19 @@ export function buildDecideDescriptor(entry) {
|
|
|
727
729
|
defaultModel: entry.models.default,
|
|
728
730
|
inferenceKinds: ["decide"],
|
|
729
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
|
+
: {}),
|
|
730
745
|
toolSupport: "none",
|
|
731
746
|
localRuntime: false,
|
|
732
747
|
healthCheck: "env-only",
|
|
@@ -796,11 +811,61 @@ assertUniqueDecideOrder(PROVIDER_DESCRIPTORS);
|
|
|
796
811
|
* provider that declares nothing is a text provider, so it is not here.
|
|
797
812
|
*/
|
|
798
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
|
+
}
|
|
799
859
|
/**
|
|
800
860
|
* Whether a decision provider is fully configured: its key and every
|
|
801
861
|
* extraRequired variable are set, each either in the environment or in
|
|
802
862
|
* `credentials` — the same config a caller passes to
|
|
803
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.
|
|
804
869
|
*/
|
|
805
870
|
function isDecisionProviderConfigured(descriptor, credentials) {
|
|
806
871
|
const isSet = (value) => typeof value === "string" && value.trim() !== "";
|
|
@@ -808,8 +873,13 @@ function isDecisionProviderConfigured(descriptor, credentials) {
|
|
|
808
873
|
const slice = descriptor.credentialsKey
|
|
809
874
|
? credentials?.[descriptor.credentialsKey]
|
|
810
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);
|
|
811
880
|
// `gatewayApiKey` is TypeSafe's config form of AI_GATEWAY_API_KEY.
|
|
812
|
-
const hasKey =
|
|
881
|
+
const hasKey = (envKeyStands &&
|
|
882
|
+
[descriptor.envVars.apiKey, ...(descriptor.envVars.fallbacks ?? [])].some(inEnv)) ||
|
|
813
883
|
isSet(slice?.apiKey) ||
|
|
814
884
|
isSet(slice?.gatewayApiKey);
|
|
815
885
|
// A required base URL can also come from credentials.<key>.baseURL, and any
|
|
@@ -845,6 +915,12 @@ export function listMediaDecisionProviders(needs) {
|
|
|
845
915
|
return media !== undefined && (!needs?.video || media.video);
|
|
846
916
|
}).map((descriptor) => descriptor.name);
|
|
847
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
|
+
}
|
|
848
924
|
/**
|
|
849
925
|
* The variables that would configure each decision provider, one clause per
|
|
850
926
|
* provider in precedence order, e.g. "TYPESAFE_API_KEY or AI_GATEWAY_API_KEY
|
|
@@ -859,6 +935,19 @@ export function describeDecisionProviderKeys() {
|
|
|
859
935
|
...(descriptor.envVars.fallbacks ?? []),
|
|
860
936
|
].filter((name) => typeof name === "string" && name !== "");
|
|
861
937
|
const required = descriptor.envVars.extraRequired ?? [];
|
|
862
|
-
|
|
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}`;
|
|
863
952
|
}).join(", or ");
|
|
864
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", "subq", "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" };
|
|
@@ -278,7 +279,13 @@ export const CATALOG_PROVIDER_IDS = [
|
|
|
278
279
|
];
|
|
279
280
|
export const DECIDE_CATALOG_JSON_ENTRIES = [
|
|
280
281
|
layaJson,
|
|
282
|
+
openaiDecisionsJson,
|
|
281
283
|
subqJson,
|
|
282
284
|
xorJson,
|
|
283
285
|
].map(asDecideEntry);
|
|
284
|
-
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": {
|