@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.
@@ -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,