@juspay/neurolink 13.4.4 → 13.4.6
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/README.md +34 -16
- package/dist/browser/neurolink.min.js +1 -1
- package/dist/cli/commands/decide.js +1 -1
- package/dist/constants/enums.d.ts +2 -0
- package/dist/constants/enums.js +2 -0
- package/dist/hitl/hitlManager.js +1 -1
- package/dist/neurolink.d.ts +2 -2
- package/dist/neurolink.js +17 -4
- package/dist/providers/catalog/xor.json +23 -9
- package/dist/types/hitl.d.ts +3 -3
- 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 +47 -47
- package/package.json +9 -8
|
@@ -221,7 +221,7 @@ export const decideCommand = {
|
|
|
221
221
|
})
|
|
222
222
|
.example('$0 decide "Refund request for a damaged item" --questions \'{"urgent":{"type":"boolean","instructions":"Is this urgent?"}}\'', "Ask a single yes/no question")
|
|
223
223
|
.example("$0 decide --state-file ticket.json --questions-file questions.json --format json", "Read state and questions from files, emit raw JSON")
|
|
224
|
-
.example('$0 decide "What color is this?" --provider xor --image ./photo.png --questions \'{"color":{"type":"choice","instructions":"What color is the image?","criteria":{"red":"red","blue":"blue"}}}\'', "Ask about an image (XOR, Perplexity, Cloudflare Clef and OpenAI Decisions read images)"),
|
|
224
|
+
.example('$0 decide "What color is this?" --provider xor --model xor-omni --image ./photo.png --questions \'{"color":{"type":"choice","instructions":"What color is the image?","criteria":{"red":"red","blue":"blue"}}}\'', "Ask about an image (XOR with its xor-omni model, Perplexity, Cloudflare Clef and OpenAI Decisions read images)"),
|
|
225
225
|
handler: async (argv) => {
|
|
226
226
|
const outputFormat = argv.format ?? "text";
|
|
227
227
|
// --- Validate before any provider work ---------------------------------
|
|
@@ -1777,6 +1777,8 @@ export declare enum XiaomiMimoModels {
|
|
|
1777
1777
|
MIMO_V2_6_FLASH = "mimo-v2.6-flash"
|
|
1778
1778
|
}
|
|
1779
1779
|
export declare enum XorModels {
|
|
1780
|
+
XOR_LATEST = "xor-latest",
|
|
1781
|
+
XOR_OMNI = "xor-omni",
|
|
1780
1782
|
XOR_1_1 = "xor-1.1"
|
|
1781
1783
|
}
|
|
1782
1784
|
export declare enum ZAiModels {
|
package/dist/constants/enums.js
CHANGED
|
@@ -2104,6 +2104,8 @@ export var XiaomiMimoModels;
|
|
|
2104
2104
|
})(XiaomiMimoModels || (XiaomiMimoModels = {}));
|
|
2105
2105
|
export var XorModels;
|
|
2106
2106
|
(function (XorModels) {
|
|
2107
|
+
XorModels["XOR_LATEST"] = "xor-latest";
|
|
2108
|
+
XorModels["XOR_OMNI"] = "xor-omni";
|
|
2107
2109
|
XorModels["XOR_1_1"] = "xor-1.1";
|
|
2108
2110
|
})(XorModels || (XorModels = {}));
|
|
2109
2111
|
export var ZAiModels;
|
package/dist/hitl/hitlManager.js
CHANGED
|
@@ -49,7 +49,7 @@ export class HITLManager extends EventEmitter {
|
|
|
49
49
|
dangerousActions: config.dangerousActions,
|
|
50
50
|
timeout: config.timeout ?? DEFAULT_TIMEOUT, // Default: 30 seconds
|
|
51
51
|
confirmationMethod: config.confirmationMethod ?? "event", // Default: "event"
|
|
52
|
-
allowArgumentModification: config.allowArgumentModification ?? DEFAULT_ALLOW_MODIFICATION, // Default:
|
|
52
|
+
allowArgumentModification: config.allowArgumentModification ?? DEFAULT_ALLOW_MODIFICATION, // Default: false
|
|
53
53
|
autoApproveOnTimeout: config.autoApproveOnTimeout ?? false, // Default: false (safe)
|
|
54
54
|
autoApproveExclusions: Array.isArray(config.autoApproveExclusions)
|
|
55
55
|
? [...config.autoApproveExclusions]
|
package/dist/neurolink.d.ts
CHANGED
|
@@ -334,9 +334,9 @@ export declare class NeuroLink {
|
|
|
334
334
|
* @param config.enableOrchestration - Whether to enable smart model orchestration (default: false)
|
|
335
335
|
* @param config.hitl - Configuration for Human-in-the-Loop safety features
|
|
336
336
|
* @param config.hitl.enabled - Whether to enable HITL tool confirmation (default: false)
|
|
337
|
-
* @param config.hitl.dangerousActions - Keywords that trigger confirmation
|
|
337
|
+
* @param config.hitl.dangerousActions - Keywords that trigger confirmation; required when HITL is enabled
|
|
338
338
|
* @param config.hitl.timeout - Confirmation timeout in milliseconds (default: 30000)
|
|
339
|
-
* @param config.hitl.allowArgumentModification -
|
|
339
|
+
* @param config.hitl.allowArgumentModification - Advertised to the approval UI as `allowModification` (default: false); not enforced, a response's `modifiedArguments` is applied either way
|
|
340
340
|
* @param config.toolRegistry - Optional tool registry instance for advanced use cases (default: new MCPToolRegistry())
|
|
341
341
|
*
|
|
342
342
|
* @example
|
package/dist/neurolink.js
CHANGED
|
@@ -242,13 +242,26 @@ const MEMORY_WRITE_TIMEOUT_MS = 30_000;
|
|
|
242
242
|
*
|
|
243
243
|
* @example With HITL security
|
|
244
244
|
* ```typescript
|
|
245
|
+
* import type { ConfirmationRequestEvent } from '@juspay/neurolink';
|
|
246
|
+
*
|
|
245
247
|
* const neurolink = new NeuroLink({
|
|
246
248
|
* hitl: {
|
|
247
249
|
* enabled: true,
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
+
* dangerousActions: ['writeFile', 'executeCode'],
|
|
251
|
+
* timeout: 30000
|
|
250
252
|
* }
|
|
251
253
|
* });
|
|
254
|
+
*
|
|
255
|
+
* // Approval is event-based; answer each request with the matching id
|
|
256
|
+
* const emitter = neurolink.getEventEmitter();
|
|
257
|
+
* emitter.on('hitl:confirmation-request', async (event) => {
|
|
258
|
+
* const { payload } = event as ConfirmationRequestEvent; // listeners are typed unknown
|
|
259
|
+
* const approved = await askUser(payload); // your approval UI
|
|
260
|
+
* emitter.emit('hitl:confirmation-response', {
|
|
261
|
+
* type: 'hitl:confirmation-response',
|
|
262
|
+
* payload: { confirmationId: payload.confirmationId, approved }
|
|
263
|
+
* });
|
|
264
|
+
* });
|
|
252
265
|
* ```
|
|
253
266
|
*
|
|
254
267
|
* @example With Redis memory
|
|
@@ -1173,9 +1186,9 @@ export class NeuroLink {
|
|
|
1173
1186
|
* @param config.enableOrchestration - Whether to enable smart model orchestration (default: false)
|
|
1174
1187
|
* @param config.hitl - Configuration for Human-in-the-Loop safety features
|
|
1175
1188
|
* @param config.hitl.enabled - Whether to enable HITL tool confirmation (default: false)
|
|
1176
|
-
* @param config.hitl.dangerousActions - Keywords that trigger confirmation
|
|
1189
|
+
* @param config.hitl.dangerousActions - Keywords that trigger confirmation; required when HITL is enabled
|
|
1177
1190
|
* @param config.hitl.timeout - Confirmation timeout in milliseconds (default: 30000)
|
|
1178
|
-
* @param config.hitl.allowArgumentModification -
|
|
1191
|
+
* @param config.hitl.allowArgumentModification - Advertised to the approval UI as `allowModification` (default: false); not enforced, a response's `modifiedArguments` is applied either way
|
|
1179
1192
|
* @param config.toolRegistry - Optional tool registry instance for advanced use cases (default: new MCPToolRegistry())
|
|
1180
1193
|
*
|
|
1181
1194
|
* @example
|
|
@@ -6,15 +6,29 @@
|
|
|
6
6
|
"inferenceKinds": ["decide"],
|
|
7
7
|
"wire": {},
|
|
8
8
|
"models": {
|
|
9
|
-
"default": "xor-
|
|
10
|
-
"topModels": ["xor-
|
|
9
|
+
"default": "xor-latest",
|
|
10
|
+
"topModels": ["xor-latest", "xor-omni"],
|
|
11
11
|
"catalog": {
|
|
12
|
-
"xor-
|
|
12
|
+
"xor-latest": {
|
|
13
13
|
"contextWindow": 250000,
|
|
14
14
|
"maxOutputTokens": 256,
|
|
15
15
|
"vision": false,
|
|
16
16
|
"status": "production",
|
|
17
|
-
"description": "Recommended - XOR
|
|
17
|
+
"description": "Recommended - XOR text alias; boolean, choice and score questions, 2 to 64 options each"
|
|
18
|
+
},
|
|
19
|
+
"xor-omni": {
|
|
20
|
+
"contextWindow": 250000,
|
|
21
|
+
"maxOutputTokens": 256,
|
|
22
|
+
"vision": true,
|
|
23
|
+
"status": "production",
|
|
24
|
+
"description": "XOR multimodal alias; reads images and video alongside the state, for boolean, choice and score questions"
|
|
25
|
+
},
|
|
26
|
+
"xor-1.1": {
|
|
27
|
+
"contextWindow": 250000,
|
|
28
|
+
"maxOutputTokens": 256,
|
|
29
|
+
"vision": false,
|
|
30
|
+
"status": "retired",
|
|
31
|
+
"description": "Name served by the deprecated /v1/systemone route; superseded by xor-latest and xor-omni. Still sent as given when you name it - the endpoint decides whether it accepts it"
|
|
18
32
|
}
|
|
19
33
|
}
|
|
20
34
|
},
|
|
@@ -30,7 +44,7 @@
|
|
|
30
44
|
"baseURLRequired": true,
|
|
31
45
|
"baseURLNormalize": ["strip-trailing-slash", "strip-v1-suffix"],
|
|
32
46
|
"baseURLShape": "origin",
|
|
33
|
-
"endpoint": "{baseURL}/v1/
|
|
47
|
+
"endpoint": "{baseURL}/v1/decisions",
|
|
34
48
|
"model": { "placement": "body" },
|
|
35
49
|
"media": { "imagesKey": "images", "videoKey": "video" },
|
|
36
50
|
"requestIdHeaders": [
|
|
@@ -71,14 +85,14 @@
|
|
|
71
85
|
"description": "API key for your XOR endpoint",
|
|
72
86
|
"instructions": [
|
|
73
87
|
"1. Reach an XOR deployment, or use a LiteLLM proxy with a pass-through route to one",
|
|
74
|
-
"2. Set XOR_BASE_URL to its origin (NeuroLink calls <XOR_BASE_URL>/v1/
|
|
75
|
-
"3. Set {apiKeyEnvVar} to the key it accepts (on LiteLLM, a virtual key whose team allows xor-
|
|
88
|
+
"2. Set XOR_BASE_URL to its origin (NeuroLink calls <XOR_BASE_URL>/v1/decisions)",
|
|
89
|
+
"3. Set {apiKeyEnvVar} to the key it accepts (on LiteLLM, a virtual key whose team allows xor-latest, and xor-omni for media)"
|
|
76
90
|
]
|
|
77
91
|
},
|
|
78
92
|
"evidence": {
|
|
79
93
|
"rosterVerified": {
|
|
80
|
-
"date": "2026-
|
|
81
|
-
"method": "
|
|
94
|
+
"date": "2026-10-18",
|
|
95
|
+
"method": "POST <origin>/v1/decisions with model xor-latest and a 3-option choice question returned HTTP 200 with served model xor-text-2, through NeuroLink's decide(); a 65-option choice question returned HTTP 422 reading 2..64 options, got 65. A 64-option question returned 200 and 77 options returned 422 on both aliases when probed by hand the same day. A successful xor-omni answer, and any image, audio or video request, were not exercised. The figures contextWindow 250000, maxOutputTokens 256, the 200000-token state window and the media limits are carried over from xor-1.1, not re-measured for the aliases; xor-1.1 is kept only as a retired name"
|
|
82
96
|
},
|
|
83
97
|
"liveMatrix": null,
|
|
84
98
|
"addedInPR": "https://github.com/juspay/neurolink/pull/1984"
|
package/dist/types/hitl.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ export type HITLConfig = {
|
|
|
18
18
|
timeout?: number;
|
|
19
19
|
/** Communication method - currently only "event" is supported (default: "event") */
|
|
20
20
|
confirmationMethod?: "event";
|
|
21
|
-
/**
|
|
21
|
+
/** Advertised to the approval UI as allowModification (default: false); not enforced, a response's modifiedArguments is applied either way */
|
|
22
22
|
allowArgumentModification?: boolean;
|
|
23
23
|
/** Auto-approve requests when they timeout (default: false - rejects on timeout) */
|
|
24
24
|
autoApproveOnTimeout?: boolean;
|
|
@@ -72,7 +72,7 @@ export type ConfirmationResult = {
|
|
|
72
72
|
approved: boolean;
|
|
73
73
|
/** Optional reason for rejection (if approved is false) */
|
|
74
74
|
reason?: string;
|
|
75
|
-
/** User-modified arguments
|
|
75
|
+
/** User-modified arguments; applied whether or not allowArgumentModification is enabled */
|
|
76
76
|
modifiedArguments?: unknown;
|
|
77
77
|
/** Time taken for user to respond in milliseconds */
|
|
78
78
|
responseTime: number;
|
|
@@ -107,7 +107,7 @@ export type ConfirmationRequestEvent = {
|
|
|
107
107
|
};
|
|
108
108
|
/** Confirmation timeout in milliseconds */
|
|
109
109
|
timeoutMs: number;
|
|
110
|
-
/**
|
|
110
|
+
/** Hint for the approval UI, copied from allowArgumentModification; not enforced */
|
|
111
111
|
allowModification: boolean;
|
|
112
112
|
};
|
|
113
113
|
};
|
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) {
|