@juspay/neurolink 13.4.5 → 13.4.7
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/types/livekit.d.ts +1 -1
- package/dist/voice/livekit/config.d.ts +13 -1
- package/dist/voice/livekit/config.js +44 -7
- package/dist/voice/livekit/realtimeModel.d.ts +3 -1
- package/dist/voice/livekit/realtimeModel.js +9 -1
- package/docs-site/static/search-index.json +3030 -3634
- package/package.json +9 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
## [13.4.
|
|
1
|
+
## [13.4.7](https://github.com/juspay/neurolink/compare/v13.4.6...v13.4.7) (2026-10-18)
|
|
2
2
|
|
|
3
3
|
### Bug Fixes
|
|
4
4
|
|
|
5
|
-
- **(
|
|
5
|
+
- **(search-index):** read comment lines inside code fences as code, not headings ([45557b3](https://github.com/juspay/neurolink/commit/45557b3bef6fdcc05105883b6ac7fec2ef0bbb37))
|
|
6
6
|
|
|
7
7
|
## [11.2.3](https://github.com/juspay/neurolink/compare/v11.2.2...v11.2.3) (2026-08-19)
|
|
8
8
|
|
package/dist/types/livekit.d.ts
CHANGED
|
@@ -502,7 +502,7 @@ export type RealtimeVoiceAgentConfig = {
|
|
|
502
502
|
location?: string;
|
|
503
503
|
model?: string;
|
|
504
504
|
voice?: string;
|
|
505
|
-
/** xai only: WebSocket base URL of the realtime endpoint. */
|
|
505
|
+
/** xai only: WebSocket base URL of the realtime endpoint. A `ws://` URL to a host other than localhost, 127.0.0.0/8 or [::1] is refused unless `XAI_REALTIME_ALLOW_INSECURE=true`, because the xAI key would cross the network unencrypted. */
|
|
506
506
|
xaiBaseURL?: string;
|
|
507
507
|
responseModality?: string;
|
|
508
508
|
systemPrompt?: string;
|
|
@@ -23,6 +23,15 @@ export declare function resolveLiveKitServerConfig(): LiveKitServerConfig;
|
|
|
23
23
|
* `VOICE_LLM_MODEL`.
|
|
24
24
|
*/
|
|
25
25
|
export declare function resolveBrainDefaults(): LiveKitBrainDefaults;
|
|
26
|
+
/**
|
|
27
|
+
* Refuse a `ws://` URL to a host other than loopback unless
|
|
28
|
+
* `XAI_REALTIME_ALLOW_INSECURE=true`: the xAI plugin sends `XAI_API_KEY` to the
|
|
29
|
+
* URL it is given, and `ws://` sends it unencrypted. Anything else passes,
|
|
30
|
+
* including a value that is not a ws or wss URL, which is for the caller's own
|
|
31
|
+
* check. The error names the setting and the opt-in and does not repeat the
|
|
32
|
+
* value.
|
|
33
|
+
*/
|
|
34
|
+
export declare function requireNoCleartextCrossHost(value: string, name: string): void;
|
|
26
35
|
/**
|
|
27
36
|
* Resolve the realtime (speech-to-speech) voice configuration from the
|
|
28
37
|
* environment.
|
|
@@ -31,7 +40,10 @@ export declare function resolveBrainDefaults(): LiveKitBrainDefaults;
|
|
|
31
40
|
* env at all. `VOICE_REALTIME_BACKEND` picks the model family (`gemini`, the
|
|
32
41
|
* default, or `xai`); `backend` here overrides it and then the environment's
|
|
33
42
|
* value is not read. A non-blank `xaiBaseURL` here likewise replaces
|
|
34
|
-
* `XAI_REALTIME_BASE_URL` after the same ws or wss check.
|
|
43
|
+
* `XAI_REALTIME_BASE_URL` after the same ws or wss check. Either URL is
|
|
44
|
+
* checked whatever the backend. A `ws://` URL to a host other than `localhost`,
|
|
45
|
+
* `127.0.0.0/8` or `[::1]` is refused unless `XAI_REALTIME_ALLOW_INSECURE=true`,
|
|
46
|
+
* because the xAI plugin sends `XAI_API_KEY` to it unencrypted.
|
|
35
47
|
* `VOICE_LIVE_MODEL` overrides the backend's default model.
|
|
36
48
|
* Vertex `project` is resolved from `VERTEX_PROJECT`, then the service-account
|
|
37
49
|
* project (`GOOGLE_AUTH_BREEZE_PROJECT_ID`), then `GOOGLE_CLOUD_PROJECT_ID`;
|
|
@@ -77,6 +77,7 @@ const DEFAULT_SYSTEM_PROMPT = "You are a concise voice assistant for a merchant
|
|
|
77
77
|
const DEFAULT_GREETING = "Briefly greet the merchant and ask how you can help with their store today.";
|
|
78
78
|
const DEFAULT_LIGHTHOUSE_URL = "http://localhost:5173";
|
|
79
79
|
const DEFAULT_MCP_PATH = "/ai/mcp/v2";
|
|
80
|
+
const ALLOW_INSECURE_ENV = "XAI_REALTIME_ALLOW_INSECURE";
|
|
80
81
|
/**
|
|
81
82
|
* The backend named by `VOICE_REALTIME_BACKEND`: `gemini` or `xai`, matched
|
|
82
83
|
* case-insensitively, with blank or absent meaning `gemini`. The error names the
|
|
@@ -100,20 +101,53 @@ function requireKnownBackend(value) {
|
|
|
100
101
|
}
|
|
101
102
|
throw new Error('The realtime voice backend must be "gemini" or "xai"');
|
|
102
103
|
}
|
|
103
|
-
function
|
|
104
|
+
function parseWebSocketUrl(value) {
|
|
104
105
|
try {
|
|
105
|
-
const
|
|
106
|
-
return protocol === "ws:" || protocol === "wss:";
|
|
106
|
+
const url = new URL(value);
|
|
107
|
+
return url.protocol === "ws:" || url.protocol === "wss:" ? url : undefined;
|
|
107
108
|
}
|
|
108
109
|
catch {
|
|
109
|
-
return
|
|
110
|
+
return undefined;
|
|
110
111
|
}
|
|
111
112
|
}
|
|
112
|
-
/**
|
|
113
|
+
/**
|
|
114
|
+
* Whether a WHATWG-parsed hostname is `localhost`, an address in `127.0.0.0/8`
|
|
115
|
+
* or `[::1]`. The parser has already lowercased the name and reduced shorthand
|
|
116
|
+
* IPv4 forms (`127.1`, `0x7f.1`) to dotted decimal, and a userinfo prefix such
|
|
117
|
+
* as `localhost@host` never reaches `hostname`, so neither can pass for a
|
|
118
|
+
* loopback host.
|
|
119
|
+
*/
|
|
120
|
+
function isLoopbackHost(hostname) {
|
|
121
|
+
if (hostname === "localhost" || hostname === "[::1]") {
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
const octets = hostname.split(".");
|
|
125
|
+
return (octets.length === 4 &&
|
|
126
|
+
octets[0] === "127" &&
|
|
127
|
+
octets.every((octet) => /^\d{1,3}$/.test(octet) && Number(octet) <= 255));
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Refuse a `ws://` URL to a host other than loopback unless
|
|
131
|
+
* `XAI_REALTIME_ALLOW_INSECURE=true`: the xAI plugin sends `XAI_API_KEY` to the
|
|
132
|
+
* URL it is given, and `ws://` sends it unencrypted. Anything else passes,
|
|
133
|
+
* including a value that is not a ws or wss URL, which is for the caller's own
|
|
134
|
+
* check. The error names the setting and the opt-in and does not repeat the
|
|
135
|
+
* value.
|
|
136
|
+
*/
|
|
137
|
+
export function requireNoCleartextCrossHost(value, name) {
|
|
138
|
+
const url = parseWebSocketUrl(value);
|
|
139
|
+
if (url?.protocol === "ws:" &&
|
|
140
|
+
!isLoopbackHost(url.hostname) &&
|
|
141
|
+
readEnv(ALLOW_INSECURE_ENV, "").toLowerCase() !== "true") {
|
|
142
|
+
throw new Error(`${name} is a ws:// URL to a host other than localhost, 127.0.0.0/8 or [::1], which would send XAI_API_KEY unencrypted. Use wss://, or set ${ALLOW_INSECURE_ENV}=true to accept that`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/** A ws or wss URL that passes `requireNoCleartextCrossHost`. The error names the setting and does not repeat the value. */
|
|
113
146
|
function requireWebSocketUrl(value, name) {
|
|
114
|
-
if (
|
|
147
|
+
if (parseWebSocketUrl(value) === undefined) {
|
|
115
148
|
throw new Error(`${name} must be a ws:// or wss:// URL`);
|
|
116
149
|
}
|
|
150
|
+
requireNoCleartextCrossHost(value, name);
|
|
117
151
|
return value;
|
|
118
152
|
}
|
|
119
153
|
/** `XAI_REALTIME_BASE_URL`, which must be a ws or wss URL when set. */
|
|
@@ -136,7 +170,10 @@ function resolveXaiBaseUrl(override) {
|
|
|
136
170
|
* env at all. `VOICE_REALTIME_BACKEND` picks the model family (`gemini`, the
|
|
137
171
|
* default, or `xai`); `backend` here overrides it and then the environment's
|
|
138
172
|
* value is not read. A non-blank `xaiBaseURL` here likewise replaces
|
|
139
|
-
* `XAI_REALTIME_BASE_URL` after the same ws or wss check.
|
|
173
|
+
* `XAI_REALTIME_BASE_URL` after the same ws or wss check. Either URL is
|
|
174
|
+
* checked whatever the backend. A `ws://` URL to a host other than `localhost`,
|
|
175
|
+
* `127.0.0.0/8` or `[::1]` is refused unless `XAI_REALTIME_ALLOW_INSECURE=true`,
|
|
176
|
+
* because the xAI plugin sends `XAI_API_KEY` to it unencrypted.
|
|
140
177
|
* `VOICE_LIVE_MODEL` overrides the backend's default model.
|
|
141
178
|
* Vertex `project` is resolved from `VERTEX_PROJECT`, then the service-account
|
|
142
179
|
* project (`GOOGLE_AUTH_BREEZE_PROJECT_ID`), then `GOOGLE_CLOUD_PROJECT_ID`;
|
|
@@ -28,6 +28,8 @@ export declare function requireXaiApiKey(): void;
|
|
|
28
28
|
* The gemini branch builds exactly what the agent has always built: Vertex AI,
|
|
29
29
|
* the configured model, transcripts of both sides, and project, location,
|
|
30
30
|
* voice and modality when set. The xai branch needs `XAI_API_KEY` in the
|
|
31
|
-
* environment and throws before touching the plugin when it is missing.
|
|
31
|
+
* environment and throws before touching the plugin when it is missing. It also
|
|
32
|
+
* throws before touching the plugin for a `ws://` base URL to a host other than
|
|
33
|
+
* loopback, unless `XAI_REALTIME_ALLOW_INSECURE=true`, as config resolution does.
|
|
32
34
|
*/
|
|
33
35
|
export declare function createRealtimeLlm(cfg: RealtimeVoiceConfig): Promise<RealtimeLlm>;
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
*
|
|
14
14
|
* See docs/features/livekit-voice-agent.md.
|
|
15
15
|
*/
|
|
16
|
+
import { requireNoCleartextCrossHost } from "./config.js";
|
|
16
17
|
/**
|
|
17
18
|
* Fail early, naming the variable, when `XAI_API_KEY` is blank or absent.
|
|
18
19
|
*
|
|
@@ -53,6 +54,11 @@ async function createGeminiRealtimeLlm(cfg) {
|
|
|
53
54
|
}
|
|
54
55
|
async function createXaiRealtimeLlm(cfg) {
|
|
55
56
|
requireXaiApiKey();
|
|
57
|
+
// `cfg` can be built by hand, without `resolveRealtimeVoiceConfig`, so the
|
|
58
|
+
// key-leak check runs here as well as there.
|
|
59
|
+
if (cfg.xaiBaseURL) {
|
|
60
|
+
requireNoCleartextCrossHost(cfg.xaiBaseURL, "xaiBaseURL");
|
|
61
|
+
}
|
|
56
62
|
const xai = await import("@livekit/agents-plugin-xai");
|
|
57
63
|
// Keys are added only when set. The plugin spreads these options over its
|
|
58
64
|
// own defaults, so a key holding `undefined` would erase the default: a
|
|
@@ -75,7 +81,9 @@ async function createXaiRealtimeLlm(cfg) {
|
|
|
75
81
|
* The gemini branch builds exactly what the agent has always built: Vertex AI,
|
|
76
82
|
* the configured model, transcripts of both sides, and project, location,
|
|
77
83
|
* voice and modality when set. The xai branch needs `XAI_API_KEY` in the
|
|
78
|
-
* environment and throws before touching the plugin when it is missing.
|
|
84
|
+
* environment and throws before touching the plugin when it is missing. It also
|
|
85
|
+
* throws before touching the plugin for a `ws://` base URL to a host other than
|
|
86
|
+
* loopback, unless `XAI_REALTIME_ALLOW_INSECURE=true`, as config resolution does.
|
|
79
87
|
*/
|
|
80
88
|
export async function createRealtimeLlm(cfg) {
|
|
81
89
|
switch (cfg.backend) {
|