@juspay/neurolink 12.55.3 → 12.56.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.
- package/CHANGELOG.md +2 -2
- package/dist/browser/neurolink.min.js +415 -415
- package/dist/cli/commands/setup.d.ts +3 -3
- package/dist/cli/commands/setup.js +4 -5
- package/dist/constants/enums.d.ts +4 -9
- package/dist/constants/enums.js +5 -10
- package/dist/factories/providerDescriptors.js +7 -48
- package/dist/factories/providerRegistry.js +1 -9
- package/dist/models/manifestRegistry.js +0 -2
- package/dist/neurolink.d.ts +12 -8
- package/dist/neurolink.js +89 -25
- package/dist/providers/catalog/index.generated.d.ts +1 -1
- package/dist/providers/catalog/index.generated.js +6 -2
- package/dist/providers/catalog/provider-catalog.schema.json +54 -1
- package/dist/providers/catalog/schema.js +154 -8
- package/dist/providers/catalog/xor.json +86 -0
- package/dist/providers/configuredDecision.d.ts +2 -2
- package/dist/providers/configuredDecision.js +22 -5
- package/dist/providers/decide/baseUrl.d.ts +3 -1
- package/dist/providers/decide/baseUrl.js +43 -0
- package/dist/providers/decide/errors.js +42 -16
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/ollamaDiagnostics.d.ts +31 -0
- package/dist/types/ollamaDiagnostics.js +1 -0
- package/dist/types/providerCatalog.d.ts +27 -1
- package/dist/types/providerCatalog.generated.d.ts +2 -2
- package/dist/types/providers.d.ts +4 -10
- package/dist/utils/modelChoices.js +1 -9
- package/dist/utils/ollamaDiagnostics.d.ts +69 -0
- package/dist/utils/ollamaDiagnostics.js +192 -0
- package/dist/utils/providerConfig.d.ts +0 -5
- package/dist/utils/providerConfig.js +0 -17
- package/dist/utils/providerHealth.d.ts +33 -19
- package/dist/utils/providerHealth.js +224 -85
- package/docs-site/static/search-index.json +7 -7
- package/package.json +3 -1
- package/dist/models/manifests/xor.d.ts +0 -12
- package/dist/models/manifests/xor.js +0 -30
- package/dist/providers/xor.d.ts +0 -48
- package/dist/providers/xor.js +0 -192
|
@@ -262,12 +262,23 @@ export type ProviderCatalogJson = {
|
|
|
262
262
|
evidence: CatalogEvidence;
|
|
263
263
|
};
|
|
264
264
|
export type DecideFormatterName = "fastapi-validation";
|
|
265
|
-
export type DecideBaseURLTransform = "strip-trailing-slash";
|
|
265
|
+
export type DecideBaseURLTransform = "strip-trailing-slash" | "strip-v1-suffix";
|
|
266
|
+
/** A check a base URL must pass once normalised; `origin` allows only an http(s) URL with no userinfo, query string or fragment. */
|
|
267
|
+
export type DecideBaseURLShape = "origin";
|
|
266
268
|
export type DecideErrorRule = {
|
|
267
269
|
/** One status or a list of them. A rule sets this or `statusMin`, never both. */
|
|
268
270
|
status?: number | readonly number[];
|
|
269
271
|
/** Matches every status at or above this value. */
|
|
270
272
|
statusMin?: number;
|
|
273
|
+
/**
|
|
274
|
+
* With a status, the rule also needs the error message to match this regular
|
|
275
|
+
* expression (case-insensitive, searched in the first 2,000 characters of the
|
|
276
|
+
* message after the key is removed). Bounded: no unescaped `*`, `+` or
|
|
277
|
+
* `{n,}`, no quantifier on a group, no count above 255, and the counts side
|
|
278
|
+
* by side multiply to at most 4,096 ways (each `?` is 2, each `{n,m}` is
|
|
279
|
+
* m-n+1).
|
|
280
|
+
*/
|
|
281
|
+
messagePattern?: string;
|
|
271
282
|
kind: DecisionErrorKind;
|
|
272
283
|
retryable?: boolean;
|
|
273
284
|
};
|
|
@@ -289,16 +300,31 @@ export type DecideModel = {
|
|
|
289
300
|
/** With `body-unless-alias`: the model name that means "send no model". */
|
|
290
301
|
alias?: string;
|
|
291
302
|
};
|
|
303
|
+
/**
|
|
304
|
+
* The request body keys prepared media is written under. Set together with
|
|
305
|
+
* `decisionLimits.media`, which says how much of it a request may carry.
|
|
306
|
+
*/
|
|
307
|
+
export type DecideMedia = {
|
|
308
|
+
/** Written only when the request carries at least one image. */
|
|
309
|
+
imagesKey: string;
|
|
310
|
+
/** Written only when the request carries a video; required exactly when `decisionLimits.media.video` is true. */
|
|
311
|
+
videoKey?: string;
|
|
312
|
+
};
|
|
292
313
|
export type DecideBlock = {
|
|
293
314
|
dialect: DecideDialectName;
|
|
294
315
|
/** Decision order, lower runs first; unique across decide providers. */
|
|
295
316
|
order: number;
|
|
296
317
|
/** True when the provider has no built-in endpoint and the caller must supply one. */
|
|
297
318
|
baseURLRequired?: boolean;
|
|
319
|
+
/** Applied to the base URL in list order. */
|
|
298
320
|
baseURLNormalize?: readonly DecideBaseURLTransform[];
|
|
321
|
+
/** Refuses a base URL of another shape before any request. Omitted means no check. */
|
|
322
|
+
baseURLShape?: DecideBaseURLShape;
|
|
299
323
|
/** Request URL; `{baseURL}` is the only placeholder. */
|
|
300
324
|
endpoint: string;
|
|
301
325
|
model: DecideModel;
|
|
326
|
+
/** Body keys for images and video, after `model`, `state` and `questions`. Omitted means the body never carries media. */
|
|
327
|
+
media?: DecideMedia;
|
|
302
328
|
requestIdHeaders?: readonly string[];
|
|
303
329
|
response?: {
|
|
304
330
|
modelPaths?: readonly string[];
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export type CatalogProviderName = "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" | "laya" | "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";
|
|
2
|
-
export type CatalogCredentialKey = "a2agent" | "abliteration" | "aboveDev" | "ai21" | "aiand" | "aihubmix" | "aionlabs" | "ambient" | "apiRoute" | "arcee" | "atlasCloud" | "avianIo" | "baiduQianfan" | "baseten" | "beeHeossi" | "byteplusModelark" | "bytez" | "cerebras" | "charmHyper" | "chutes" | "cloudflare" | "crusoe" | "dashscope" | "deepinfra" | "deepseek" | "empiriolabs" | "featherlessAi" | "fireworks" | "friendli" | "gmicloud" | "gradientai" | "groq" | "hetznerInference" | "huggingFace" | "inceptionLabs" | "inceptron" | "inco" | "inferenceNet" | "ioIntelligence" | "koscompute" | "laya" | "lemonfoxAi" | "lilac" | "llmtech" | "longcat" | "mancer" | "metaModelApi" | "minimax" | "mistral" | "moark" | "modelscope" | "moonshotAi" | "morph" | "naverClovaStudio" | "nebius" | "neuralwatt" | "novita" | "ovhcloud" | "parasail" | "paretoInference" | "perplexity" | "poolside" | "primeIntellect" | "reka" | "runinfra" | "sakanaAi" | "sambanova" | "sarvam" | "scaleway" | "siliconflow" | "stackit" | "stepfun" | "subconscious" | "synthetic" | "telnyx" | "thinkingMachines" | "tinfoil" | "together" | "umansAi" | "upstage" | "veniceAi" | "vispark" | "vultrInference" | "wafer" | "wandbInference" | "xai" | "xiaomiMimo" | "zAi";
|
|
1
|
+
export type CatalogProviderName = "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" | "laya" | "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" | "xor" | "z-ai";
|
|
2
|
+
export type CatalogCredentialKey = "a2agent" | "abliteration" | "aboveDev" | "ai21" | "aiand" | "aihubmix" | "aionlabs" | "ambient" | "apiRoute" | "arcee" | "atlasCloud" | "avianIo" | "baiduQianfan" | "baseten" | "beeHeossi" | "byteplusModelark" | "bytez" | "cerebras" | "charmHyper" | "chutes" | "cloudflare" | "crusoe" | "dashscope" | "deepinfra" | "deepseek" | "empiriolabs" | "featherlessAi" | "fireworks" | "friendli" | "gmicloud" | "gradientai" | "groq" | "hetznerInference" | "huggingFace" | "inceptionLabs" | "inceptron" | "inco" | "inferenceNet" | "ioIntelligence" | "koscompute" | "laya" | "lemonfoxAi" | "lilac" | "llmtech" | "longcat" | "mancer" | "metaModelApi" | "minimax" | "mistral" | "moark" | "modelscope" | "moonshotAi" | "morph" | "naverClovaStudio" | "nebius" | "neuralwatt" | "novita" | "ovhcloud" | "parasail" | "paretoInference" | "perplexity" | "poolside" | "primeIntellect" | "reka" | "runinfra" | "sakanaAi" | "sambanova" | "sarvam" | "scaleway" | "siliconflow" | "stackit" | "stepfun" | "subconscious" | "synthetic" | "telnyx" | "thinkingMachines" | "tinfoil" | "together" | "umansAi" | "upstage" | "veniceAi" | "vispark" | "vultrInference" | "wafer" | "wandbInference" | "xai" | "xiaomiMimo" | "xor" | "zAi";
|
|
@@ -541,6 +541,10 @@ export type NeurolinkCredentials = {
|
|
|
541
541
|
apiKey?: string;
|
|
542
542
|
baseURL?: string;
|
|
543
543
|
};
|
|
544
|
+
xor?: {
|
|
545
|
+
apiKey?: string;
|
|
546
|
+
baseURL?: string;
|
|
547
|
+
};
|
|
544
548
|
zAi?: {
|
|
545
549
|
apiKey?: string;
|
|
546
550
|
baseURL?: string;
|
|
@@ -601,16 +605,6 @@ export type NeurolinkCredentials = {
|
|
|
601
605
|
*/
|
|
602
606
|
gatewayURL?: string;
|
|
603
607
|
};
|
|
604
|
-
/**
|
|
605
|
-
* XOR (Juspay, open weights) — the `decide` inference type. There is no
|
|
606
|
-
* built-in endpoint: `baseURL` (or XOR_BASE_URL) is required, pointing at the
|
|
607
|
-
* origin of an XOR deployment or a proxy route to one; requests go to
|
|
608
|
-
* `<baseURL>/v1/systemone`.
|
|
609
|
-
*/
|
|
610
|
-
xor?: {
|
|
611
|
-
apiKey?: string;
|
|
612
|
-
baseURL?: string;
|
|
613
|
-
};
|
|
614
608
|
/**
|
|
615
609
|
* Perplexity Decisions API — the `decide` inference type. The key is the same
|
|
616
610
|
* `PERPLEXITY_API_KEY` the `perplexity` text provider reads, but this slice
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Centralized model choices for CLI commands
|
|
3
3
|
* Derives choices from model enums to ensure consistency
|
|
4
4
|
*/
|
|
5
|
-
import { AIProviderName, OpenAIModels, AnthropicModels, GoogleAIModels, BedrockModels, VertexModels, OllamaModels, AzureOpenAIModels, LiteLLMModels, SageMakerModels, OpenRouterModels, NvidiaNimModels, CohereModels, VoyageModels, JinaModels, StabilityModels, IdeogramModels, RecraftModels, TypeSafeModels,
|
|
5
|
+
import { AIProviderName, OpenAIModels, AnthropicModels, GoogleAIModels, BedrockModels, VertexModels, OllamaModels, AzureOpenAIModels, LiteLLMModels, SageMakerModels, OpenRouterModels, NvidiaNimModels, CohereModels, VoyageModels, JinaModels, StabilityModels, IdeogramModels, RecraftModels, TypeSafeModels, PerplexityDeciderModels, CloudflareClefModels, ReplicateModels, } from "../constants/enums.js";
|
|
6
6
|
import { getCatalogJsonEntries, getDecideCatalogEntries, } from "../providers/catalog/loader.js";
|
|
7
7
|
/**
|
|
8
8
|
* Looks up the JSON catalog entry for a provider, if it is one of the 11
|
|
@@ -377,12 +377,6 @@ const TOP_MODELS_CONFIG = {
|
|
|
377
377
|
description: "Preview build of Jev",
|
|
378
378
|
},
|
|
379
379
|
],
|
|
380
|
-
[AIProviderName.XOR]: [
|
|
381
|
-
{
|
|
382
|
-
model: XorModels.XOR_1_1,
|
|
383
|
-
description: "Recommended - XOR 1.1, decision model that also reads images and video",
|
|
384
|
-
},
|
|
385
|
-
],
|
|
386
380
|
[AIProviderName.PERPLEXITY_DECIDER]: [
|
|
387
381
|
{
|
|
388
382
|
model: PerplexityDeciderModels.PPLX_DECIDER_V1_27B,
|
|
@@ -445,7 +439,6 @@ export const DEFAULT_MODELS = {
|
|
|
445
439
|
[AIProviderName.IDEOGRAM]: IdeogramModels.IDEOGRAM_V3,
|
|
446
440
|
[AIProviderName.RECRAFT]: RecraftModels.RECRAFT_V3,
|
|
447
441
|
[AIProviderName.TYPESAFE]: TypeSafeModels.JEV_LATEST,
|
|
448
|
-
[AIProviderName.XOR]: XorModels.XOR_1_1,
|
|
449
442
|
[AIProviderName.PERPLEXITY_DECIDER]: PerplexityDeciderModels.PPLX_DECIDER_V1_27B,
|
|
450
443
|
[AIProviderName.CLOUDFLARE_CLEF]: CloudflareClefModels.CLEF,
|
|
451
444
|
};
|
|
@@ -480,7 +473,6 @@ const MODEL_ENUMS = {
|
|
|
480
473
|
[AIProviderName.IDEOGRAM]: IdeogramModels,
|
|
481
474
|
[AIProviderName.RECRAFT]: RecraftModels,
|
|
482
475
|
[AIProviderName.TYPESAFE]: TypeSafeModels,
|
|
483
|
-
[AIProviderName.XOR]: XorModels,
|
|
484
476
|
[AIProviderName.PERPLEXITY_DECIDER]: PerplexityDeciderModels,
|
|
485
477
|
[AIProviderName.CLOUDFLARE_CLEF]: CloudflareClefModels,
|
|
486
478
|
[AIProviderName.AUTO]: null,
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { OllamaDiagnosticContext, OllamaDiagnosticGate, OllamaDiagnosticOutcome, OllamaDiagnosticPermit, OllamaDiagnosticSnapshot } from "../types/index.js";
|
|
2
|
+
export declare const OLLAMA_DIAGNOSTIC_RESET_MS = 300000;
|
|
3
|
+
/**
|
|
4
|
+
* Validate the shared failure threshold once and reuse that first-use value.
|
|
5
|
+
* Missing or invalid PROVIDER_FAILURE_THRESHOLD values use the existing default
|
|
6
|
+
* of three; ProviderHealthChecker retains its original initialization point.
|
|
7
|
+
*/
|
|
8
|
+
export declare function getValidatedProviderFailureThreshold(): number;
|
|
9
|
+
/** Return diagnostic error text with URL secrets redacted for logs and status. */
|
|
10
|
+
export declare function getOllamaDiagnosticErrorMessage(error: unknown): string;
|
|
11
|
+
/**
|
|
12
|
+
* Normalize an endpoint identity without its fragment, retaining path and query.
|
|
13
|
+
* The key can contain credentials and must remain private like the source URL.
|
|
14
|
+
*/
|
|
15
|
+
export declare function getOllamaDiagnosticEndpointKey(url: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* Resolve native /api/tags at the configured Ollama origin, independently of a
|
|
18
|
+
* compatible /v1 base path. OLLAMA_BASE_URL precedes the legacy OLLAMA_API_BASE
|
|
19
|
+
* alias, then localhost; malformed URLs and non-HTTP(S) schemes are rejected.
|
|
20
|
+
*/
|
|
21
|
+
export declare function resolveOllamaNativeTagsUrl(): string;
|
|
22
|
+
/**
|
|
23
|
+
* Capture endpoint identity and model before asynchronous diagnostic work.
|
|
24
|
+
* Later environment changes cannot retarget this context or its health-cache
|
|
25
|
+
* identity; failure accounting remains shared by endpoint rather than model.
|
|
26
|
+
*/
|
|
27
|
+
export declare function captureOllamaDiagnosticContext(model?: string): OllamaDiagnosticContext;
|
|
28
|
+
/**
|
|
29
|
+
* Copy the current policy state after applying initialization or timed expiry.
|
|
30
|
+
* This observation issues no request and records neither failure nor recovery.
|
|
31
|
+
*/
|
|
32
|
+
export declare function getOllamaDiagnosticSnapshot(context: OllamaDiagnosticContext): OllamaDiagnosticSnapshot;
|
|
33
|
+
/**
|
|
34
|
+
* Admit a native probe or report backoff at the current failure threshold.
|
|
35
|
+
* Timed expiry is applied before this decision. Admission captures the clear
|
|
36
|
+
* generation without reserving a request or recording an outcome; callers must
|
|
37
|
+
* record the actual outcome on completion.
|
|
38
|
+
*/
|
|
39
|
+
export declare function beginOllamaDiagnostic(context: OllamaDiagnosticContext): OllamaDiagnosticGate;
|
|
40
|
+
/**
|
|
41
|
+
* Apply timed expiry, then an admitted outcome once to the latest endpoint state.
|
|
42
|
+
* Transport/protocol failures increment its current count; success clears a
|
|
43
|
+
* nonzero count; not-run records no outcome. Timed expiry preserves permits,
|
|
44
|
+
* including those admitted before the boundary and completed after it.
|
|
45
|
+
* Duplicate permits and permits invalidated by explicit clearing are ignored.
|
|
46
|
+
*
|
|
47
|
+
* @returns A copy of the resulting state, or undefined for an ignored permit.
|
|
48
|
+
*/
|
|
49
|
+
export declare function completeOllamaDiagnostic(attempt: OllamaDiagnosticPermit, outcome: OllamaDiagnosticOutcome): OllamaDiagnosticSnapshot | undefined;
|
|
50
|
+
/**
|
|
51
|
+
* Clear every endpoint and advance generation, invalidating in-flight permits.
|
|
52
|
+
* Unlike timed expiry, this prevents an older completion from repopulating state.
|
|
53
|
+
*/
|
|
54
|
+
export declare function clearOllamaDiagnostics(): void;
|
|
55
|
+
/**
|
|
56
|
+
* Separate cached health by captured endpoint and model without splitting the
|
|
57
|
+
* endpoint-wide diagnostic counter. This private key can contain URL secrets.
|
|
58
|
+
*/
|
|
59
|
+
export declare function getOllamaHealthCacheKey(context: OllamaDiagnosticContext): string;
|
|
60
|
+
export declare class OllamaProtocolError extends Error {
|
|
61
|
+
/** Identify an answered tags response whose models envelope is malformed. */
|
|
62
|
+
constructor();
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Require an object containing a models array, including an empty array.
|
|
66
|
+
* Individual model entries are left to existing normalization; a malformed
|
|
67
|
+
* envelope throws OllamaProtocolError rather than appearing as no models.
|
|
68
|
+
*/
|
|
69
|
+
export declare function readOllamaTagsModels(payload: unknown): unknown[];
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/** Internal native-diagnostic policy; inference and provider selection are separate. */
|
|
2
|
+
import { logger } from "./logger.js";
|
|
3
|
+
import { DEFAULT_OLLAMA_MODEL } from "../providers/ollama/constants.js";
|
|
4
|
+
import { redactUrlsInText } from "./logSanitize.js";
|
|
5
|
+
export const OLLAMA_DIAGNOSTIC_RESET_MS = 300000;
|
|
6
|
+
let validatedFailureThreshold;
|
|
7
|
+
/**
|
|
8
|
+
* Validate the shared failure threshold once and reuse that first-use value.
|
|
9
|
+
* Missing or invalid PROVIDER_FAILURE_THRESHOLD values use the existing default
|
|
10
|
+
* of three; ProviderHealthChecker retains its original initialization point.
|
|
11
|
+
*/
|
|
12
|
+
export function getValidatedProviderFailureThreshold() {
|
|
13
|
+
if (validatedFailureThreshold !== undefined) {
|
|
14
|
+
return validatedFailureThreshold;
|
|
15
|
+
}
|
|
16
|
+
const envValue = process.env.PROVIDER_FAILURE_THRESHOLD;
|
|
17
|
+
if (!envValue) {
|
|
18
|
+
validatedFailureThreshold = 3;
|
|
19
|
+
return validatedFailureThreshold;
|
|
20
|
+
}
|
|
21
|
+
const parsed = Number(envValue);
|
|
22
|
+
if (isNaN(parsed) || parsed <= 0 || parsed > 10) {
|
|
23
|
+
logger.warn(`Invalid PROVIDER_FAILURE_THRESHOLD: ${envValue} (must be between 1 and 10), using default: 3`);
|
|
24
|
+
validatedFailureThreshold = 3;
|
|
25
|
+
return validatedFailureThreshold;
|
|
26
|
+
}
|
|
27
|
+
validatedFailureThreshold = parsed;
|
|
28
|
+
return validatedFailureThreshold;
|
|
29
|
+
}
|
|
30
|
+
/** Return diagnostic error text with URL secrets redacted for logs and status. */
|
|
31
|
+
export function getOllamaDiagnosticErrorMessage(error) {
|
|
32
|
+
return redactUrlsInText(error instanceof Error ? error.message : String(error));
|
|
33
|
+
}
|
|
34
|
+
const states = new Map();
|
|
35
|
+
const completed = new WeakSet();
|
|
36
|
+
let clearGeneration = 0;
|
|
37
|
+
/**
|
|
38
|
+
* Normalize an endpoint identity without its fragment, retaining path and query.
|
|
39
|
+
* The key can contain credentials and must remain private like the source URL.
|
|
40
|
+
*/
|
|
41
|
+
export function getOllamaDiagnosticEndpointKey(url) {
|
|
42
|
+
const parsed = new URL(url);
|
|
43
|
+
parsed.hash = "";
|
|
44
|
+
return parsed.toString();
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Resolve native /api/tags at the configured Ollama origin, independently of a
|
|
48
|
+
* compatible /v1 base path. OLLAMA_BASE_URL precedes the legacy OLLAMA_API_BASE
|
|
49
|
+
* alias, then localhost; malformed URLs and non-HTTP(S) schemes are rejected.
|
|
50
|
+
*/
|
|
51
|
+
export function resolveOllamaNativeTagsUrl() {
|
|
52
|
+
const base = process.env.OLLAMA_BASE_URL ||
|
|
53
|
+
process.env.OLLAMA_API_BASE ||
|
|
54
|
+
"http://localhost:11434";
|
|
55
|
+
const resolved = new URL("/api/tags", base);
|
|
56
|
+
if (resolved.protocol !== "http:" && resolved.protocol !== "https:") {
|
|
57
|
+
throw new Error("Invalid OLLAMA_BASE_URL format (OLLAMA_API_BASE is still accepted as a legacy alias)");
|
|
58
|
+
}
|
|
59
|
+
resolved.hash = "";
|
|
60
|
+
return resolved.toString();
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Capture endpoint identity and model before asynchronous diagnostic work.
|
|
64
|
+
* Later environment changes cannot retarget this context or its health-cache
|
|
65
|
+
* identity; failure accounting remains shared by endpoint rather than model.
|
|
66
|
+
*/
|
|
67
|
+
export function captureOllamaDiagnosticContext(model = process.env.OLLAMA_MODEL || DEFAULT_OLLAMA_MODEL) {
|
|
68
|
+
const url = resolveOllamaNativeTagsUrl();
|
|
69
|
+
return { url, key: getOllamaDiagnosticEndpointKey(url), model };
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Initialize endpoint state or expire failures five minutes after the latest one.
|
|
73
|
+
* Expiry clears the count/timestamp and advances revision, retaining generation
|
|
74
|
+
* so existing in-flight permits remain valid. Only an explicit clear changes
|
|
75
|
+
* generation and invalidates those permits.
|
|
76
|
+
*/
|
|
77
|
+
function getState(context) {
|
|
78
|
+
let state = states.get(context.key);
|
|
79
|
+
if (!state) {
|
|
80
|
+
state = {
|
|
81
|
+
count: 0,
|
|
82
|
+
lastFailureAt: undefined,
|
|
83
|
+
revision: 0,
|
|
84
|
+
generation: clearGeneration,
|
|
85
|
+
};
|
|
86
|
+
states.set(context.key, state);
|
|
87
|
+
}
|
|
88
|
+
if (state.lastFailureAt !== undefined &&
|
|
89
|
+
Date.now() - state.lastFailureAt >= OLLAMA_DIAGNOSTIC_RESET_MS) {
|
|
90
|
+
// This is a fixed-window expiry, not an outcome from a cache hit/probe.
|
|
91
|
+
state.count = 0;
|
|
92
|
+
state.lastFailureAt = undefined;
|
|
93
|
+
state.revision++;
|
|
94
|
+
}
|
|
95
|
+
return state;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Copy the current policy state after applying initialization or timed expiry.
|
|
99
|
+
* This observation issues no request and records neither failure nor recovery.
|
|
100
|
+
*/
|
|
101
|
+
export function getOllamaDiagnosticSnapshot(context) {
|
|
102
|
+
return { ...getState(context) };
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Admit a native probe or report backoff at the current failure threshold.
|
|
106
|
+
* Timed expiry is applied before this decision. Admission captures the clear
|
|
107
|
+
* generation without reserving a request or recording an outcome; callers must
|
|
108
|
+
* record the actual outcome on completion.
|
|
109
|
+
*/
|
|
110
|
+
export function beginOllamaDiagnostic(context) {
|
|
111
|
+
const state = getState(context);
|
|
112
|
+
if (state.count >= getValidatedProviderFailureThreshold()) {
|
|
113
|
+
return {
|
|
114
|
+
kind: "suppressed",
|
|
115
|
+
count: state.count,
|
|
116
|
+
reason: `Ollama native diagnostic backed off after ${state.count} consecutive transport failures`,
|
|
117
|
+
generation: state.generation,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
return { kind: "allowed", context, generation: state.generation };
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Apply timed expiry, then an admitted outcome once to the latest endpoint state.
|
|
124
|
+
* Transport/protocol failures increment its current count; success clears a
|
|
125
|
+
* nonzero count; not-run records no outcome. Timed expiry preserves permits,
|
|
126
|
+
* including those admitted before the boundary and completed after it.
|
|
127
|
+
* Duplicate permits and permits invalidated by explicit clearing are ignored.
|
|
128
|
+
*
|
|
129
|
+
* @returns A copy of the resulting state, or undefined for an ignored permit.
|
|
130
|
+
*/
|
|
131
|
+
export function completeOllamaDiagnostic(attempt, outcome) {
|
|
132
|
+
if (completed.has(attempt)) {
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
completed.add(attempt);
|
|
136
|
+
const storedState = states.get(attempt.context.key);
|
|
137
|
+
if (!storedState || storedState.generation !== attempt.generation) {
|
|
138
|
+
return undefined; // Explicit clearing invalidated this in-flight token.
|
|
139
|
+
}
|
|
140
|
+
const state = getState(attempt.context);
|
|
141
|
+
if (outcome === "transport-failure" || outcome === "protocol-failure") {
|
|
142
|
+
// Read and increment the latest completed state, never the starting count.
|
|
143
|
+
state.count++;
|
|
144
|
+
state.lastFailureAt = Date.now();
|
|
145
|
+
state.revision++;
|
|
146
|
+
}
|
|
147
|
+
else if (outcome === "success" && state.count !== 0) {
|
|
148
|
+
state.count = 0;
|
|
149
|
+
state.lastFailureAt = undefined;
|
|
150
|
+
state.revision++;
|
|
151
|
+
}
|
|
152
|
+
// Only admitted probes may reach this function. not-run cannot claim failure
|
|
153
|
+
// or recovery; genuine success while already clear retains the cache revision.
|
|
154
|
+
return { ...state };
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Clear every endpoint and advance generation, invalidating in-flight permits.
|
|
158
|
+
* Unlike timed expiry, this prevents an older completion from repopulating state.
|
|
159
|
+
*/
|
|
160
|
+
export function clearOllamaDiagnostics() {
|
|
161
|
+
clearGeneration++;
|
|
162
|
+
states.clear();
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Separate cached health by captured endpoint and model without splitting the
|
|
166
|
+
* endpoint-wide diagnostic counter. This private key can contain URL secrets.
|
|
167
|
+
*/
|
|
168
|
+
export function getOllamaHealthCacheKey(context) {
|
|
169
|
+
return JSON.stringify(["ollama", context.key, context.model]);
|
|
170
|
+
}
|
|
171
|
+
export class OllamaProtocolError extends Error {
|
|
172
|
+
/** Identify an answered tags response whose models envelope is malformed. */
|
|
173
|
+
constructor() {
|
|
174
|
+
super("Invalid models format from Ollama API");
|
|
175
|
+
this.name = "OllamaProtocolError";
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Require an object containing a models array, including an empty array.
|
|
180
|
+
* Individual model entries are left to existing normalization; a malformed
|
|
181
|
+
* envelope throws OllamaProtocolError rather than appearing as no models.
|
|
182
|
+
*/
|
|
183
|
+
export function readOllamaTagsModels(payload) {
|
|
184
|
+
if (payload === null ||
|
|
185
|
+
typeof payload !== "object" ||
|
|
186
|
+
Array.isArray(payload) ||
|
|
187
|
+
!("models" in payload) ||
|
|
188
|
+
!Array.isArray(payload.models)) {
|
|
189
|
+
throw new OllamaProtocolError();
|
|
190
|
+
}
|
|
191
|
+
return payload.models;
|
|
192
|
+
}
|
|
@@ -400,11 +400,6 @@ export declare function resolveOpenAICompatConfig(entry: OpenAICompatConfigInput
|
|
|
400
400
|
* TypeSafe (Jev) — the `decide` inference type. Not a text provider.
|
|
401
401
|
*/
|
|
402
402
|
export declare function createTypeSafeConfig(): ProviderConfigOptions;
|
|
403
|
-
/**
|
|
404
|
-
* XOR — the `decide` inference type. Not a text provider, and it has no
|
|
405
|
-
* built-in endpoint: XOR_BASE_URL is required alongside the key.
|
|
406
|
-
*/
|
|
407
|
-
export declare function createXorConfig(): ProviderConfigOptions;
|
|
408
403
|
/**
|
|
409
404
|
* Perplexity Decisions API — the `decide` inference type, not the Sonar text
|
|
410
405
|
* models. It reads the same PERPLEXITY_API_KEY as the `perplexity` text
|
|
@@ -1324,23 +1324,6 @@ export function createTypeSafeConfig() {
|
|
|
1324
1324
|
],
|
|
1325
1325
|
};
|
|
1326
1326
|
}
|
|
1327
|
-
/**
|
|
1328
|
-
* XOR — the `decide` inference type. Not a text provider, and it has no
|
|
1329
|
-
* built-in endpoint: XOR_BASE_URL is required alongside the key.
|
|
1330
|
-
*/
|
|
1331
|
-
export function createXorConfig() {
|
|
1332
|
-
return {
|
|
1333
|
-
providerName: "XOR",
|
|
1334
|
-
envVarName: "XOR_API_KEY",
|
|
1335
|
-
setupUrl: "https://huggingface.co/juspay/xor",
|
|
1336
|
-
description: "API key for your XOR endpoint",
|
|
1337
|
-
instructions: [
|
|
1338
|
-
"1. Reach an XOR deployment, or use a LiteLLM proxy with a pass-through route to one",
|
|
1339
|
-
"2. Set XOR_BASE_URL to its origin (NeuroLink calls <XOR_BASE_URL>/v1/systemone)",
|
|
1340
|
-
"3. Set XOR_API_KEY to the key it accepts (on LiteLLM, a virtual key whose team allows xor-1.1)",
|
|
1341
|
-
],
|
|
1342
|
-
};
|
|
1343
|
-
}
|
|
1344
1327
|
/**
|
|
1345
1328
|
* Perplexity Decisions API — the `decide` inference type, not the Sonar text
|
|
1346
1329
|
* models. It reads the same PERPLEXITY_API_KEY as the `perplexity` text
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Provider Health Checking System
|
|
3
|
-
* Prevents 500 errors by validating provider availability and configuration
|
|
4
|
-
*/
|
|
5
1
|
import { AIProviderName } from "../constants/enums.js";
|
|
6
2
|
import type { ProviderHealthCheckOptions, ProviderHealthStatusOptions } from "../types/index.js";
|
|
7
3
|
export declare class ProviderHealthChecker {
|
|
@@ -9,20 +5,16 @@ export declare class ProviderHealthChecker {
|
|
|
9
5
|
private static readonly DEFAULT_TIMEOUT;
|
|
10
6
|
private static readonly DEFAULT_CACHE_AGE;
|
|
11
7
|
/**
|
|
12
|
-
* Fixed backoff
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* `maxCacheAge` for its own health-status cache). Evicting the breaker
|
|
17
|
-
* entry using whichever caller happens to read it next would let a
|
|
18
|
-
* short-TTL caller silently erase a backoff another caller is relying on
|
|
19
|
-
* — see `checkProviderHealth`. This constant governs breaker expiry alone
|
|
20
|
-
* and is intentionally independent of `maxCacheAge`/`DEFAULT_CACHE_AGE`.
|
|
8
|
+
* Fixed backoff for the existing non-Ollama provider breaker. Ollama uses
|
|
9
|
+
* the same fixed window in ollamaDiagnostics, shared by every native caller.
|
|
10
|
+
* Neither window depends on a caller's maxCacheAge: expiring a health cache
|
|
11
|
+
* must not silently erase a transport backoff owned by another caller.
|
|
21
12
|
*/
|
|
22
13
|
private static readonly CIRCUIT_BREAKER_RESET_MS;
|
|
23
14
|
private static readonly CONSECUTIVE_FAILURE_THRESHOLD;
|
|
24
15
|
/**
|
|
25
|
-
*
|
|
16
|
+
* Non-Ollama circuit breaker state, keyed by provider. Ollama's separate
|
|
17
|
+
* native policy captures the actual endpoint and records aggregate outcomes.
|
|
26
18
|
*
|
|
27
19
|
* `count` is a count of consecutive failed CONNECTIVITY PROBES (plus the
|
|
28
20
|
* rare case of the check itself throwing) — never of "the provider is
|
|
@@ -56,11 +48,14 @@ export declare class ProviderHealthChecker {
|
|
|
56
48
|
*/
|
|
57
49
|
private static readonly MAX_CONCURRENT_HEALTH_CHECKS;
|
|
58
50
|
/**
|
|
59
|
-
*
|
|
51
|
+
* Preserve the validated threshold's original first-use initialization.
|
|
60
52
|
*/
|
|
61
53
|
private static getValidatedFailureThreshold;
|
|
62
54
|
/**
|
|
63
|
-
*
|
|
55
|
+
* Check configuration and optional connectivity/model availability.
|
|
56
|
+
* Ollama captures its endpoint/model once and records only probes that run;
|
|
57
|
+
* a cache hit or missing configured model does not invent a transport failure.
|
|
58
|
+
* Its cached result must match the current diagnostic revision/generation.
|
|
64
59
|
*/
|
|
65
60
|
static checkProviderHealth(providerName: AIProviderName, options?: ProviderHealthCheckOptions): Promise<ProviderHealthStatusOptions>;
|
|
66
61
|
/**
|
|
@@ -189,18 +184,33 @@ export declare class ProviderHealthChecker {
|
|
|
189
184
|
private static getLiteLLMModelsUrl;
|
|
190
185
|
private static getConfiguredLiteLLMModel;
|
|
191
186
|
private static getOllamaBaseUrl;
|
|
187
|
+
/** Resolve the shared native tags URL rather than the compatible models URL. */
|
|
192
188
|
private static getOllamaTagsUrl;
|
|
193
189
|
private static getConfiguredOllamaModel;
|
|
194
190
|
private static fetchJsonWithTimeout;
|
|
195
191
|
private static normalizeModelList;
|
|
196
192
|
private static hasRequestedModel;
|
|
193
|
+
/**
|
|
194
|
+
* Fetch native tags from the captured endpoint and normalize its models.
|
|
195
|
+
* A malformed envelope rejects; a valid empty list remains an empty result.
|
|
196
|
+
*/
|
|
197
197
|
private static getOllamaAvailableModels;
|
|
198
198
|
private static getLiteLLMAvailableModels;
|
|
199
|
+
/**
|
|
200
|
+
* Check a requested model against native tags while keeping diagnostic outcome
|
|
201
|
+
* separate from model availability. An answered valid list is diagnostic
|
|
202
|
+
* success even if that model is absent; malformed JSON/envelopes are protocol
|
|
203
|
+
* failures. A caller recording an aggregate health probe disables recording
|
|
204
|
+
* here so its admitted permit is completed only once at the outer boundary.
|
|
205
|
+
*/
|
|
199
206
|
private static checkOllamaAvailability;
|
|
200
207
|
private static checkLiteLLMAvailability;
|
|
201
208
|
private static checkLiteLLMConfig;
|
|
202
209
|
/**
|
|
203
|
-
*
|
|
210
|
+
* Report Ollama configuration and model availability using the captured context.
|
|
211
|
+
* Backoff can suppress native work; a missing installed model is a configuration
|
|
212
|
+
* issue after a successful probe. Return actual probe activity/failure for the
|
|
213
|
+
* enclosing health check to record, without completing a second policy permit.
|
|
204
214
|
*/
|
|
205
215
|
private static checkOllamaConfig;
|
|
206
216
|
/**
|
|
@@ -208,7 +218,9 @@ export declare class ProviderHealthChecker {
|
|
|
208
218
|
*/
|
|
209
219
|
private static getCommonModelsForProvider;
|
|
210
220
|
/**
|
|
211
|
-
*
|
|
221
|
+
* Return health within the caller's TTL and, for Ollama, the captured identity.
|
|
222
|
+
* Ollama revision/generation changes invalidate cached status; reading the
|
|
223
|
+
* snapshot may apply timed expiry but never records a probe outcome.
|
|
212
224
|
*/
|
|
213
225
|
private static getCachedHealth;
|
|
214
226
|
/**
|
|
@@ -267,7 +279,9 @@ export declare class ProviderHealthChecker {
|
|
|
267
279
|
*/
|
|
268
280
|
static initializeBackgroundHealthChecks(): void;
|
|
269
281
|
/**
|
|
270
|
-
* Clear health
|
|
282
|
+
* Clear cached health and failure state for one provider or every provider.
|
|
283
|
+
* Clearing Ollama removes all endpoint/model cache entries and explicitly
|
|
284
|
+
* advances diagnostic generation so older in-flight permits cannot commit.
|
|
271
285
|
*/
|
|
272
286
|
static clearHealthCache(providerName?: AIProviderName): void;
|
|
273
287
|
static checkFallbackProviderAvailability(providerName: string, model: string): Promise<{
|