@juspay/neurolink 12.25.5 → 12.26.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/CHANGELOG.md +2 -2
  2. package/dist/browser/neurolink.min.js +382 -382
  3. package/dist/cli/commands/decide.d.ts +2 -1
  4. package/dist/cli/commands/decide.js +4 -2
  5. package/dist/cli/commands/setup.js +2 -1
  6. package/dist/cli/factories/commandFactory.js +1 -1
  7. package/dist/cli/utils/classifierRouterFlags.js +1 -1
  8. package/dist/constants/enums.d.ts +15 -0
  9. package/dist/constants/enums.js +16 -0
  10. package/dist/core/baseProvider.js +2 -2
  11. package/dist/factories/providerDescriptors.d.ts +16 -7
  12. package/dist/factories/providerDescriptors.js +97 -15
  13. package/dist/factories/providerRegistry.js +9 -1
  14. package/dist/mcp/externalServerManager.js +16 -2
  15. package/dist/mcp/mcpCircuitBreaker.d.ts +24 -3
  16. package/dist/mcp/mcpCircuitBreaker.js +57 -18
  17. package/dist/mcp/toolDiscoveryService.js +20 -4
  18. package/dist/models/manifestRegistry.js +2 -0
  19. package/dist/models/manifests/laya.d.ts +12 -0
  20. package/dist/models/manifests/laya.js +54 -0
  21. package/dist/neurolink.js +8 -3
  22. package/dist/providers/laya.d.ts +40 -0
  23. package/dist/providers/laya.js +170 -0
  24. package/dist/providers/systemOneDecision.d.ts +102 -0
  25. package/dist/providers/systemOneDecision.js +443 -0
  26. package/dist/providers/typesafe.d.ts +32 -47
  27. package/dist/providers/typesafe.js +64 -386
  28. package/dist/routing/classifierRouter.d.ts +2 -2
  29. package/dist/routing/classifierRouter.js +10 -8
  30. package/dist/types/classifierRouter.d.ts +13 -3
  31. package/dist/types/cli.d.ts +3 -2
  32. package/dist/types/decision.d.ts +29 -0
  33. package/dist/types/externalMcp.d.ts +7 -0
  34. package/dist/types/providers.d.ts +20 -1
  35. package/dist/utils/modelChoices.js +21 -1
  36. package/dist/utils/providerConfig.d.ts +5 -0
  37. package/dist/utils/providerConfig.js +17 -0
  38. package/docs-site/static/search-index.json +24 -10
  39. package/package.json +4 -2
