@juspay/neurolink 12.28.4 → 12.29.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 +9 -2
- package/dist/adapters/tts/googleTTSHandler.d.ts +71 -1
- package/dist/adapters/tts/googleTTSHandler.js +254 -0
- package/dist/browser/neurolink.min.js +399 -399
- package/dist/cli/commands/voices.d.ts +23 -0
- package/dist/cli/commands/voices.js +102 -0
- package/dist/cli/loop/optionsSchema.d.ts +1 -1
- package/dist/cli/parser.js +3 -0
- package/dist/constants/enums.d.ts +13 -0
- package/dist/constants/enums.js +15 -0
- package/dist/core/baseProvider.d.ts +4 -0
- package/dist/core/baseProvider.js +245 -148
- package/dist/core/modules/structuredOutputPolicy.d.ts +27 -0
- package/dist/core/modules/structuredOutputPolicy.js +36 -0
- package/dist/core/nativeGenerateLoop.js +7 -1
- package/dist/neurolink.d.ts +42 -0
- package/dist/neurolink.js +102 -2
- package/dist/providers/catalog/index.generated.d.ts +1 -1
- package/dist/providers/catalog/index.generated.js +6 -0
- package/dist/providers/catalog/morph.json +97 -0
- package/dist/providers/catalog/novita.json +114 -0
- package/dist/providers/openaiChatCompletionsBase.d.ts +22 -0
- package/dist/providers/openaiChatCompletionsBase.js +291 -8
- package/dist/server/utils/validation.d.ts +34 -0
- package/dist/server/utils/validation.js +27 -0
- package/dist/server/websocket/WebSocketHandler.d.ts +4 -6
- package/dist/server/websocket/WebSocketHandler.js +185 -18
- package/dist/types/cli.d.ts +11 -0
- package/dist/types/generate.d.ts +15 -0
- package/dist/types/isolatedAgent.d.ts +16 -7
- package/dist/types/openaiCompatible.d.ts +6 -0
- package/dist/types/providerCatalog.generated.d.ts +2 -2
- package/dist/types/providers.d.ts +50 -3
- package/dist/types/server.d.ts +8 -0
- package/dist/types/stream.d.ts +30 -0
- package/dist/types/tts.d.ts +10 -0
- package/dist/types/utilities.d.ts +16 -3
- package/dist/utils/logger.d.ts +58 -13
- package/dist/utils/logger.js +156 -11
- package/dist/utils/providerHealth.d.ts +65 -1
- package/dist/utils/providerHealth.js +216 -54
- package/dist/utils/ttsProcessor.d.ts +30 -1
- package/dist/utils/ttsProcessor.js +76 -0
- package/docs-site/static/search-index.json +4 -4
- package/package.json +6 -1
package/dist/utils/logger.js
CHANGED
|
@@ -13,6 +13,19 @@
|
|
|
13
13
|
* - Structured data support for complex objects
|
|
14
14
|
* - Tabular data display
|
|
15
15
|
*/
|
|
16
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
17
|
+
/**
|
|
18
|
+
* Identifies which NeuroLink instance is emitting, for the async operation
|
|
19
|
+
* currently on the stack. Populated by `runInInstanceScope()` — which the SDK
|
|
20
|
+
* entry points (`generate`, `stream`, `generateText`) wrap their bodies in —
|
|
21
|
+
* so a log call anywhere beneath them can be attributed without every call
|
|
22
|
+
* site threading an instance through.
|
|
23
|
+
*
|
|
24
|
+
* Empty for logs emitted outside any call (construction, background MCP
|
|
25
|
+
* reconnects, module init). Those stay unattributed rather than being charged
|
|
26
|
+
* to whichever instance happens to hold the global sink.
|
|
27
|
+
*/
|
|
28
|
+
const instanceLogScope = new AsyncLocalStorage();
|
|
16
29
|
// OTel trace context for log correlation (optional — gracefully no-ops if OTel not initialized)
|
|
17
30
|
let traceApi = null;
|
|
18
31
|
let traceApiPromise = null;
|
|
@@ -48,6 +61,30 @@ function argvRequestsJsonOutput() {
|
|
|
48
61
|
return argv.some((arg, index) => (FORMAT_FLAGS.includes(arg) && argv[index + 1] === "json") ||
|
|
49
62
|
FORMAT_FLAGS.some((flag) => arg === `${flag}=json`));
|
|
50
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Best-effort isolated copy of a log call's `data` payload for one consumer.
|
|
66
|
+
* `NeuroLinkLogger.log()` hands the same call's data to every scoped emitter
|
|
67
|
+
* for the active instance plus the process-wide sink — a designed, supported
|
|
68
|
+
* multi-consumer configuration — so a copy per consumer is what keeps one
|
|
69
|
+
* consumer's in-place mutation (e.g. redacting a field before forwarding it)
|
|
70
|
+
* from silently corrupting what a sibling consumer, or stored log history,
|
|
71
|
+
* observes. Non-object values need no copy — they are already by-value.
|
|
72
|
+
* Values `structuredClone` cannot handle (functions, etc.) are handed through
|
|
73
|
+
* unchanged rather than dropped: isolation is not achievable for those
|
|
74
|
+
* without lossy serialization, so that narrow case keeps the old
|
|
75
|
+
* shared-reference behavior instead of losing the payload.
|
|
76
|
+
*/
|
|
77
|
+
function cloneLogData(data) {
|
|
78
|
+
if (data === null || typeof data !== "object") {
|
|
79
|
+
return data;
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
return structuredClone(data);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return data;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
51
88
|
class NeuroLinkLogger {
|
|
52
89
|
logLevel = "info";
|
|
53
90
|
logs = [];
|
|
@@ -55,6 +92,7 @@ class NeuroLinkLogger {
|
|
|
55
92
|
isDebugMode;
|
|
56
93
|
diagnosticsToStderr = false;
|
|
57
94
|
eventEmitter;
|
|
95
|
+
scopedEmitters = new Map();
|
|
58
96
|
constructor() {
|
|
59
97
|
// Cache debug mode check to avoid repeated array searches
|
|
60
98
|
this.isDebugMode =
|
|
@@ -90,6 +128,75 @@ class NeuroLinkLogger {
|
|
|
90
128
|
}
|
|
91
129
|
this.eventEmitter = undefined;
|
|
92
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* Runs `fn` with every log call beneath it attributed to `instanceId`.
|
|
133
|
+
*
|
|
134
|
+
* Nesting is safe and expected: a host turn that delegates to a worker ends
|
|
135
|
+
* up with the worker's id on top for the duration of the worker's call, and
|
|
136
|
+
* the host's id restored afterwards. Re-entering with the same id is a
|
|
137
|
+
* no-op in effect.
|
|
138
|
+
*
|
|
139
|
+
* @param instanceId - Identifier of the emitting NeuroLink instance
|
|
140
|
+
* @param fn - Work to run inside the scope
|
|
141
|
+
* @returns Whatever `fn` returns
|
|
142
|
+
*/
|
|
143
|
+
runInInstanceScope(instanceId, fn) {
|
|
144
|
+
return instanceLogScope.run(instanceId, fn);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* The instance id currently attributed, or undefined outside any scope.
|
|
148
|
+
*/
|
|
149
|
+
getInstanceScope() {
|
|
150
|
+
return instanceLogScope.getStore();
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Subscribes `emitter` to log events emitted by one instance only.
|
|
154
|
+
*
|
|
155
|
+
* Unlike {@link setEventEmitter} — a single process-wide sink that receives
|
|
156
|
+
* everything — a scoped emitter receives only events logged inside that
|
|
157
|
+
* instance's {@link runInInstanceScope}. Several emitters may share an id.
|
|
158
|
+
*
|
|
159
|
+
* @param instanceId - Instance whose events this emitter should receive
|
|
160
|
+
* @param emitter - The sink to subscribe
|
|
161
|
+
*/
|
|
162
|
+
addScopedEventEmitter(instanceId, emitter) {
|
|
163
|
+
const existing = this.scopedEmitters.get(instanceId);
|
|
164
|
+
if (existing) {
|
|
165
|
+
existing.add(emitter);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
this.scopedEmitters.set(instanceId, new Set([emitter]));
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Unsubscribes a scoped emitter. Must be called when the owning instance is
|
|
172
|
+
* disposed, or the emitter — and everything it closes over — is retained by
|
|
173
|
+
* the process-global logger for the lifetime of the process.
|
|
174
|
+
*
|
|
175
|
+
* @param instanceId - Instance the emitter was registered against
|
|
176
|
+
* @param emitter - The sink to unsubscribe
|
|
177
|
+
*/
|
|
178
|
+
removeScopedEventEmitter(instanceId, emitter) {
|
|
179
|
+
const existing = this.scopedEmitters.get(instanceId);
|
|
180
|
+
if (!existing) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
existing.delete(emitter);
|
|
184
|
+
if (existing.size === 0) {
|
|
185
|
+
this.scopedEmitters.delete(instanceId);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Drops every scoped emitter registered against an instance.
|
|
190
|
+
*
|
|
191
|
+
* Called from `NeuroLink.dispose()`: the registry lives on the
|
|
192
|
+
* process-global logger, so an instance that goes away without clearing its
|
|
193
|
+
* own entry would keep its sinks — and their closures — reachable forever.
|
|
194
|
+
*
|
|
195
|
+
* @param instanceId - Instance whose sinks should be dropped
|
|
196
|
+
*/
|
|
197
|
+
clearScopedEventEmitters(instanceId) {
|
|
198
|
+
this.scopedEmitters.delete(instanceId);
|
|
199
|
+
}
|
|
93
200
|
/**
|
|
94
201
|
* Sets the minimum log level that will be processed and output.
|
|
95
202
|
* Log messages with a level lower than this will be ignored.
|
|
@@ -274,18 +381,50 @@ class NeuroLinkLogger {
|
|
|
274
381
|
timestamp: new Date(),
|
|
275
382
|
data,
|
|
276
383
|
};
|
|
277
|
-
// Emit
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
384
|
+
// Emit to the per-instance sinks for whichever instance is on the stack,
|
|
385
|
+
// then to the process-wide sink. The two are independent: a scoped sink
|
|
386
|
+
// never sees another instance's events, and the global sink still sees
|
|
387
|
+
// everything, so existing setEventEmitter() consumers are unaffected.
|
|
388
|
+
//
|
|
389
|
+
// Several emitters may be registered for the same instance id (see
|
|
390
|
+
// addScopedEventEmitter's own JSDoc), and the global sink always runs
|
|
391
|
+
// alongside them — so one call's `data` object is, by design, handed to
|
|
392
|
+
// more than one consumer. Each consumer below gets its OWN clone rather
|
|
393
|
+
// than the shared `data` reference, so an in-place mutation by one (e.g.
|
|
394
|
+
// a bridge redacting a field before forwarding it) cannot corrupt what a
|
|
395
|
+
// sibling consumer sees or what `entry` — which keeps the original,
|
|
396
|
+
// unmodified `data` — stores for `getLogs()`.
|
|
397
|
+
const scopeId = instanceLogScope.getStore();
|
|
398
|
+
const scoped = scopeId === undefined ? undefined : this.scopedEmitters.get(scopeId);
|
|
399
|
+
if (scoped || this.eventEmitter) {
|
|
400
|
+
const timestampMs = entry.timestamp.getTime();
|
|
401
|
+
if (scoped) {
|
|
402
|
+
for (const emitter of scoped) {
|
|
403
|
+
try {
|
|
404
|
+
emitter.emit("log-event", {
|
|
405
|
+
level,
|
|
406
|
+
message,
|
|
407
|
+
timestamp: timestampMs,
|
|
408
|
+
data: cloneLogData(data),
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
catch {
|
|
412
|
+
// Silently ignore emitter errors to avoid disrupting logging
|
|
413
|
+
}
|
|
414
|
+
}
|
|
286
415
|
}
|
|
287
|
-
|
|
288
|
-
|
|
416
|
+
if (this.eventEmitter) {
|
|
417
|
+
try {
|
|
418
|
+
this.eventEmitter.emit("log-event", {
|
|
419
|
+
level,
|
|
420
|
+
message,
|
|
421
|
+
timestamp: timestampMs,
|
|
422
|
+
data: cloneLogData(data),
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
catch {
|
|
426
|
+
// Silently ignore emitter errors to avoid disrupting logging
|
|
427
|
+
}
|
|
289
428
|
}
|
|
290
429
|
}
|
|
291
430
|
// Store log entry
|
|
@@ -496,6 +635,12 @@ export const logger = {
|
|
|
496
635
|
clearLogs: () => neuroLinkLogger.clearLogs(),
|
|
497
636
|
setEventEmitter: (emitter) => neuroLinkLogger.setEventEmitter(emitter),
|
|
498
637
|
clearEventEmitter: (ifEmitter) => neuroLinkLogger.clearEventEmitter(ifEmitter),
|
|
638
|
+
// Per-instance routing (see NeuroLinkLogger.runInInstanceScope)
|
|
639
|
+
runInInstanceScope: (instanceId, fn) => neuroLinkLogger.runInInstanceScope(instanceId, fn),
|
|
640
|
+
getInstanceScope: () => neuroLinkLogger.getInstanceScope(),
|
|
641
|
+
addScopedEventEmitter: (instanceId, emitter) => neuroLinkLogger.addScopedEventEmitter(instanceId, emitter),
|
|
642
|
+
removeScopedEventEmitter: (instanceId, emitter) => neuroLinkLogger.removeScopedEventEmitter(instanceId, emitter),
|
|
643
|
+
clearScopedEventEmitters: (instanceId) => neuroLinkLogger.clearScopedEventEmitters(instanceId),
|
|
499
644
|
};
|
|
500
645
|
/**
|
|
501
646
|
* MCP compatibility exports - all use the same unified logger instance.
|
|
@@ -8,8 +8,53 @@ export declare class ProviderHealthChecker {
|
|
|
8
8
|
private static healthCache;
|
|
9
9
|
private static readonly DEFAULT_TIMEOUT;
|
|
10
10
|
private static readonly DEFAULT_CACHE_AGE;
|
|
11
|
+
/**
|
|
12
|
+
* Fixed backoff window for the circuit breaker below, in milliseconds.
|
|
13
|
+
* Deliberately NOT the per-call `maxCacheAge` option: `consecutiveFailures`
|
|
14
|
+
* is a single process-wide map keyed only by provider name, shared by every
|
|
15
|
+
* caller (e.g. `checkFallbackProviderAvailability` hard-codes a 15s
|
|
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`.
|
|
21
|
+
*/
|
|
22
|
+
private static readonly CIRCUIT_BREAKER_RESET_MS;
|
|
11
23
|
private static readonly CONSECUTIVE_FAILURE_THRESHOLD;
|
|
24
|
+
/**
|
|
25
|
+
* Circuit breaker state, keyed by provider.
|
|
26
|
+
*
|
|
27
|
+
* `count` is a count of consecutive failed CONNECTIVITY PROBES (plus the
|
|
28
|
+
* rare case of the check itself throwing) — never of "the provider is
|
|
29
|
+
* unconfigured". Those are different facts and only one of them is worth
|
|
30
|
+
* backing off from. A missing credential is a free, local, determinate
|
|
31
|
+
* answer that costs nothing to re-derive and flips the instant an env var
|
|
32
|
+
* is set; treating it as a failure is what let a sweep of ~38 providers
|
|
33
|
+
* (issue #1305 grew it from 8) blacklist every provider the machine
|
|
34
|
+
* happens not to have credentials for, after which the sweep reported
|
|
35
|
+
* `isConfigured: false` and no `responseTime` for a provider that was
|
|
36
|
+
* merely unconfigured — and kept reporting it after the key was supplied.
|
|
37
|
+
*
|
|
38
|
+
* `lastFailureAt` bounds how long a run of failures is held against a
|
|
39
|
+
* provider; see `checkProviderHealth`.
|
|
40
|
+
*/
|
|
12
41
|
private static consecutiveFailures;
|
|
42
|
+
/**
|
|
43
|
+
* Hard cap on simultaneous per-provider checks inside
|
|
44
|
+
* `checkAllProvidersHealth`. Issue #1305: the sweep used to cover only 8
|
|
45
|
+
* of ~38 registered providers; now every non-opted-out descriptor is
|
|
46
|
+
* included (see `excludeFromHealthSweep`). Firing all ~38 at once via a
|
|
47
|
+
* single `Promise.allSettled` would be harmless by default
|
|
48
|
+
* (`includeConnectivityTest` defaults to false, so most calls never
|
|
49
|
+
* leave the process), but a caller who opts into
|
|
50
|
+
* `includeConnectivityTest: true` would otherwise open up to ~38
|
|
51
|
+
* concurrent outbound HTTP connections to that many different vendors in
|
|
52
|
+
* one burst. A worker pool keeps that bounded without reducing coverage
|
|
53
|
+
* — every provider is still checked, just not all in the same instant,
|
|
54
|
+
* and without the latency a fixed batch would add (see
|
|
55
|
+
* `checkAllProvidersHealth`).
|
|
56
|
+
*/
|
|
57
|
+
private static readonly MAX_CONCURRENT_HEALTH_CHECKS;
|
|
13
58
|
/**
|
|
14
59
|
* Validate and return a safe failure threshold value
|
|
15
60
|
*/
|
|
@@ -236,7 +281,26 @@ export declare class ProviderHealthChecker {
|
|
|
236
281
|
*/
|
|
237
282
|
static getBestHealthyProvider(preferredProviders?: string[]): Promise<string | null>;
|
|
238
283
|
/**
|
|
239
|
-
* Get health status for all registered providers
|
|
284
|
+
* Get health status for all registered providers.
|
|
285
|
+
*
|
|
286
|
+
* Membership: every descriptor in PROVIDER_DESCRIPTORS EXCEPT those that
|
|
287
|
+
* explicitly opt out via `excludeFromHealthSweep` (none currently do).
|
|
288
|
+
* Before issue #1305's fix, membership was implicit opt-IN via
|
|
289
|
+
* `defaultHealthSweepPriority`, which only 8 of ~38 descriptors carried —
|
|
290
|
+
* every other registered provider was silently invisible to this sweep,
|
|
291
|
+
* including anything a caller had actually configured and could use.
|
|
292
|
+
*
|
|
293
|
+
* Order: `defaultHealthSweepPriority` still decides ORDER for the
|
|
294
|
+
* providers that set it (lower = first — order is behaviour for
|
|
295
|
+
* first-healthy-wins callers like `getBestHealthyProvider`); everything
|
|
296
|
+
* else sorts after them, in PROVIDER_DESCRIPTORS's own declaration order
|
|
297
|
+
* (Array.prototype.sort is stable, so ties never reshuffle).
|
|
298
|
+
*
|
|
299
|
+
* Checks run through a worker pool of at most
|
|
300
|
+
* MAX_CONCURRENT_HEALTH_CHECKS, so a full sweep of every provider can't
|
|
301
|
+
* fire dozens of concurrent requests at once when a caller opts into
|
|
302
|
+
* `includeConnectivityTest`, and no check waits on a slower one beside
|
|
303
|
+
* it.
|
|
240
304
|
*/
|
|
241
305
|
static checkAllProvidersHealth(options?: ProviderHealthCheckOptions): Promise<ProviderHealthStatusOptions[]>;
|
|
242
306
|
/**
|