@juspay/neurolink 13.2.0 → 13.4.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 +2 -6
- package/dist/browser/neurolink.min.js +411 -411
- package/dist/core/baseProvider.d.ts +0 -1
- package/dist/core/baseProvider.js +5 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/middleware/defaults.d.ts +68 -0
- package/dist/middleware/defaults.js +225 -0
- package/dist/middleware/factory.js +4 -15
- package/dist/middleware/presets.d.ts +12 -0
- package/dist/middleware/presets.js +29 -0
- package/dist/neurolink.d.ts +9 -0
- package/dist/neurolink.js +31 -0
- package/dist/types/config.d.ts +22 -0
- package/dist/types/livekit.d.ts +37 -11
- package/dist/voice/livekit/config.d.ts +13 -8
- package/dist/voice/livekit/config.js +72 -8
- package/dist/voice/livekit/index.d.ts +1 -0
- package/dist/voice/livekit/index.js +1 -0
- package/dist/voice/livekit/realtimeMcpTools.d.ts +8 -7
- package/dist/voice/livekit/realtimeMcpTools.js +10 -9
- package/dist/voice/livekit/realtimeMemory.d.ts +11 -3
- package/dist/voice/livekit/realtimeMemory.js +23 -5
- package/dist/voice/livekit/realtimeModel.d.ts +33 -0
- package/dist/voice/livekit/realtimeModel.js +89 -0
- package/dist/voice/livekit/realtimeVoiceAgent.d.ts +10 -6
- package/dist/voice/livekit/realtimeVoiceAgent.js +42 -39
- package/dist/voice/livekit/schemaSanitizer.d.ts +2 -0
- package/dist/voice/livekit/schemaSanitizer.js +2 -0
- package/docs-site/static/search-index.json +47 -36
- package/package.json +7 -3
|
@@ -599,7 +599,6 @@ export declare abstract class BaseProvider implements AIProvider {
|
|
|
599
599
|
/**
|
|
600
600
|
* Get AI SDK model with middleware applied
|
|
601
601
|
* This method wraps the base model with any configured middleware
|
|
602
|
-
* TODO(#1953): Implement global level middlewares that can be used
|
|
603
602
|
*/
|
|
604
603
|
protected getAISDKModelWithMiddleware(options?: TextGenerationOptions | StreamOptions): Promise<LanguageModel>;
|
|
605
604
|
/**
|
|
@@ -117,7 +117,11 @@ export class BaseProvider {
|
|
|
117
117
|
modelName;
|
|
118
118
|
providerName;
|
|
119
119
|
defaultTimeout = 30000; // 30 seconds
|
|
120
|
-
|
|
120
|
+
// Only set by a subclass passing the 4th constructor argument; no provider
|
|
121
|
+
// does. NeuroLink resolves its instance, global and environment middleware
|
|
122
|
+
// defaults into `options.middleware` before a call reaches here (see
|
|
123
|
+
// middleware/defaults.ts), so a per-call value already carries them.
|
|
124
|
+
middlewareOptions;
|
|
121
125
|
// Tools are conditionally included based on centralized configuration
|
|
122
126
|
directTools = shouldDisableBuiltinTools()
|
|
123
127
|
? {}
|
|
@@ -2509,7 +2513,6 @@ export class BaseProvider {
|
|
|
2509
2513
|
/**
|
|
2510
2514
|
* Get AI SDK model with middleware applied
|
|
2511
2515
|
* This method wraps the base model with any configured middleware
|
|
2512
|
-
* TODO(#1953): Implement global level middlewares that can be used
|
|
2513
2516
|
*/
|
|
2514
2517
|
async getAISDKModelWithMiddleware(options = {}) {
|
|
2515
2518
|
return this.applyMiddlewareToModel(await this.getAISDKModel(), options);
|
package/dist/index.d.ts
CHANGED
|
@@ -89,6 +89,7 @@ export { shutdownOpenTelemetry, flushOpenTelemetry, getLangfuseHealthStatus, set
|
|
|
89
89
|
export { clearAnalyticsMetrics, createAnalyticsMiddleware, getAnalyticsMetrics, } from "./middleware/builtin/analytics.js";
|
|
90
90
|
export { createLifecycleMiddleware } from "./middleware/builtin/lifecycle.js";
|
|
91
91
|
export { MiddlewareFactory } from "./middleware/factory.js";
|
|
92
|
+
export { clearGlobalMiddleware, configureGlobalMiddleware, } from "./middleware/defaults.js";
|
|
92
93
|
export { tool, jsonSchema, stepCountIs } from "./utils/tool.js";
|
|
93
94
|
export { ExporterRegistry } from "./observability/exporterRegistry.js";
|
|
94
95
|
export { NoOpExporter } from "./observability/exporters/baseExporter.js";
|
package/dist/index.js
CHANGED
|
@@ -178,6 +178,7 @@ export { shutdownOpenTelemetry, flushOpenTelemetry, getLangfuseHealthStatus, set
|
|
|
178
178
|
export { clearAnalyticsMetrics, createAnalyticsMiddleware, getAnalyticsMetrics, } from "./middleware/builtin/analytics.js";
|
|
179
179
|
export { createLifecycleMiddleware } from "./middleware/builtin/lifecycle.js";
|
|
180
180
|
export { MiddlewareFactory } from "./middleware/factory.js";
|
|
181
|
+
export { clearGlobalMiddleware, configureGlobalMiddleware, } from "./middleware/defaults.js";
|
|
181
182
|
// Tool + schema helpers. Previously a consumer reached for `tool()` and
|
|
182
183
|
// `jsonSchema()` from the ai package to build tools for generate({tools}).
|
|
183
184
|
// That package is no longer a dependency, so the equivalents ship here.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { MiddlewareFactoryOptions } from "../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Middleware defaults.
|
|
4
|
+
*
|
|
5
|
+
* A generate()/stream() call resolves its middleware from up to four layers,
|
|
6
|
+
* lowest precedence first:
|
|
7
|
+
*
|
|
8
|
+
* 1. NEUROLINK_MIDDLEWARE_PRESET (process environment)
|
|
9
|
+
* 2. configureGlobalMiddleware() (module-level, set at runtime)
|
|
10
|
+
* 3. new NeuroLink({ middleware }) (per instance)
|
|
11
|
+
* 4. generate({ middleware }) (per call)
|
|
12
|
+
*
|
|
13
|
+
* A layer that is absent changes nothing, so a call made with no default set
|
|
14
|
+
* keeps exactly the middleware it passed.
|
|
15
|
+
*/
|
|
16
|
+
export declare const MIDDLEWARE_PRESET_ENV_VAR = "NEUROLINK_MIDDLEWARE_PRESET";
|
|
17
|
+
/**
|
|
18
|
+
* Layer `override` over `base`.
|
|
19
|
+
*
|
|
20
|
+
* - `preset`, `middleware`, `enabledMiddleware` and `disabledMiddleware`: the
|
|
21
|
+
* override's value replaces the base's whole; arrays are never concatenated.
|
|
22
|
+
* - `middlewareConfig`: merged per middleware id. Within an id, `enabled` and
|
|
23
|
+
* `conditions` come from the override when it sets them, and `config` is
|
|
24
|
+
* merged key by key, so a key the override sets replaces the base's value
|
|
25
|
+
* for that key as a whole (nested objects and arrays are not combined).
|
|
26
|
+
* - `global`: merged key by key.
|
|
27
|
+
*
|
|
28
|
+
* The factory applies `disabledMiddleware` after `enabledMiddleware`, so an id
|
|
29
|
+
* the override switches on must be taken out of a disabled list it inherits
|
|
30
|
+
* (and the reverse), or a lower layer's list would out-vote a call that asked
|
|
31
|
+
* for the opposite.
|
|
32
|
+
*/
|
|
33
|
+
export declare function mergeMiddlewareOptions(base: MiddlewareFactoryOptions, override: MiddlewareFactoryOptions): MiddlewareFactoryOptions;
|
|
34
|
+
/**
|
|
35
|
+
* Resolve the options a call runs with. `defaults` is ordered lowest
|
|
36
|
+
* precedence first; the per-call options sit on top.
|
|
37
|
+
*
|
|
38
|
+
* With no default present this returns `call` itself, so callers that never
|
|
39
|
+
* opted in see no change at all.
|
|
40
|
+
*/
|
|
41
|
+
export declare function resolveMiddlewareOptions(defaults: ReadonlyArray<MiddlewareFactoryOptions | undefined>, call: MiddlewareFactoryOptions | undefined): MiddlewareFactoryOptions | undefined;
|
|
42
|
+
/**
|
|
43
|
+
* Validate a default layer and copy it, so later edits to the caller's object
|
|
44
|
+
* cannot change a default that was already set. Plain data (a word list, a
|
|
45
|
+
* replacement string) is copied in full; custom middleware, callbacks and
|
|
46
|
+
* model handles keep their identity. Throws a TypeError for a value that is
|
|
47
|
+
* not an object and for a preset name that is not built in.
|
|
48
|
+
*/
|
|
49
|
+
export declare function snapshotMiddlewareOptions(options: MiddlewareFactoryOptions, source: string): MiddlewareFactoryOptions;
|
|
50
|
+
/**
|
|
51
|
+
* Process-wide middleware defaults. Applies to every NeuroLink instance, below
|
|
52
|
+
* the instance's own `middleware` option and above NEUROLINK_MIDDLEWARE_PRESET.
|
|
53
|
+
* Replaces any earlier call rather than adding to it, and is read on each
|
|
54
|
+
* generate()/stream(), so instances that already exist pick it up. Throws a
|
|
55
|
+
* TypeError when `options` is not an object or names a preset that is not
|
|
56
|
+
* built in.
|
|
57
|
+
*/
|
|
58
|
+
export declare function configureGlobalMiddleware(options: MiddlewareFactoryOptions): void;
|
|
59
|
+
/** Remove the defaults set by {@link configureGlobalMiddleware}. */
|
|
60
|
+
export declare function clearGlobalMiddleware(): void;
|
|
61
|
+
/** The current global defaults, or undefined when none are set. */
|
|
62
|
+
export declare function getGlobalMiddleware(): MiddlewareFactoryOptions | undefined;
|
|
63
|
+
/**
|
|
64
|
+
* The preset named by NEUROLINK_MIDDLEWARE_PRESET, as the lowest default layer.
|
|
65
|
+
* An unset or blank variable is no layer; an unrecognised name is reported
|
|
66
|
+
* on stderr once per distinct value and ignored.
|
|
67
|
+
*/
|
|
68
|
+
export declare function readEnvMiddlewarePreset(env?: NodeJS.ProcessEnv): MiddlewareFactoryOptions | undefined;
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { logger } from "../utils/logger.js";
|
|
2
|
+
import { BUILT_IN_MIDDLEWARE_PRESET_NAMES } from "./presets.js";
|
|
3
|
+
/**
|
|
4
|
+
* Middleware defaults.
|
|
5
|
+
*
|
|
6
|
+
* A generate()/stream() call resolves its middleware from up to four layers,
|
|
7
|
+
* lowest precedence first:
|
|
8
|
+
*
|
|
9
|
+
* 1. NEUROLINK_MIDDLEWARE_PRESET (process environment)
|
|
10
|
+
* 2. configureGlobalMiddleware() (module-level, set at runtime)
|
|
11
|
+
* 3. new NeuroLink({ middleware }) (per instance)
|
|
12
|
+
* 4. generate({ middleware }) (per call)
|
|
13
|
+
*
|
|
14
|
+
* A layer that is absent changes nothing, so a call made with no default set
|
|
15
|
+
* keeps exactly the middleware it passed.
|
|
16
|
+
*/
|
|
17
|
+
export const MIDDLEWARE_PRESET_ENV_VAR = "NEUROLINK_MIDDLEWARE_PRESET";
|
|
18
|
+
let globalMiddleware;
|
|
19
|
+
const warnedPresets = new Set();
|
|
20
|
+
const isPlainObject = (value) => typeof value === "object" &&
|
|
21
|
+
value !== null &&
|
|
22
|
+
!Array.isArray(value) &&
|
|
23
|
+
!(value instanceof Date) &&
|
|
24
|
+
!(value instanceof RegExp);
|
|
25
|
+
/** Own entries whose value is not `undefined`, so an unset key never wins. */
|
|
26
|
+
const definedEntries = (value) => Object.fromEntries(Object.entries(value ?? {}).filter(([, entry]) => entry !== undefined));
|
|
27
|
+
const isPureData = (value) => {
|
|
28
|
+
if (value === null || typeof value !== "object") {
|
|
29
|
+
return typeof value !== "function" && typeof value !== "symbol";
|
|
30
|
+
}
|
|
31
|
+
if (Array.isArray(value)) {
|
|
32
|
+
return value.every(isPureData);
|
|
33
|
+
}
|
|
34
|
+
const proto = Object.getPrototypeOf(value);
|
|
35
|
+
return ((proto === Object.prototype || proto === null) &&
|
|
36
|
+
Object.values(value).every(isPureData));
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Copy a value only when it is plain data (JSON-like, no functions or class
|
|
40
|
+
* instances anywhere inside). A model handle, a callback or a custom
|
|
41
|
+
* middleware keeps its identity, because copying one would split its state
|
|
42
|
+
* from the object the caller still holds.
|
|
43
|
+
*/
|
|
44
|
+
const copyIfPureData = (value) => isPureData(value) ? structuredClone(value) : value;
|
|
45
|
+
const isEmptyLayer = (layer) => Object.keys(definedEntries(layer)).length === 0;
|
|
46
|
+
const mergeConfigEntry = (base, override) => {
|
|
47
|
+
const merged = {
|
|
48
|
+
...definedEntries(base),
|
|
49
|
+
...definedEntries(override),
|
|
50
|
+
};
|
|
51
|
+
if (base?.config !== undefined || override?.config !== undefined) {
|
|
52
|
+
merged.config = {
|
|
53
|
+
...definedEntries(base?.config),
|
|
54
|
+
...definedEntries(override?.config),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
return merged;
|
|
58
|
+
};
|
|
59
|
+
/** Middleware ids this layer switches on or off, by any of the three routes. */
|
|
60
|
+
const idsSwitched = (layer, state) => {
|
|
61
|
+
const ids = new Set((state ? layer.enabledMiddleware : layer.disabledMiddleware) ?? []);
|
|
62
|
+
for (const [id, entry] of Object.entries(layer.middlewareConfig ?? {})) {
|
|
63
|
+
if (entry?.enabled === state) {
|
|
64
|
+
ids.add(id);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return ids;
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Layer `override` over `base`.
|
|
71
|
+
*
|
|
72
|
+
* - `preset`, `middleware`, `enabledMiddleware` and `disabledMiddleware`: the
|
|
73
|
+
* override's value replaces the base's whole; arrays are never concatenated.
|
|
74
|
+
* - `middlewareConfig`: merged per middleware id. Within an id, `enabled` and
|
|
75
|
+
* `conditions` come from the override when it sets them, and `config` is
|
|
76
|
+
* merged key by key, so a key the override sets replaces the base's value
|
|
77
|
+
* for that key as a whole (nested objects and arrays are not combined).
|
|
78
|
+
* - `global`: merged key by key.
|
|
79
|
+
*
|
|
80
|
+
* The factory applies `disabledMiddleware` after `enabledMiddleware`, so an id
|
|
81
|
+
* the override switches on must be taken out of a disabled list it inherits
|
|
82
|
+
* (and the reverse), or a lower layer's list would out-vote a call that asked
|
|
83
|
+
* for the opposite.
|
|
84
|
+
*/
|
|
85
|
+
export function mergeMiddlewareOptions(base, override) {
|
|
86
|
+
const merged = {};
|
|
87
|
+
const preset = override.preset ?? base.preset;
|
|
88
|
+
if (preset !== undefined) {
|
|
89
|
+
merged.preset = preset;
|
|
90
|
+
}
|
|
91
|
+
const custom = override.middleware ?? base.middleware;
|
|
92
|
+
if (custom !== undefined) {
|
|
93
|
+
merged.middleware = [...custom];
|
|
94
|
+
}
|
|
95
|
+
const switchedOn = idsSwitched(override, true);
|
|
96
|
+
const switchedOff = idsSwitched(override, false);
|
|
97
|
+
const enabled = override.enabledMiddleware ??
|
|
98
|
+
base.enabledMiddleware?.filter((id) => !switchedOff.has(id));
|
|
99
|
+
if (enabled !== undefined) {
|
|
100
|
+
merged.enabledMiddleware = [...enabled];
|
|
101
|
+
}
|
|
102
|
+
const disabled = override.disabledMiddleware ??
|
|
103
|
+
base.disabledMiddleware?.filter((id) => !switchedOn.has(id));
|
|
104
|
+
if (disabled !== undefined) {
|
|
105
|
+
merged.disabledMiddleware = [...disabled];
|
|
106
|
+
}
|
|
107
|
+
if (base.middlewareConfig || override.middlewareConfig) {
|
|
108
|
+
const ids = new Set([
|
|
109
|
+
...Object.keys(base.middlewareConfig ?? {}),
|
|
110
|
+
...Object.keys(override.middlewareConfig ?? {}),
|
|
111
|
+
]);
|
|
112
|
+
merged.middlewareConfig = Object.fromEntries([...ids].map((id) => [
|
|
113
|
+
id,
|
|
114
|
+
mergeConfigEntry(base.middlewareConfig?.[id], override.middlewareConfig?.[id]),
|
|
115
|
+
]));
|
|
116
|
+
}
|
|
117
|
+
if (base.global || override.global) {
|
|
118
|
+
merged.global = {
|
|
119
|
+
...definedEntries(base.global),
|
|
120
|
+
...definedEntries(override.global),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
return merged;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Resolve the options a call runs with. `defaults` is ordered lowest
|
|
127
|
+
* precedence first; the per-call options sit on top.
|
|
128
|
+
*
|
|
129
|
+
* With no default present this returns `call` itself, so callers that never
|
|
130
|
+
* opted in see no change at all.
|
|
131
|
+
*/
|
|
132
|
+
export function resolveMiddlewareOptions(defaults, call) {
|
|
133
|
+
const layers = defaults.filter((layer) => layer !== undefined && !isEmptyLayer(layer));
|
|
134
|
+
if (layers.length === 0) {
|
|
135
|
+
return call;
|
|
136
|
+
}
|
|
137
|
+
const inherited = layers.reduce((acc, layer) => mergeMiddlewareOptions(acc, layer), {});
|
|
138
|
+
return isPlainObject(call)
|
|
139
|
+
? mergeMiddlewareOptions(inherited, call)
|
|
140
|
+
: inherited;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Validate a default layer and copy it, so later edits to the caller's object
|
|
144
|
+
* cannot change a default that was already set. Plain data (a word list, a
|
|
145
|
+
* replacement string) is copied in full; custom middleware, callbacks and
|
|
146
|
+
* model handles keep their identity. Throws a TypeError for a value that is
|
|
147
|
+
* not an object and for a preset name that is not built in.
|
|
148
|
+
*/
|
|
149
|
+
export function snapshotMiddlewareOptions(options, source) {
|
|
150
|
+
if (!isPlainObject(options)) {
|
|
151
|
+
throw new TypeError(`${source}: middleware must be an object of MiddlewareFactoryOptions`);
|
|
152
|
+
}
|
|
153
|
+
if (options.preset !== undefined &&
|
|
154
|
+
!BUILT_IN_MIDDLEWARE_PRESET_NAMES.includes(options.preset)) {
|
|
155
|
+
// An unknown name left in the layer would replace a valid lower-layer
|
|
156
|
+
// preset and leave guardrails off with no sign of it, so a typo fails here.
|
|
157
|
+
throw new TypeError(`${source}: unknown middleware preset ${JSON.stringify(options.preset)}. Known presets: ${BUILT_IN_MIDDLEWARE_PRESET_NAMES.join(", ")}.`);
|
|
158
|
+
}
|
|
159
|
+
const merged = mergeMiddlewareOptions({}, options);
|
|
160
|
+
return {
|
|
161
|
+
...merged,
|
|
162
|
+
...(merged.middlewareConfig
|
|
163
|
+
? {
|
|
164
|
+
middlewareConfig: Object.fromEntries(Object.entries(merged.middlewareConfig).map(([id, entry]) => [
|
|
165
|
+
id,
|
|
166
|
+
{
|
|
167
|
+
...entry,
|
|
168
|
+
...(entry.config
|
|
169
|
+
? {
|
|
170
|
+
config: Object.fromEntries(Object.entries(entry.config).map(([key, value]) => [
|
|
171
|
+
key,
|
|
172
|
+
copyIfPureData(value),
|
|
173
|
+
])),
|
|
174
|
+
}
|
|
175
|
+
: {}),
|
|
176
|
+
...(entry.conditions
|
|
177
|
+
? { conditions: copyIfPureData(entry.conditions) }
|
|
178
|
+
: {}),
|
|
179
|
+
},
|
|
180
|
+
])),
|
|
181
|
+
}
|
|
182
|
+
: {}),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Process-wide middleware defaults. Applies to every NeuroLink instance, below
|
|
187
|
+
* the instance's own `middleware` option and above NEUROLINK_MIDDLEWARE_PRESET.
|
|
188
|
+
* Replaces any earlier call rather than adding to it, and is read on each
|
|
189
|
+
* generate()/stream(), so instances that already exist pick it up. Throws a
|
|
190
|
+
* TypeError when `options` is not an object or names a preset that is not
|
|
191
|
+
* built in.
|
|
192
|
+
*/
|
|
193
|
+
export function configureGlobalMiddleware(options) {
|
|
194
|
+
globalMiddleware = snapshotMiddlewareOptions(options, "configureGlobalMiddleware");
|
|
195
|
+
}
|
|
196
|
+
/** Remove the defaults set by {@link configureGlobalMiddleware}. */
|
|
197
|
+
export function clearGlobalMiddleware() {
|
|
198
|
+
globalMiddleware = undefined;
|
|
199
|
+
}
|
|
200
|
+
/** The current global defaults, or undefined when none are set. */
|
|
201
|
+
export function getGlobalMiddleware() {
|
|
202
|
+
return globalMiddleware;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* The preset named by NEUROLINK_MIDDLEWARE_PRESET, as the lowest default layer.
|
|
206
|
+
* An unset or blank variable is no layer; an unrecognised name is reported
|
|
207
|
+
* on stderr once per distinct value and ignored.
|
|
208
|
+
*/
|
|
209
|
+
export function readEnvMiddlewarePreset(env = process.env) {
|
|
210
|
+
const raw = env[MIDDLEWARE_PRESET_ENV_VAR]?.trim();
|
|
211
|
+
if (!raw) {
|
|
212
|
+
return undefined;
|
|
213
|
+
}
|
|
214
|
+
if (BUILT_IN_MIDDLEWARE_PRESET_NAMES.includes(raw)) {
|
|
215
|
+
return { preset: raw };
|
|
216
|
+
}
|
|
217
|
+
if (!warnedPresets.has(raw)) {
|
|
218
|
+
warnedPresets.add(raw);
|
|
219
|
+
// Written to stderr rather than through logger.warn, which drops warnings
|
|
220
|
+
// unless debugging: a mistyped preset would otherwise leave guardrails off
|
|
221
|
+
// with no sign of it.
|
|
222
|
+
logger.alwaysStderr(`[Middleware] Ignoring ${MIDDLEWARE_PRESET_ENV_VAR}=${JSON.stringify(raw)}: not a known preset. Known presets: ${BUILT_IN_MIDDLEWARE_PRESET_NAMES.join(", ")}.`);
|
|
223
|
+
}
|
|
224
|
+
return undefined;
|
|
225
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { MiddlewareRegistry } from "./registry.js";
|
|
2
|
+
import { createBuiltInMiddlewarePresets } from "./presets.js";
|
|
2
3
|
import { createAnalyticsMiddleware } from "./builtin/analytics.js";
|
|
3
4
|
import { createGuardrailsMiddleware } from "./builtin/guardrails.js";
|
|
4
5
|
import { createAutoEvaluationMiddleware } from "./builtin/autoEvaluation.js";
|
|
@@ -30,21 +31,9 @@ export class MiddlewareFactory {
|
|
|
30
31
|
lifecycle: createLifecycleMiddleware,
|
|
31
32
|
};
|
|
32
33
|
// Register built-in presets
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
config: { analytics: { enabled: true } },
|
|
37
|
-
});
|
|
38
|
-
this.registerPreset({
|
|
39
|
-
name: "all",
|
|
40
|
-
description: "Enables all available middleware.",
|
|
41
|
-
config: { analytics: { enabled: true }, guardrails: { enabled: true } },
|
|
42
|
-
});
|
|
43
|
-
this.registerPreset({
|
|
44
|
-
name: "security",
|
|
45
|
-
description: "Focuses on security with guardrails.",
|
|
46
|
-
config: { guardrails: { enabled: true } },
|
|
47
|
-
});
|
|
34
|
+
for (const preset of createBuiltInMiddlewarePresets()) {
|
|
35
|
+
this.registerPreset(preset);
|
|
36
|
+
}
|
|
48
37
|
// Register custom middleware if provided
|
|
49
38
|
logger.debug("Initializing MiddlewareFactory", { options });
|
|
50
39
|
if (options.middleware) {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { MiddlewarePreset } from "../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* The presets every `MiddlewareFactory` registers. Built fresh on each call so
|
|
4
|
+
* a factory that edits its own `presets` map cannot change another factory's.
|
|
5
|
+
*
|
|
6
|
+
* `all` is deliberately not "everything": autoEvaluation and lifecycle are
|
|
7
|
+
* opt-in per call and stay out of it. `security` only switches guardrails on;
|
|
8
|
+
* it ships no word list and no model filter, so it redacts nothing until the
|
|
9
|
+
* caller supplies `badWords` or `modelFilter`.
|
|
10
|
+
*/
|
|
11
|
+
export declare function createBuiltInMiddlewarePresets(): MiddlewarePreset[];
|
|
12
|
+
export declare const BUILT_IN_MIDDLEWARE_PRESET_NAMES: readonly string[];
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The presets every `MiddlewareFactory` registers. Built fresh on each call so
|
|
3
|
+
* a factory that edits its own `presets` map cannot change another factory's.
|
|
4
|
+
*
|
|
5
|
+
* `all` is deliberately not "everything": autoEvaluation and lifecycle are
|
|
6
|
+
* opt-in per call and stay out of it. `security` only switches guardrails on;
|
|
7
|
+
* it ships no word list and no model filter, so it redacts nothing until the
|
|
8
|
+
* caller supplies `badWords` or `modelFilter`.
|
|
9
|
+
*/
|
|
10
|
+
export function createBuiltInMiddlewarePresets() {
|
|
11
|
+
return [
|
|
12
|
+
{
|
|
13
|
+
name: "default",
|
|
14
|
+
description: "Default preset with analytics enabled.",
|
|
15
|
+
config: { analytics: { enabled: true } },
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: "all",
|
|
19
|
+
description: "Enables analytics and guardrails.",
|
|
20
|
+
config: { analytics: { enabled: true }, guardrails: { enabled: true } },
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: "security",
|
|
24
|
+
description: "Enables guardrails. Filters nothing until badWords or modelFilter is configured.",
|
|
25
|
+
config: { guardrails: { enabled: true } },
|
|
26
|
+
},
|
|
27
|
+
];
|
|
28
|
+
}
|
|
29
|
+
export const BUILT_IN_MIDDLEWARE_PRESET_NAMES = createBuiltInMiddlewarePresets().map((preset) => preset.name);
|
package/dist/neurolink.d.ts
CHANGED
|
@@ -159,6 +159,8 @@ export declare class NeuroLink {
|
|
|
159
159
|
/** Session-scoped pins of tools discovered via search_tools (tools.discovery mode). */
|
|
160
160
|
private discoveryPins;
|
|
161
161
|
private enableOrchestration;
|
|
162
|
+
private readonly envMiddleware?;
|
|
163
|
+
private readonly constructorMiddleware?;
|
|
162
164
|
private authProvider?;
|
|
163
165
|
private pendingAuthConfig?;
|
|
164
166
|
private authInitPromise?;
|
|
@@ -234,6 +236,13 @@ export declare class NeuroLink {
|
|
|
234
236
|
private enforceSessionBudget;
|
|
235
237
|
private assertInputText;
|
|
236
238
|
private applyAuthenticatedRequestContext;
|
|
239
|
+
/**
|
|
240
|
+
* Fold the instance, global and environment middleware defaults beneath the
|
|
241
|
+
* call's own `middleware`. Runs before the lifecycle helpers so they layer
|
|
242
|
+
* onFinish/onError on top of the resolved config rather than replacing it.
|
|
243
|
+
* With no default set the options are left untouched.
|
|
244
|
+
*/
|
|
245
|
+
private applyMiddlewareDefaults;
|
|
237
246
|
private applyGenerateLifecycleMiddleware;
|
|
238
247
|
private applyStreamLifecycleMiddleware;
|
|
239
248
|
private initializeMemoryConfig;
|
package/dist/neurolink.js
CHANGED
|
@@ -94,6 +94,7 @@ import { hasLifecycleErrorFired, markLifecycleErrorFired, markLifecycleStreamCal
|
|
|
94
94
|
import { interleaveTTSStream } from "./utils/ttsStream.js";
|
|
95
95
|
import { resolveLifecycleTimeoutMs } from "./utils/lifecycleTimeout.js";
|
|
96
96
|
import { cloneOptionsForCallIsolation } from "./utils/cloneOptions.js";
|
|
97
|
+
import { getGlobalMiddleware, readEnvMiddlewarePreset, resolveMiddlewareOptions, snapshotMiddlewareOptions, } from "./middleware/defaults.js";
|
|
97
98
|
import { preserveLiveStreamAccessors } from "./utils/streamResultAccessors.js";
|
|
98
99
|
import { coerceJsonToSchema, recoverScalarRoot, schemaAccepts, } from "./utils/json/coerce.js";
|
|
99
100
|
// Factory processing imports
|
|
@@ -655,6 +656,12 @@ export class NeuroLink {
|
|
|
655
656
|
discoveryPins = new Map();
|
|
656
657
|
// Add orchestration property
|
|
657
658
|
enableOrchestration;
|
|
659
|
+
// Language-model middleware defaults, lowest precedence first: the layer
|
|
660
|
+
// read from NEUROLINK_MIDDLEWARE_PRESET, then the constructor's `middleware`
|
|
661
|
+
// option. The module-level global registry sits between them and is read on
|
|
662
|
+
// each call, so it can change after construction. See middleware/defaults.ts.
|
|
663
|
+
envMiddleware;
|
|
664
|
+
constructorMiddleware;
|
|
658
665
|
// Authentication provider for secure access control
|
|
659
666
|
authProvider;
|
|
660
667
|
pendingAuthConfig;
|
|
@@ -900,6 +907,18 @@ export class NeuroLink {
|
|
|
900
907
|
...tokenDerivedFields,
|
|
901
908
|
};
|
|
902
909
|
}
|
|
910
|
+
/**
|
|
911
|
+
* Fold the instance, global and environment middleware defaults beneath the
|
|
912
|
+
* call's own `middleware`. Runs before the lifecycle helpers so they layer
|
|
913
|
+
* onFinish/onError on top of the resolved config rather than replacing it.
|
|
914
|
+
* With no default set the options are left untouched.
|
|
915
|
+
*/
|
|
916
|
+
applyMiddlewareDefaults(options) {
|
|
917
|
+
const resolved = resolveMiddlewareOptions([this.envMiddleware, getGlobalMiddleware(), this.constructorMiddleware], options.middleware);
|
|
918
|
+
if (resolved !== options.middleware) {
|
|
919
|
+
options.middleware = resolved;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
903
922
|
applyGenerateLifecycleMiddleware(options) {
|
|
904
923
|
if (!options.onFinish && !options.onError) {
|
|
905
924
|
return;
|
|
@@ -1207,6 +1226,13 @@ export class NeuroLink {
|
|
|
1207
1226
|
return metricsTraceContextStorage.getStore() ?? null;
|
|
1208
1227
|
}
|
|
1209
1228
|
constructor(config) {
|
|
1229
|
+
// Validated first: an invalid default must throw before the constructor
|
|
1230
|
+
// starts telemetry or any other process-wide state.
|
|
1231
|
+
this.envMiddleware = readEnvMiddlewarePreset();
|
|
1232
|
+
this.constructorMiddleware =
|
|
1233
|
+
config?.middleware === undefined
|
|
1234
|
+
? undefined
|
|
1235
|
+
: snapshotMiddlewareOptions(config.middleware, "NeuroLink constructor");
|
|
1210
1236
|
if (config?.toolRegistry) {
|
|
1211
1237
|
// A supplied registry (a host's, shared with a worker) keeps its owner's
|
|
1212
1238
|
// roots; per-call executeTool() binds this instance's policy anyway.
|
|
@@ -4157,6 +4183,7 @@ Current user's request: ${currentInput}`;
|
|
|
4157
4183
|
options.input.text = piiResult.text;
|
|
4158
4184
|
}
|
|
4159
4185
|
this.enforceSessionBudget(options.maxBudgetUsd);
|
|
4186
|
+
this.applyMiddlewareDefaults(options);
|
|
4160
4187
|
this.applyGenerateLifecycleMiddleware(options);
|
|
4161
4188
|
await this.applyAuthenticatedRequestContext(options);
|
|
4162
4189
|
return { options, originalPrompt };
|
|
@@ -5470,6 +5497,9 @@ Current user's request: ${currentInput}`;
|
|
|
5470
5497
|
// NL-004: Resolve model aliases/deprecations before processing
|
|
5471
5498
|
isolated.model = resolveModel(isolated.model, this.modelAliasConfig);
|
|
5472
5499
|
bindFileToolRoots(isolated, this.resolveFileToolRoots(isolated.toolRoots));
|
|
5500
|
+
// generateText() reaches the same providers as generate() without passing
|
|
5501
|
+
// through its prelude, so the middleware defaults are folded in here too.
|
|
5502
|
+
this.applyMiddlewareDefaults(isolated);
|
|
5473
5503
|
// Use internal generation method directly
|
|
5474
5504
|
return await this.generateTextInternal(isolated);
|
|
5475
5505
|
}
|
|
@@ -8291,6 +8321,7 @@ Current user's request: ${currentInput}`;
|
|
|
8291
8321
|
this.enforceSessionBudget(options.maxBudgetUsd);
|
|
8292
8322
|
await this.applyAuthenticatedRequestContext(options);
|
|
8293
8323
|
this.emitStreamStartEvents(options, startTime);
|
|
8324
|
+
this.applyMiddlewareDefaults(options);
|
|
8294
8325
|
this.applyStreamLifecycleMiddleware(options);
|
|
8295
8326
|
}
|
|
8296
8327
|
async maybeHandleWorkflowStreamRequest(params) {
|
package/dist/types/config.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ import type { ContextRelevanceOptions } from "./context.js";
|
|
|
17
17
|
import type { DecisionHooks } from "./decision.js";
|
|
18
18
|
import type { SkillsConfig } from "./skills.js";
|
|
19
19
|
import type { KnowledgeGroundingConfig } from "./knowledge.js";
|
|
20
|
+
import type { MiddlewareFactoryOptions } from "./middleware.js";
|
|
20
21
|
/**
|
|
21
22
|
* Main NeuroLink configuration type
|
|
22
23
|
*/
|
|
@@ -184,6 +185,27 @@ export type NeurolinkConstructorConfig = {
|
|
|
184
185
|
* {@link KnowledgeGroundingConfig}.
|
|
185
186
|
*/
|
|
186
187
|
knowledgeGrounding?: KnowledgeGroundingConfig;
|
|
188
|
+
/**
|
|
189
|
+
* Language-model middleware applied to every generate()/stream() on this
|
|
190
|
+
* instance (guardrails, analytics, custom middleware). Spares each call from
|
|
191
|
+
* repeating the same `middleware` option.
|
|
192
|
+
*
|
|
193
|
+
* Precedence, lowest to highest: `NEUROLINK_MIDDLEWARE_PRESET`,
|
|
194
|
+
* `configureGlobalMiddleware()`, this option, then the call's own
|
|
195
|
+
* `middleware`. `middlewareConfig` merges per middleware id, and a key set
|
|
196
|
+
* by a higher layer replaces the lower layer's value; arrays are replaced,
|
|
197
|
+
* never concatenated. A call can switch off an inherited middleware with
|
|
198
|
+
* `disabledMiddleware` or `middlewareConfig.<id>.enabled: false`.
|
|
199
|
+
*
|
|
200
|
+
* Throws a TypeError at construction when this is not an object or its
|
|
201
|
+
* `preset` is not a built-in preset (`default`, `all` or `security`).
|
|
202
|
+
*
|
|
203
|
+
* Unset by default, in which case calls behave exactly as before. Note that
|
|
204
|
+
* `preset: "security"` only turns guardrails on: nothing is redacted until
|
|
205
|
+
* `middlewareConfig.guardrails.config` supplies `badWords` or `modelFilter`.
|
|
206
|
+
* See {@link MiddlewareFactoryOptions}.
|
|
207
|
+
*/
|
|
208
|
+
middleware?: MiddlewareFactoryOptions;
|
|
187
209
|
};
|
|
188
210
|
/**
|
|
189
211
|
* Configuration for MCP enhancement modules wired into generate()/stream() paths.
|
package/dist/types/livekit.d.ts
CHANGED
|
@@ -421,30 +421,52 @@ export type LiveKitServerCredentials = {
|
|
|
421
421
|
apiKey: string;
|
|
422
422
|
apiSecret: string;
|
|
423
423
|
};
|
|
424
|
+
/**
|
|
425
|
+
* The realtime speech-to-speech model a call runs on: Gemini Live on Vertex
|
|
426
|
+
* (the default) or xAI Grok Voice through LiveKit's xAI plugin.
|
|
427
|
+
*/
|
|
428
|
+
export type RealtimeVoiceBackend = "gemini" | "xai";
|
|
429
|
+
/**
|
|
430
|
+
* A LiveKit realtime model as far as callers of the model factory can rely on.
|
|
431
|
+
* It is declared structurally so the published declarations name no optional
|
|
432
|
+
* LiveKit plugin; at runtime it is a `RealtimeModel` subclass of the backend's
|
|
433
|
+
* plugin.
|
|
434
|
+
*/
|
|
435
|
+
export type RealtimeLlm = {
|
|
436
|
+
readonly model: string;
|
|
437
|
+
label: () => string;
|
|
438
|
+
close: () => Promise<void>;
|
|
439
|
+
};
|
|
424
440
|
/**
|
|
425
441
|
* Realtime voice configuration resolved from the environment.
|
|
426
442
|
*
|
|
427
|
-
* In speech-to-speech mode one realtime model (Gemini Live on Vertex
|
|
428
|
-
* reasoning, TTS, and turn detection — so there is no
|
|
429
|
-
* config. `resolveRealtimeVoiceConfig` fills every
|
|
430
|
-
* (with defaults); `RealtimeVoiceAgentConfig` lets a
|
|
443
|
+
* In speech-to-speech mode one realtime model (Gemini Live on Vertex, or xAI
|
|
444
|
+
* Grok Voice) does STT, reasoning, TTS, and turn detection — so there is no
|
|
445
|
+
* separate STT/TTS/VAD/EOU config. `resolveRealtimeVoiceConfig` fills every
|
|
446
|
+
* field from `process.env` (with defaults); `RealtimeVoiceAgentConfig` lets a
|
|
447
|
+
* caller override any of them. The API key of the xai backend is deliberately
|
|
448
|
+
* not part of the config: the xAI plugin reads `XAI_API_KEY` itself.
|
|
431
449
|
*/
|
|
432
450
|
export type RealtimeVoiceConfig = {
|
|
433
|
-
/**
|
|
451
|
+
/** Which realtime model family handles the call (VOICE_REALTIME_BACKEND; default "gemini"). */
|
|
452
|
+
backend: RealtimeVoiceBackend;
|
|
453
|
+
/** Vertex project id (from VERTEX_PROJECT / GOOGLE_AUTH_* / GOOGLE_CLOUD_PROJECT_ID). Gemini only. */
|
|
434
454
|
project: string | undefined;
|
|
435
|
-
/** Vertex location; native-audio Live is served on `global`, not regionally. */
|
|
455
|
+
/** Vertex location; native-audio Live is served on `global`, not regionally. Gemini only. */
|
|
436
456
|
location: string;
|
|
437
|
-
/** Realtime model id (e.g. "gemini-live-2.5-flash"). */
|
|
457
|
+
/** Realtime model id (e.g. "gemini-live-2.5-flash" for gemini, "grok-voice-3.0" for xai). */
|
|
438
458
|
model: string;
|
|
439
|
-
/** Optional
|
|
459
|
+
/** Optional voice name for either backend; omit for the plugin default. */
|
|
440
460
|
voice: string | undefined;
|
|
441
|
-
/**
|
|
461
|
+
/** xai only: WebSocket base URL of the realtime endpoint (XAI_REALTIME_BASE_URL); unset keeps the plugin's own endpoint. */
|
|
462
|
+
xaiBaseURL: string | undefined;
|
|
463
|
+
/** Response modality: "AUDIO" (native S2S) or "TEXT" (half-cascade). Gemini only. */
|
|
442
464
|
responseModality: string;
|
|
443
465
|
/** System prompt / instructions for the agent. */
|
|
444
466
|
systemPrompt: string;
|
|
445
467
|
/** Opening line the agent speaks on connect ("" disables). */
|
|
446
468
|
greeting: string;
|
|
447
|
-
/** Whether to bridge Lighthouse MCP tools as
|
|
469
|
+
/** Whether to bridge Lighthouse MCP tools as function tools of the realtime model (either backend). */
|
|
448
470
|
toolsEnabled: boolean;
|
|
449
471
|
/** Full URL of the MCP server the tools are bridged from. */
|
|
450
472
|
mcpUrl: string;
|
|
@@ -474,10 +496,14 @@ export type RealtimeVoiceLogContext = {
|
|
|
474
496
|
* everything via env.
|
|
475
497
|
*/
|
|
476
498
|
export type RealtimeVoiceAgentConfig = {
|
|
499
|
+
/** Realtime backend; omitted means the environment's VOICE_REALTIME_BACKEND, and without it "gemini". */
|
|
500
|
+
backend?: RealtimeVoiceBackend;
|
|
477
501
|
project?: string;
|
|
478
502
|
location?: string;
|
|
479
503
|
model?: string;
|
|
480
504
|
voice?: string;
|
|
505
|
+
/** xai only: WebSocket base URL of the realtime endpoint. */
|
|
506
|
+
xaiBaseURL?: string;
|
|
481
507
|
responseModality?: string;
|
|
482
508
|
systemPrompt?: string;
|
|
483
509
|
greeting?: string;
|
|
@@ -736,7 +762,7 @@ export type BuildRealtimeMcpToolsParams = {
|
|
|
736
762
|
* Hard cap per MCP tool call, in milliseconds (default 30000).
|
|
737
763
|
*
|
|
738
764
|
* Without one, a stalled MCP server holds the realtime turn open forever:
|
|
739
|
-
*
|
|
765
|
+
* The realtime model waits on the function result, so the user gets silence rather than
|
|
740
766
|
* an error. Bounding the call turns that into a normal tool failure the
|
|
741
767
|
* model can talk about.
|
|
742
768
|
*/
|