package/dist/neurolink.js CHANGED
@@ -34,7 +34,7 @@ import { buildToolRoutingCatalog, buildRoutingQueryFromHistory, resolveToolRouti
34
34
  import { ToolRoutingCache } from "./core/toolRoutingCache.js";
35
35
  import { DEFAULT_RECENT_TURNS, KnowledgeGroundingEngine, } from "./knowledge/index.js";
36
36
  import { AIProviderFactory } from "./core/factory.js";
37
- import { resolveDefaultDecisionProvider } from "./factories/providerDescriptors.js";
37
+ import { describeDecisionProviderKeys, resolveDefaultDecisionProvider, } from "./factories/providerDescriptors.js";
38
38
  import { resolveRequestKind } from "./core/resolveRequestKind.js";
39
39
  import { createToolEventPayload } from "./core/toolEvents.js";
40
40
  import { ProviderFactory } from "./factories/providerFactory.js";
@@ -966,6 +966,8 @@ export class NeuroLink {
966
966
  // throwing, so an absent or broken decision provider leaves routing
967
967
  // exactly as it was.
968
968
  decide: (decideOptions) => this.tryDecide(decideOptions),
969
+ // Read lazily: `this.credentials` is assigned later in the constructor.
970
+ hasDecisionProvider: () => resolveDefaultDecisionProvider(this.credentials) !== undefined,
969
971
  logger: {
970
972
  debug: (message, meta) => logger.debug(message, meta),
971
973
  warn: (message, meta) => logger.warn(message, meta),
@@ -12700,9 +12702,12 @@ Current user's request: ${currentInput}`;
12700
12702
  * @throws when no decision provider is configured, or the call fails
12701
12703
  */
12702
12704
  async decide(options) {
12703
- const providerName = options.provider ?? resolveDefaultDecisionProvider();
12705
+ // SDK config counts as much as the environment: credentials given to the
12706
+ // constructor or to this call can configure a provider on their own.
12707
+ const providerName = options.provider ??
12708
+ resolveDefaultDecisionProvider(this.resolveCredentials(options.credentials));
12704
12709
  if (!providerName) {
12705
- throw new Error("No decision provider is configured. Set TYPESAFE_API_KEY, or pass `provider` explicitly.");
12710
+ throw new Error(`No decision provider is configured. Set ${describeDecisionProviderKeys()} (in the environment or in \`credentials\`), or pass \`provider\` explicitly.`);
12706
12711
  }
12707
12712
  const questionCount = Object.keys(options.questions).length;
12708
12713
  // Observability parity with generate()/stream(): an OTEL span for the
@@ -0,0 +1,40 @@
1
+ import type { DecisionError, DecisionState, NeurolinkCredentials } from "../types/index.js";
2
+ import { SystemOneDecisionProvider } from "./systemOneDecision.js";
3
+ /**
4
+ * Laya Provider — the `decide` inference type only.
5
+ *
6
+ * Laya is Convai Innovations' open-weights "System One" decision model: the
7
+ * same typed `noul` / `choice` / `score` questions as TypeSafe's Jev, answered
8
+ * by an encoder in one forward pass, on the same wire. The request loop,
9
+ * retries, auth circuit breaker and answer parsing are shared with every
10
+ * decision provider in {@link SystemOneDecisionProvider}.
11
+ *
12
+ * Its encoders read far less than Jev: 1,024 tokens on the `typed-decisions`
13
+ * and `multilingual` checkpoints, 512 on `english`. The descriptor's
14
+ * `decisionLimits` refuses what does not fit rather than letting the server
15
+ * cut the state off silently.
16
+ *
17
+ * @see https://github.com/NandhaKishorM/laya
18
+ */
19
+ export declare class LayaProvider extends SystemOneDecisionProvider {
20
+ private readonly apiKey;
21
+ private readonly baseURL;
22
+ constructor(modelName?: string, sdk?: unknown, _region?: string, credentials?: NeurolinkCredentials["laya"]);
23
+ protected getDefaultModel(): string;
24
+ protected vendorLabel(): string;
25
+ protected vendorDisplayName(): string;
26
+ protected decisionApiKey(): string;
27
+ protected missingKeyMessage(): string;
28
+ protected missingConfigMessage(): string | undefined;
29
+ protected decisionEndpoint(): string;
30
+ protected decisionHeaders(): Record<string, string>;
31
+ /** `auto` means "let Laya's router choose", which it does when no model is named. */
32
+ protected buildDecisionBody(state: DecisionState, questions: Record<string, Record<string, unknown>>, model: string): Record<string, unknown>;
33
+ protected parseDecisionError(status: number, payload: unknown, requestId: string | undefined): DecisionError;
34
+ protected readRequestId(headers: Headers): string | undefined;
35
+ /**
36
+ * The checkpoint that actually answered is `routing.model` — the one Laya's
37
+ * router picked when the request said `auto`.
38
+ */
39
+ protected resolveResponseModel(payload: Record<string, unknown>, requestedModel: string): string;
40
+ }
@@ -0,0 +1,170 @@
1
+ import { LayaModels } from "../constants/enums.js";
2
+ import { logger } from "../utils/logger.js";
3
+ import { redactUrlForError } from "../utils/logSanitize.js";
4
+ import { getProviderModel } from "../utils/providerConfig.js";
5
+ import { describeValidationErrors, isRecord, SystemOneDecisionProvider, } from "./systemOneDecision.js";
6
+ /**
7
+ * LiteLLM echoes credentials back in its error texts, in more than one
8
+ * wording: a masked key plus its hash on a rejected key, the whole key when it
9
+ * does not look like a LiteLLM key, a key hash on a rate limit. None of it
10
+ * belongs in an error message or a log, whatever the wording, so the
11
+ * configured key, anything shaped like a LiteLLM key and any long hex run are
12
+ * all removed.
13
+ */
14
+ function redactCredentials(message, apiKey) {
15
+ const withoutKey = apiKey
16
+ ? message.split(apiKey).join("[redacted]")
17
+ : message;
18
+ return withoutKey
19
+ .replace(/\.?\s*Received API Key\s*=[\s\S]*$/, "")
20
+ .replace(/\bsk-[A-Za-z0-9._-]+/g, "[redacted]")
21
+ .replace(/\b[0-9a-f]{32,}\b/gi, "[redacted]")
22
+ .trim();
23
+ }
24
+ /**
25
+ * Normalise the two envelopes a Laya failure can arrive in.
26
+ *
27
+ * LiteLLM's own, `{"error":{"message","type","code"}}`, for anything the proxy
28
+ * rejects before Laya sees it (a bad key, a rate limit). FastAPI's, from
29
+ * Laya's server: `{"detail":"..."}` for its own checks (question types, the
30
+ * question cap) and `{"detail":[{loc,msg,input}]}` for request validation.
31
+ */
32
+ function parseLayaError(status, body, requestId) {
33
+ const retryable = status === 429 || status >= 500;
34
+ const kind = status === 401 || status === 403
35
+ ? "authentication"
36
+ : status === 413
37
+ ? "max_tokens_exceeded"
38
+ : status === 429
39
+ ? "rate_limit"
40
+ : status === 503
41
+ ? "overloaded"
42
+ : status >= 500
43
+ ? "server"
44
+ : "invalid_request";
45
+ const proxyError = isRecord(body) && isRecord(body.error) ? body.error : undefined;
46
+ if (proxyError &&
47
+ typeof proxyError.message === "string" &&
48
+ proxyError.message.length > 0) {
49
+ return {
50
+ kind,
51
+ message: proxyError.message,
52
+ status,
53
+ requestId,
54
+ retryable,
55
+ };
56
+ }
57
+ const detail = isRecord(body) ? body.detail : undefined;
58
+ if (Array.isArray(detail)) {
59
+ return {
60
+ kind: "invalid_request",
61
+ message: `Request failed validation — ${describeValidationErrors(detail)}`,
62
+ status,
63
+ requestId,
64
+ retryable: false,
65
+ };
66
+ }
67
+ if (typeof detail === "string" && detail.length > 0) {
68
+ return { kind, message: detail, status, requestId, retryable };
69
+ }
70
+ return {
71
+ kind,
72
+ message: `Laya request failed with HTTP ${status}`,
73
+ status,
74
+ requestId,
75
+ retryable,
76
+ };
77
+ }
78
+ /**
79
+ * Laya Provider — the `decide` inference type only.
80
+ *
81
+ * Laya is Convai Innovations' open-weights "System One" decision model: the
82
+ * same typed `noul` / `choice` / `score` questions as TypeSafe's Jev, answered
83
+ * by an encoder in one forward pass, on the same wire. The request loop,
84
+ * retries, auth circuit breaker and answer parsing are shared with every
85
+ * decision provider in {@link SystemOneDecisionProvider}.
86
+ *
87
+ * Its encoders read far less than Jev: 1,024 tokens on the `typed-decisions`
88
+ * and `multilingual` checkpoints, 512 on `english`. The descriptor's
89
+ * `decisionLimits` refuses what does not fit rather than letting the server
90
+ * cut the state off silently.
91
+ *
92
+ * @see https://github.com/NandhaKishorM/laya
93
+ */
94
+ export class LayaProvider extends SystemOneDecisionProvider {
95
+ apiKey;
96
+ baseURL;
97
+ constructor(modelName, sdk, _region, credentials) {
98
+ super(modelName, "laya", sdk);
99
+ this.apiKey =
100
+ credentials?.apiKey?.trim() || (process.env.LAYA_API_KEY?.trim() ?? "");
101
+ // No built-in endpoint: Laya is self-hosted or reached through a proxy
102
+ // (a Laya server, or a LiteLLM pass-through route to one), so the base URL
103
+ // comes only from config. `||`, not `??`, so a blank one counts as unset.
104
+ this.baseURL = (credentials?.baseURL?.trim() ||
105
+ process.env.LAYA_BASE_URL?.trim() ||
106
+ "").replace(/\/+$/, "");
107
+ // A self-hosted LAYA_BASE_URL can carry `user:pass@` or a `?token=`.
108
+ logger.debug("Laya Provider initialized (decide only)", {
109
+ modelName: this.modelName,
110
+ baseURL: this.baseURL ? redactUrlForError(this.baseURL) : "(not set)",
111
+ });
112
+ }
113
+ getDefaultModel() {
114
+ return getProviderModel("LAYA_MODEL", LayaModels.TYPED_DECISIONS);
115
+ }
116
+ vendorLabel() {
117
+ return "Laya";
118
+ }
119
+ vendorDisplayName() {
120
+ return "Laya";
121
+ }
122
+ decisionApiKey() {
123
+ return this.apiKey;
124
+ }
125
+ missingKeyMessage() {
126
+ return "Laya requires an API key. Set LAYA_API_KEY or pass credentials.laya.apiKey.";
127
+ }
128
+ missingConfigMessage() {
129
+ return this.baseURL
130
+ ? undefined
131
+ : "Laya requires a base URL. Set LAYA_BASE_URL or pass credentials.laya.baseURL.";
132
+ }
133
+ decisionEndpoint() {
134
+ return `${this.baseURL}/predict`;
135
+ }
136
+ decisionHeaders() {
137
+ return {
138
+ Authorization: `Bearer ${this.apiKey}`,
139
+ "Content-Type": "application/json",
140
+ };
141
+ }
142
+ /** `auto` means "let Laya's router choose", which it does when no model is named. */
143
+ buildDecisionBody(state, questions, model) {
144
+ return model === LayaModels.AUTO
145
+ ? { state, questions }
146
+ : { model, state, questions };
147
+ }
148
+ parseDecisionError(status, payload, requestId) {
149
+ const parsed = parseLayaError(status, payload, requestId);
150
+ const message = redactCredentials(parsed.message, this.apiKey);
151
+ return {
152
+ ...parsed,
153
+ message: message || `Laya request failed with HTTP ${status}`,
154
+ };
155
+ }
156
+ readRequestId(headers) {
157
+ return headers.get("x-litellm-call-id") ?? undefined;
158
+ }
159
+ /**
160
+ * The checkpoint that actually answered is `routing.model` — the one Laya's
161
+ * router picked when the request said `auto`.
162
+ */
163
+ resolveResponseModel(payload, requestedModel) {
164
+ const routing = payload.routing;
165
+ if (isRecord(routing) && typeof routing.model === "string") {
166
+ return routing.model;
167
+ }
168
+ return super.resolveResponseModel(payload, requestedModel);
169
+ }
170
+ }
@@ -0,0 +1,102 @@
1
+ import type { AIProviderName } from "../constants/enums.js";
2
+ import { BaseProvider } from "../core/baseProvider.js";
3
+ import { ProviderError } from "../types/index.js";
4
+ import type { DecisionError, DecisionQuestion, DecisionRequest, DecisionResult, DecisionState, LanguageModel, StreamOptions, StreamResult, ValidationSchema } from "../types/index.js";
5
+ /**
6
+ * Generous enough for a cold start (measured at 2.0–2.7s after idle on Jev)
7
+ * while still bounded. Every internal caller is fail-open, so this only bites
8
+ * on a genuine stall.
9
+ */
10
+ export declare const DEFAULT_DECISION_TIMEOUT_MS = 5000;
11
+ export declare const isRecord: (v: unknown) => v is Record<string, unknown>;
12
+ export declare const asNumber: (v: unknown) => number | undefined;
13
+ /**
14
+ * FastAPI's request-validation envelope, `{"detail":[{loc, msg, input}]}`,
15
+ * reduced to its field paths and messages. `input` echoes the caller's own
16
+ * request back — possibly the whole state — so it is never surfaced.
17
+ */
18
+ export declare function describeValidationErrors(detail: readonly unknown[]): string;
19
+ /**
20
+ * The shared half of every "System One" decision provider — a model that takes
21
+ * one `state` plus typed questions and returns one typed answer each, with no
22
+ * text anywhere.
23
+ *
24
+ * Vendors differ in where the request goes, how it is authenticated, what the
25
+ * body carries and what their error envelopes look like. They do not differ in
26
+ * anything else, so everything else lives here: the missing-key check, the
27
+ * auth circuit breaker, the timeout and retry loop, answer parsing and usage.
28
+ * A new decision provider implements the abstract hooks and nothing more.
29
+ */
30
+ export declare abstract class SystemOneDecisionProvider extends BaseProvider {
31
+ protected readonly proxyFetch: typeof fetch;
32
+ private readonly maxRetries;
33
+ /**
34
+ * Set once the key has been rejected. A bad key cannot fix itself, and
35
+ * without this every later request would pay a full round trip (~265ms
36
+ * measured on Jev) to be told so again — a permanent tax on the hot path for
37
+ * a misconfiguration.
38
+ */
39
+ private authFailed;
40
+ protected constructor(modelName: string | undefined, providerName: AIProviderName, sdk: unknown);
41
+ /** Short vendor name for messages and logs, e.g. "TypeSafe". */
42
+ protected abstract vendorLabel(): string;
43
+ /** How the model is named when describing it, e.g. "TypeSafe (Jev)". */
44
+ protected abstract vendorDisplayName(): string;
45
+ protected abstract decisionApiKey(): string;
46
+ protected abstract missingKeyMessage(): string;
47
+ /**
48
+ * Why this instance cannot send a request even though it has a key, such as
49
+ * a provider with no built-in endpoint and none configured. Undefined when
50
+ * nothing is missing.
51
+ */
52
+ protected missingConfigMessage(): string | undefined;
53
+ protected abstract decisionEndpoint(): string;
54
+ protected abstract decisionHeaders(): Record<string, string>;
55
+ protected abstract buildDecisionBody(state: DecisionState, questions: Record<string, Record<string, unknown>>, model: string): Record<string, unknown>;
56
+ protected abstract parseDecisionError(status: number, payload: unknown, requestId: string | undefined): DecisionError;
57
+ protected abstract readRequestId(headers: Headers): string | undefined;
58
+ /**
59
+ * The System One wire calls the yes/no primitive `noul`; this codebase and
60
+ * every SDK exposing it call it `boolean`.
61
+ */
62
+ protected encodeQuestion(question: DecisionQuestion): Record<string, unknown>;
63
+ /** Per-question confidence a transport reports outside the answer objects. */
64
+ protected reportedConfidence(_payload: unknown): Record<string, number>;
65
+ protected resolveResponseModel(payload: Record<string, unknown>, requestedModel: string): string;
66
+ /** Extra fields for the credential-rejected warning. */
67
+ protected diagnosticContext(): Record<string, unknown>;
68
+ protected getProviderName(): AIProviderName;
69
+ protected getDefaultDecisionModel(): string | undefined;
70
+ supportsTools(): boolean;
71
+ /**
72
+ * A decision model produces no text, so there is no chat model behind it.
73
+ * Unreachable while the descriptor declares `inferenceKinds: ["decide"]`;
74
+ * kept actionable for the case where something routes here anyway.
75
+ */
76
+ protected getAISDKModel(): LanguageModel;
77
+ protected executeStream(_options: StreamOptions, _analysisSchema?: ValidationSchema): Promise<StreamResult>;
78
+ /**
79
+ * Must RETURN the error, never throw it (CLAUDE.md rule 6).
80
+ */
81
+ protected formatProviderError(error: unknown): Error;
82
+ /**
83
+ * Evaluate a state against a batch of typed questions.
84
+ *
85
+ * Throws a {@link ProviderError} carrying a normalised {@link DecisionError}
86
+ * in `cause`. Internal hot paths should prefer `NeuroLink.tryDecide()`,
87
+ * which returns null instead.
88
+ */
89
+ decide(request: DecisionRequest): Promise<DecisionResult>;
90
+ /**
91
+ * Refuse a request the model cannot read in full. An encoder cuts the state
92
+ * off past its window without saying so, so without this a decision would
93
+ * be made on input the model never saw — and reported as if it had.
94
+ */
95
+ private assertWithinDecisionLimits;
96
+ /**
97
+ * Wrap a normalised decision failure in the provider error type, keeping
98
+ * the structured detail reachable via `cause` for callers that branch on
99
+ * `kind` (authentication vs rate_limit vs max_tokens_exceeded).
100
+ */
101
+ protected decisionError(detail: DecisionError): ProviderError;
102
+ }