@vellumai/vellum-gateway 0.8.2 → 0.8.3
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/ARCHITECTURE.md +1 -1
- package/package.json +1 -1
- package/src/__tests__/config-file-watcher.test.ts +57 -0
- package/src/__tests__/route-schema-guard.test.ts +4 -0
- package/src/__tests__/twilio-webhooks.test.ts +47 -0
- package/src/auth/ipc-route-policy.ts +6 -0
- package/src/channels/inbound-event.ts +4 -2
- package/src/channels/types.ts +2 -0
- package/src/config-file-watcher.ts +44 -1
- package/src/feature-flag-registry.json +21 -29
- package/src/http/routes/a2a-routes.test.ts +129 -0
- package/src/http/routes/a2a-routes.ts +121 -0
- package/src/http/routes/twilio-voice-verify-callback.ts +41 -12
- package/src/http/routes/twilio-voice-webhook.test.ts +55 -0
- package/src/http/routes/twilio-voice-webhook.ts +10 -2
- package/src/index.ts +14 -0
- package/src/risk/bash-risk-classifier.test.ts +24 -0
- package/src/risk/command-registry/commands/assistant.ts +33 -0
- package/src/risk/command-registry.test.ts +5 -0
- package/src/runtime/client.ts +66 -14
- package/src/twilio/validate-webhook.ts +7 -1
- package/src/types.ts +1 -0
- package/src/velay/client.test.ts +100 -0
- package/src/velay/client.ts +73 -0
package/src/velay/client.ts
CHANGED
|
@@ -92,6 +92,7 @@ export class VelayTunnelClient {
|
|
|
92
92
|
private readTimeoutTimer: unknown = null;
|
|
93
93
|
private peerHeartbeatConfirmed = false;
|
|
94
94
|
private publishedPublicBaseUrl: string | undefined;
|
|
95
|
+
private credentialRefreshPending = false;
|
|
95
96
|
private unsubscribeConfigInvalidation: (() => void) | undefined;
|
|
96
97
|
|
|
97
98
|
constructor(private readonly options: VelayTunnelClientOptions) {
|
|
@@ -127,6 +128,37 @@ export class VelayTunnelClient {
|
|
|
127
128
|
};
|
|
128
129
|
}
|
|
129
130
|
|
|
131
|
+
refreshCredentials(reason = "credentials changed"): void {
|
|
132
|
+
if (!this.running) return;
|
|
133
|
+
|
|
134
|
+
this.reconnectAttempt = 0;
|
|
135
|
+
if (this.reconnectTimer) {
|
|
136
|
+
this.timerApi.clearTimeout(this.reconnectTimer);
|
|
137
|
+
this.reconnectTimer = null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const ws = this.ws;
|
|
141
|
+
if (ws) {
|
|
142
|
+
log.info(
|
|
143
|
+
{ reason },
|
|
144
|
+
"Restarting Velay tunnel with refreshed credentials",
|
|
145
|
+
);
|
|
146
|
+
this.disconnectActiveWebSocket(ws, 1000, "credentials changed");
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (this.connecting) {
|
|
151
|
+
this.credentialRefreshPending = true;
|
|
152
|
+
log.info(
|
|
153
|
+
{ reason },
|
|
154
|
+
"Queued Velay credential refresh behind active connect",
|
|
155
|
+
);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
this.connectForCredentialRefresh(reason);
|
|
160
|
+
}
|
|
161
|
+
|
|
130
162
|
start(): void {
|
|
131
163
|
if (this.running) return;
|
|
132
164
|
this.running = true;
|
|
@@ -145,6 +177,7 @@ export class VelayTunnelClient {
|
|
|
145
177
|
async stop(): Promise<void> {
|
|
146
178
|
this.running = false;
|
|
147
179
|
this.connecting = false;
|
|
180
|
+
this.credentialRefreshPending = false;
|
|
148
181
|
this.unsubscribeConfigInvalidation?.();
|
|
149
182
|
this.unsubscribeConfigInvalidation = undefined;
|
|
150
183
|
if (this.reconnectTimer) {
|
|
@@ -192,6 +225,9 @@ export class VelayTunnelClient {
|
|
|
192
225
|
} catch (err) {
|
|
193
226
|
this.connecting = false;
|
|
194
227
|
log.warn({ err }, "Failed to read Velay tunnel credentials");
|
|
228
|
+
if (this.consumePendingCredentialRefresh("credentials read failed")) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
195
231
|
this.scheduleReconnect();
|
|
196
232
|
return;
|
|
197
233
|
}
|
|
@@ -205,6 +241,9 @@ export class VelayTunnelClient {
|
|
|
205
241
|
const platformAssistantId = platformAssistantIdRaw?.trim() || undefined;
|
|
206
242
|
if (!apiKey) {
|
|
207
243
|
this.connecting = false;
|
|
244
|
+
if (this.consumePendingCredentialRefresh("assistant API key missing")) {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
208
247
|
log.info("Velay tunnel waiting for assistant API key");
|
|
209
248
|
this.scheduleReconnect();
|
|
210
249
|
return;
|
|
@@ -217,6 +256,9 @@ export class VelayTunnelClient {
|
|
|
217
256
|
} catch (err) {
|
|
218
257
|
this.connecting = false;
|
|
219
258
|
log.error({ err }, "Invalid Velay base URL");
|
|
259
|
+
if (this.consumePendingCredentialRefresh("Velay base URL invalid")) {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
220
262
|
this.scheduleReconnect();
|
|
221
263
|
return;
|
|
222
264
|
}
|
|
@@ -261,14 +303,45 @@ export class VelayTunnelClient {
|
|
|
261
303
|
this.disconnectActiveWebSocket(ws);
|
|
262
304
|
}
|
|
263
305
|
});
|
|
306
|
+
|
|
307
|
+
if (this.credentialRefreshPending) {
|
|
308
|
+
this.credentialRefreshPending = false;
|
|
309
|
+
log.info(
|
|
310
|
+
"Restarting Velay tunnel because credentials changed during connect",
|
|
311
|
+
);
|
|
312
|
+
this.disconnectActiveWebSocket(ws, 1000, "credentials changed");
|
|
313
|
+
}
|
|
264
314
|
} catch (err) {
|
|
265
315
|
this.ws = null;
|
|
266
316
|
this.connecting = false;
|
|
267
317
|
log.warn({ err }, "Failed to connect Velay tunnel");
|
|
318
|
+
if (this.consumePendingCredentialRefresh("WebSocket connect failed")) {
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
268
321
|
this.scheduleReconnect();
|
|
269
322
|
}
|
|
270
323
|
}
|
|
271
324
|
|
|
325
|
+
private connectForCredentialRefresh(reason: string): void {
|
|
326
|
+
this.connect().catch((err) => {
|
|
327
|
+
this.connecting = false;
|
|
328
|
+
log.error({ err, reason }, "Velay credential refresh reconnect failed");
|
|
329
|
+
this.scheduleReconnect();
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
private consumePendingCredentialRefresh(reason: string): boolean {
|
|
334
|
+
if (!this.credentialRefreshPending || !this.running) return false;
|
|
335
|
+
|
|
336
|
+
this.credentialRefreshPending = false;
|
|
337
|
+
log.info(
|
|
338
|
+
{ reason },
|
|
339
|
+
"Retrying Velay tunnel connect with refreshed credentials",
|
|
340
|
+
);
|
|
341
|
+
this.connectForCredentialRefresh(reason);
|
|
342
|
+
return true;
|
|
343
|
+
}
|
|
344
|
+
|
|
272
345
|
private async handleMessage(
|
|
273
346
|
data: unknown,
|
|
274
347
|
originWs: WebSocket,
|