@juspay/neurolink 12.27.0 → 12.27.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 +3 -3
- package/dist/browser/neurolink.min.js +373 -373
- package/dist/proxy/proxyTracer.d.ts +9 -0
- package/dist/proxy/proxyTracer.js +101 -0
- package/package.json +1 -1
|
@@ -127,6 +127,15 @@ export declare function recordFallbackAttempt(attrs: {
|
|
|
127
127
|
errorMessage?: string;
|
|
128
128
|
durationMs: number;
|
|
129
129
|
}): void;
|
|
130
|
+
/** Reset leg-health state between tests. */
|
|
131
|
+
export declare const __fallbackLegHealthTestHooks: {
|
|
132
|
+
threshold: number;
|
|
133
|
+
maxTrackedLegs: number;
|
|
134
|
+
trackedCount: () => number;
|
|
135
|
+
reset: () => void;
|
|
136
|
+
streakOf: (legKey: string) => number;
|
|
137
|
+
isReported: (legKey: string) => boolean;
|
|
138
|
+
};
|
|
130
139
|
export { ProxyTracer };
|
|
131
140
|
/** Standard SERVER span covers every proxy door, including parsing and admission failures. */
|
|
132
141
|
export declare function startProxyHttpTrace(metadata: RuntimeRequestMetadata, headers: Record<string, string>): {
|
|
@@ -757,7 +757,96 @@ class ProxyTracer {
|
|
|
757
757
|
return this.bridge.injectContext({}, trace.setSpan(context.active(), this.rootSpan));
|
|
758
758
|
}
|
|
759
759
|
}
|
|
760
|
+
/**
|
|
761
|
+
* Consecutive failures a configured fallback leg may accumulate before the
|
|
762
|
+
* proxy says so out loud.
|
|
763
|
+
*
|
|
764
|
+
* A leg that is attempted and never succeeds is invisible today: the counters
|
|
765
|
+
* record every attempt and every failure, but nothing reads them back, so the
|
|
766
|
+
* condition surfaces only if an operator happens to query telemetry. Meanwhile
|
|
767
|
+
* every request falls through to the next leg, which can bill on a different
|
|
768
|
+
* account at a different rate, so the first symptom is a cost change rather
|
|
769
|
+
* than an error. Twenty is low enough to catch a dead leg within seconds of
|
|
770
|
+
* sustained traffic and high enough that ordinary account rotation or a brief
|
|
771
|
+
* cooldown never trips it.
|
|
772
|
+
*/
|
|
773
|
+
const DEAD_FALLBACK_LEG_THRESHOLD = 20;
|
|
774
|
+
/**
|
|
775
|
+
* Upper bound on tracked legs.
|
|
776
|
+
*
|
|
777
|
+
* Not every key comes from the configured chain. The auto-provider path records
|
|
778
|
+
* `auto-provider/<requested model>`, and the requested model is whatever the
|
|
779
|
+
* caller asked for, so a client that fails requests under varying model names
|
|
780
|
+
* mints a new key each time. Without a bound these collections grow for the
|
|
781
|
+
* lifetime of the process. The cap is far above any real fallback chain, so a
|
|
782
|
+
* genuine leg is never evicted in practice.
|
|
783
|
+
*/
|
|
784
|
+
const MAX_TRACKED_FALLBACK_LEGS = 256;
|
|
785
|
+
/** Consecutive failures per `provider/model`, reset by any success. */
|
|
786
|
+
const fallbackFailureStreaks = new Map();
|
|
787
|
+
/** Legs already reported, so a dead leg is stated once and not once per turn. */
|
|
788
|
+
const reportedDeadFallbackLegs = new Set();
|
|
789
|
+
/**
|
|
790
|
+
* Record a streak, evicting the least recently touched leg past the cap.
|
|
791
|
+
*
|
|
792
|
+
* `Map` and `Set` iterate in insertion order and re-setting an existing key does
|
|
793
|
+
* not move it, so a key is deleted before being re-added. That makes recency the
|
|
794
|
+
* eviction order and keeps a leg that is actively failing from being dropped in
|
|
795
|
+
* favour of a stale one.
|
|
796
|
+
*/
|
|
797
|
+
function touchFallbackLegStreak(legKey, streak) {
|
|
798
|
+
fallbackFailureStreaks.delete(legKey);
|
|
799
|
+
fallbackFailureStreaks.set(legKey, streak);
|
|
800
|
+
while (fallbackFailureStreaks.size > MAX_TRACKED_FALLBACK_LEGS) {
|
|
801
|
+
const oldest = fallbackFailureStreaks.keys().next();
|
|
802
|
+
if (oldest.done) {
|
|
803
|
+
break;
|
|
804
|
+
}
|
|
805
|
+
fallbackFailureStreaks.delete(oldest.value);
|
|
806
|
+
reportedDeadFallbackLegs.delete(oldest.value);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* Report a fallback leg that has stopped serving, and its recovery.
|
|
811
|
+
*
|
|
812
|
+
* Deliberately keyed per leg rather than globally: a second leg going dark must
|
|
813
|
+
* still be reported while the first is known dead.
|
|
814
|
+
*/
|
|
815
|
+
function trackFallbackLegHealth(legKey, status, errorMessage) {
|
|
816
|
+
if (status === "success") {
|
|
817
|
+
fallbackFailureStreaks.delete(legKey);
|
|
818
|
+
if (reportedDeadFallbackLegs.delete(legKey)) {
|
|
819
|
+
logger.always(`[proxy] fallback ${legKey} is serving again after being reported dead`);
|
|
820
|
+
}
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
const streak = (fallbackFailureStreaks.get(legKey) ?? 0) + 1;
|
|
824
|
+
touchFallbackLegStreak(legKey, streak);
|
|
825
|
+
if (streak < DEAD_FALLBACK_LEG_THRESHOLD) {
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
if (reportedDeadFallbackLegs.has(legKey)) {
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
// Marked reported only once the report is out. Marking first meant a throwing
|
|
832
|
+
// logger — swallowed by the caller's catch — silenced the leg permanently,
|
|
833
|
+
// which is the one outcome this whole mechanism exists to prevent.
|
|
834
|
+
logger.always(`[proxy] fallback ${legKey} has failed ${streak} consecutive attempts with ` +
|
|
835
|
+
`no success — this leg is not serving. Traffic is falling through to the ` +
|
|
836
|
+
`next leg in the chain, which may bill differently. Check the account's ` +
|
|
837
|
+
`quota and credentials` +
|
|
838
|
+
(errorMessage ? `; last error: ${errorMessage.slice(0, 200)}` : ""));
|
|
839
|
+
reportedDeadFallbackLegs.add(legKey);
|
|
840
|
+
}
|
|
760
841
|
export function recordFallbackAttempt(attrs) {
|
|
842
|
+
// Leg health is tracked outside the metrics try/catch: a telemetry failure
|
|
843
|
+
// must not be the reason an operator never hears that a leg is dead.
|
|
844
|
+
try {
|
|
845
|
+
trackFallbackLegHealth(`${attrs.provider}/${attrs.model}`, attrs.status, attrs.errorMessage);
|
|
846
|
+
}
|
|
847
|
+
catch {
|
|
848
|
+
// Never let health reporting break a request.
|
|
849
|
+
}
|
|
761
850
|
try {
|
|
762
851
|
const m = getProxyMetrics();
|
|
763
852
|
const labels = { provider: attrs.provider, model: attrs.model };
|
|
@@ -776,6 +865,18 @@ export function recordFallbackAttempt(attrs) {
|
|
|
776
865
|
// metrics are best-effort
|
|
777
866
|
}
|
|
778
867
|
}
|
|
868
|
+
/** Reset leg-health state between tests. */
|
|
869
|
+
export const __fallbackLegHealthTestHooks = {
|
|
870
|
+
threshold: DEAD_FALLBACK_LEG_THRESHOLD,
|
|
871
|
+
maxTrackedLegs: MAX_TRACKED_FALLBACK_LEGS,
|
|
872
|
+
trackedCount: () => fallbackFailureStreaks.size,
|
|
873
|
+
reset: () => {
|
|
874
|
+
fallbackFailureStreaks.clear();
|
|
875
|
+
reportedDeadFallbackLegs.clear();
|
|
876
|
+
},
|
|
877
|
+
streakOf: (legKey) => fallbackFailureStreaks.get(legKey) ?? 0,
|
|
878
|
+
isReported: (legKey) => reportedDeadFallbackLegs.has(legKey),
|
|
879
|
+
};
|
|
779
880
|
export { ProxyTracer };
|
|
780
881
|
/** Standard SERVER span covers every proxy door, including parsing and admission failures. */
|
|
781
882
|
export function startProxyHttpTrace(metadata, headers) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "12.27.
|
|
3
|
+
"version": "12.27.1",
|
|
4
4
|
"packageManager": "pnpm@10.15.1",
|
|
5
5
|
"description": "The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002/call) for routing, tool selection and context budgeting. MCP-native (4 transports), voice TTS/STT/realtime, RAG, agents, memory, compaction, 9 observability exporters. OpenAI · Anthropic · Gemini · Bedrock · Azure · Ollama · TypeSafe Jev and more.",
|
|
6
6
|
"author": {
|