veryfront 0.1.280 → 0.1.281

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/esm/deno.js CHANGED
@@ -1,7 +1,6 @@
1
1
  export default {
2
2
  "name": "veryfront",
3
- "version": "0.1.277",
4
- "version": "0.1.280",
3
+ "version": "0.1.281",
5
4
  "license": "Apache-2.0",
6
5
  "nodeModulesDir": "auto",
7
6
  "workspace": [
@@ -5,6 +5,13 @@ export interface LazySandboxOptions extends SandboxOptions {
5
5
  pollIntervalMs?: number;
6
6
  heartbeatIntervalMs?: number;
7
7
  heartbeatGraceMs?: number;
8
+ execStartTimeoutMs?: number;
9
+ execStartMaxAttempts?: number;
10
+ execStartRetryDelayMs?: number;
11
+ resolveRuntimeEndpoint?: (input: {
12
+ endpoint: string;
13
+ sessionId: string;
14
+ }) => string;
8
15
  }
9
16
  /** Lazily provisions sandbox sessions and keeps them alive while in use. */
10
17
  export declare class LazySandbox {
@@ -15,6 +22,10 @@ export declare class LazySandbox {
15
22
  private readonly pollIntervalMs;
16
23
  private readonly heartbeatIntervalMs;
17
24
  private readonly heartbeatGraceMs;
25
+ private readonly execStartTimeoutMs;
26
+ private readonly execStartMaxAttempts;
27
+ private readonly execStartRetryDelayMs;
28
+ private readonly resolveRuntimeEndpointOption;
18
29
  private endpoint;
19
30
  private sessionId;
20
31
  private sessionProjectId;
@@ -55,6 +66,12 @@ export declare class LazySandbox {
55
66
  private resolveExecOptions;
56
67
  private resolveCommandJobEndpoint;
57
68
  private updateTrackedCommandJob;
69
+ private startExec;
70
+ private fetchExecStart;
71
+ private waitForExecStartRetry;
72
+ private reprovisionAfterExecStartFailure;
73
+ private resolveRuntimeEndpoint;
74
+ private requireSessionId;
58
75
  private authHeaders;
59
76
  private jsonHeaders;
60
77
  }
@@ -1 +1 @@
1
- {"version":3,"file":"lazy-sandbox.d.ts","sourceRoot":"","sources":["../../../src/src/sandbox/lazy-sandbox.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"lazy-sandbox.d.ts","sourceRoot":"","sources":["../../../src/src/sandbox/lazy-sandbox.ts"],"names":[],"mappings":"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"}
@@ -5,6 +5,15 @@ const DEFAULT_STARTUP_TIMEOUT_MS = 180_000;
5
5
  const DEFAULT_POLL_INTERVAL_MS = 2_000;
6
6
  const DEFAULT_HEARTBEAT_INTERVAL_MS = 30_000;
7
7
  const DEFAULT_HEARTBEAT_GRACE_MS = 5_000;
8
+ const DEFAULT_EXEC_START_TIMEOUT_MS = 30_000;
9
+ const DEFAULT_EXEC_START_MAX_ATTEMPTS = 3;
10
+ const DEFAULT_EXEC_START_RETRY_DELAY_MS = 1_000;
11
+ const REPROVISIONABLE_EXEC_START_ERROR_CODES = new Set([
12
+ "ECONNREFUSED",
13
+ "ECONNRESET",
14
+ "ENOTFOUND",
15
+ "EHOSTUNREACH",
16
+ ]);
8
17
  /** Lazily provisions sandbox sessions and keeps them alive while in use. */
9
18
  export class LazySandbox {
10
19
  apiUrl;
@@ -14,6 +23,10 @@ export class LazySandbox {
14
23
  pollIntervalMs;
15
24
  heartbeatIntervalMs;
16
25
  heartbeatGraceMs;
26
+ execStartTimeoutMs;
27
+ execStartMaxAttempts;
28
+ execStartRetryDelayMs;
29
+ resolveRuntimeEndpointOption;
17
30
  endpoint = null;
18
31
  sessionId = null;
19
32
  sessionProjectId = null;
@@ -31,6 +44,11 @@ export class LazySandbox {
31
44
  this.pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
32
45
  this.heartbeatIntervalMs = options.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS;
33
46
  this.heartbeatGraceMs = options.heartbeatGraceMs ?? DEFAULT_HEARTBEAT_GRACE_MS;
47
+ this.execStartTimeoutMs = options.execStartTimeoutMs ?? DEFAULT_EXEC_START_TIMEOUT_MS;
48
+ this.execStartMaxAttempts = options.execStartMaxAttempts ?? DEFAULT_EXEC_START_MAX_ATTEMPTS;
49
+ this.execStartRetryDelayMs = options.execStartRetryDelayMs ??
50
+ DEFAULT_EXEC_START_RETRY_DELAY_MS;
51
+ this.resolveRuntimeEndpointOption = options.resolveRuntimeEndpoint;
34
52
  }
35
53
  async ensure() {
36
54
  if (this.endpoint)
@@ -71,13 +89,17 @@ export class LazySandbox {
71
89
  }
72
90
  async *executeStream(command, options) {
73
91
  await this.touchSession();
74
- const res = await fetch(`${this.requireEndpoint()}/exec`, {
75
- method: "POST",
76
- headers: this.jsonHeaders(),
77
- body: JSON.stringify({ command, ...this.resolveExecOptions(options) }),
78
- });
79
- if (!res.ok) {
80
- throw REQUEST_ERROR.create({ detail: `Exec failed: ${res.status} ${await res.text()}` });
92
+ let res;
93
+ try {
94
+ res = await this.startExec(command, options);
95
+ }
96
+ catch (error) {
97
+ if (!shouldReprovisionAfterExecStartFailure(error)) {
98
+ throw error;
99
+ }
100
+ await this.reprovisionAfterExecStartFailure();
101
+ await this.touchSession();
102
+ res = await this.startExec(command, options);
81
103
  }
82
104
  if (!res.body) {
83
105
  throw new Error("Exec response has no body");
@@ -127,7 +149,7 @@ export class LazySandbox {
127
149
  }
128
150
  async startCommandJob(command, options) {
129
151
  await this.touchSession();
130
- const endpoint = this.requireEndpoint();
152
+ const endpoint = this.resolveRuntimeEndpoint();
131
153
  const res = await fetch(`${endpoint}/exec/jobs`, {
132
154
  method: "POST",
133
155
  headers: this.jsonHeaders(),
@@ -400,7 +422,7 @@ export class LazySandbox {
400
422
  return trackedEndpoint;
401
423
  }
402
424
  await this.ensure();
403
- return this.requireEndpoint();
425
+ return this.resolveRuntimeEndpoint();
404
426
  }
405
427
  updateTrackedCommandJob(job, endpoint) {
406
428
  if (job.status === "running") {
@@ -415,6 +437,65 @@ export class LazySandbox {
415
437
  this.startHeartbeatLoop();
416
438
  }
417
439
  }
440
+ async startExec(command, options) {
441
+ const endpoint = this.resolveRuntimeEndpoint();
442
+ const body = JSON.stringify({ command, ...this.resolveExecOptions(options) });
443
+ for (let attempt = 1; attempt <= this.execStartMaxAttempts; attempt += 1) {
444
+ try {
445
+ const res = await this.fetchExecStart(`${endpoint}/exec`, {
446
+ method: "POST",
447
+ headers: this.jsonHeaders(),
448
+ body,
449
+ });
450
+ if (res.ok) {
451
+ return res;
452
+ }
453
+ if (isRetryableExecStartStatus(res.status) && attempt < this.execStartMaxAttempts) {
454
+ await this.waitForExecStartRetry();
455
+ continue;
456
+ }
457
+ throw REQUEST_ERROR.create({ detail: `Exec failed: ${res.status} ${await res.text()}` });
458
+ }
459
+ catch (error) {
460
+ if (!isRetryableExecStartError(error) || attempt >= this.execStartMaxAttempts) {
461
+ throw error;
462
+ }
463
+ await this.waitForExecStartRetry();
464
+ }
465
+ }
466
+ throw new Error("Sandbox exec failed before a request was made");
467
+ }
468
+ async fetchExecStart(url, init) {
469
+ const controller = new AbortController();
470
+ const timeout = dntShim.setTimeout(() => controller.abort(), this.execStartTimeoutMs);
471
+ try {
472
+ return await fetch(url, { ...init, signal: controller.signal });
473
+ }
474
+ finally {
475
+ clearTimeout(timeout);
476
+ }
477
+ }
478
+ waitForExecStartRetry() {
479
+ return new Promise((resolve) => dntShim.setTimeout(resolve, this.execStartRetryDelayMs));
480
+ }
481
+ async reprovisionAfterExecStartFailure() {
482
+ const sessionId = this.sessionId;
483
+ if (!sessionId)
484
+ return;
485
+ await this.deleteSession(sessionId);
486
+ this.resetSessionState(sessionId);
487
+ }
488
+ resolveRuntimeEndpoint() {
489
+ const endpoint = this.requireEndpoint();
490
+ const sessionId = this.requireSessionId();
491
+ return this.resolveRuntimeEndpointOption?.({ endpoint, sessionId }) ?? endpoint;
492
+ }
493
+ requireSessionId() {
494
+ if (!this.sessionId) {
495
+ throw new Error("Sandbox session unavailable");
496
+ }
497
+ return this.sessionId;
498
+ }
418
499
  authHeaders() {
419
500
  return { Authorization: `Bearer ${this.authToken}` };
420
501
  }
@@ -425,6 +506,23 @@ export class LazySandbox {
425
506
  };
426
507
  }
427
508
  }
509
+ function isRetryableExecStartStatus(status) {
510
+ return status === 502 || status === 503 || status === 504;
511
+ }
512
+ function isRetryableExecStartError(error) {
513
+ return error instanceof Error && /fetch failed/i.test(error.message);
514
+ }
515
+ function shouldReprovisionAfterExecStartFailure(error) {
516
+ if (!(error instanceof Error)) {
517
+ return false;
518
+ }
519
+ const cause = error.cause;
520
+ if (typeof cause !== "object" || cause === null || !("code" in cause)) {
521
+ return false;
522
+ }
523
+ return typeof cause.code === "string" &&
524
+ REPROVISIONABLE_EXEC_START_ERROR_CODES.has(cause.code);
525
+ }
428
526
  function mapCommandJob(json) {
429
527
  return {
430
528
  id: json.id,
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "0.1.280";
1
+ export declare const VERSION = "0.1.281";
2
2
  //# sourceMappingURL=version-constant.d.ts.map
@@ -1,3 +1,3 @@
1
1
  // Keep in sync with deno.json version.
2
2
  // scripts/release.ts updates this constant during releases.
3
- export const VERSION = "0.1.280";
3
+ export const VERSION = "0.1.281";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "veryfront",
3
- "version": "0.1.280",
3
+ "version": "0.1.281",
4
4
  "description": "The simplest way to build AI-powered apps",
5
5
  "keywords": [
6
6
  "react",
package/src/deno.js CHANGED
@@ -1,7 +1,6 @@
1
1
  export default {
2
2
  "name": "veryfront",
3
- "version": "0.1.277",
4
- "version": "0.1.280",
3
+ "version": "0.1.281",
5
4
  "license": "Apache-2.0",
6
5
  "nodeModulesDir": "auto",
7
6
  "workspace": [
@@ -19,6 +19,10 @@ export interface LazySandboxOptions extends SandboxOptions {
19
19
  pollIntervalMs?: number;
20
20
  heartbeatIntervalMs?: number;
21
21
  heartbeatGraceMs?: number;
22
+ execStartTimeoutMs?: number;
23
+ execStartMaxAttempts?: number;
24
+ execStartRetryDelayMs?: number;
25
+ resolveRuntimeEndpoint?: (input: { endpoint: string; sessionId: string }) => string;
22
26
  }
23
27
 
24
28
  interface SandboxSessionRecord {
@@ -31,6 +35,15 @@ const DEFAULT_STARTUP_TIMEOUT_MS = 180_000;
31
35
  const DEFAULT_POLL_INTERVAL_MS = 2_000;
32
36
  const DEFAULT_HEARTBEAT_INTERVAL_MS = 30_000;
33
37
  const DEFAULT_HEARTBEAT_GRACE_MS = 5_000;
38
+ const DEFAULT_EXEC_START_TIMEOUT_MS = 30_000;
39
+ const DEFAULT_EXEC_START_MAX_ATTEMPTS = 3;
40
+ const DEFAULT_EXEC_START_RETRY_DELAY_MS = 1_000;
41
+ const REPROVISIONABLE_EXEC_START_ERROR_CODES = new Set([
42
+ "ECONNREFUSED",
43
+ "ECONNRESET",
44
+ "ENOTFOUND",
45
+ "EHOSTUNREACH",
46
+ ]);
34
47
 
35
48
  /** Lazily provisions sandbox sessions and keeps them alive while in use. */
36
49
  export class LazySandbox {
@@ -41,6 +54,15 @@ export class LazySandbox {
41
54
  private readonly pollIntervalMs: number;
42
55
  private readonly heartbeatIntervalMs: number;
43
56
  private readonly heartbeatGraceMs: number;
57
+ private readonly execStartTimeoutMs: number;
58
+ private readonly execStartMaxAttempts: number;
59
+ private readonly execStartRetryDelayMs: number;
60
+ private readonly resolveRuntimeEndpointOption:
61
+ | ((input: {
62
+ endpoint: string;
63
+ sessionId: string;
64
+ }) => string)
65
+ | undefined;
44
66
 
45
67
  private endpoint: string | null = null;
46
68
  private sessionId: string | null = null;
@@ -60,6 +82,11 @@ export class LazySandbox {
60
82
  this.pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
61
83
  this.heartbeatIntervalMs = options.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS;
62
84
  this.heartbeatGraceMs = options.heartbeatGraceMs ?? DEFAULT_HEARTBEAT_GRACE_MS;
85
+ this.execStartTimeoutMs = options.execStartTimeoutMs ?? DEFAULT_EXEC_START_TIMEOUT_MS;
86
+ this.execStartMaxAttempts = options.execStartMaxAttempts ?? DEFAULT_EXEC_START_MAX_ATTEMPTS;
87
+ this.execStartRetryDelayMs = options.execStartRetryDelayMs ??
88
+ DEFAULT_EXEC_START_RETRY_DELAY_MS;
89
+ this.resolveRuntimeEndpointOption = options.resolveRuntimeEndpoint;
63
90
  }
64
91
 
65
92
  async ensure(): Promise<void> {
@@ -105,15 +132,17 @@ export class LazySandbox {
105
132
 
106
133
  async *executeStream(command: string, options?: ExecOptions): AsyncGenerator<ExecStreamEvent> {
107
134
  await this.touchSession();
135
+ let res: Response;
136
+ try {
137
+ res = await this.startExec(command, options);
138
+ } catch (error) {
139
+ if (!shouldReprovisionAfterExecStartFailure(error)) {
140
+ throw error;
141
+ }
108
142
 
109
- const res = await fetch(`${this.requireEndpoint()}/exec`, {
110
- method: "POST",
111
- headers: this.jsonHeaders(),
112
- body: JSON.stringify({ command, ...this.resolveExecOptions(options) }),
113
- });
114
-
115
- if (!res.ok) {
116
- throw REQUEST_ERROR.create({ detail: `Exec failed: ${res.status} ${await res.text()}` });
143
+ await this.reprovisionAfterExecStartFailure();
144
+ await this.touchSession();
145
+ res = await this.startExec(command, options);
117
146
  }
118
147
 
119
148
  if (!res.body) {
@@ -175,7 +204,7 @@ export class LazySandbox {
175
204
 
176
205
  async startCommandJob(command: string, options?: ExecOptions): Promise<CommandJob> {
177
206
  await this.touchSession();
178
- const endpoint = this.requireEndpoint();
207
+ const endpoint = this.resolveRuntimeEndpoint();
179
208
 
180
209
  const res = await fetch(`${endpoint}/exec/jobs`, {
181
210
  method: "POST",
@@ -501,7 +530,7 @@ export class LazySandbox {
501
530
  }
502
531
 
503
532
  await this.ensure();
504
- return this.requireEndpoint();
533
+ return this.resolveRuntimeEndpoint();
505
534
  }
506
535
 
507
536
  private updateTrackedCommandJob(job: Pick<CommandJob, "id" | "status">, endpoint: string): void {
@@ -520,6 +549,77 @@ export class LazySandbox {
520
549
  }
521
550
  }
522
551
 
552
+ private async startExec(command: string, options?: ExecOptions): Promise<Response> {
553
+ const endpoint = this.resolveRuntimeEndpoint();
554
+ const body = JSON.stringify({ command, ...this.resolveExecOptions(options) });
555
+
556
+ for (let attempt = 1; attempt <= this.execStartMaxAttempts; attempt += 1) {
557
+ try {
558
+ const res = await this.fetchExecStart(`${endpoint}/exec`, {
559
+ method: "POST",
560
+ headers: this.jsonHeaders(),
561
+ body,
562
+ });
563
+
564
+ if (res.ok) {
565
+ return res;
566
+ }
567
+
568
+ if (isRetryableExecStartStatus(res.status) && attempt < this.execStartMaxAttempts) {
569
+ await this.waitForExecStartRetry();
570
+ continue;
571
+ }
572
+
573
+ throw REQUEST_ERROR.create({ detail: `Exec failed: ${res.status} ${await res.text()}` });
574
+ } catch (error) {
575
+ if (!isRetryableExecStartError(error) || attempt >= this.execStartMaxAttempts) {
576
+ throw error;
577
+ }
578
+
579
+ await this.waitForExecStartRetry();
580
+ }
581
+ }
582
+
583
+ throw new Error("Sandbox exec failed before a request was made");
584
+ }
585
+
586
+ private async fetchExecStart(url: string, init: RequestInit): Promise<Response> {
587
+ const controller = new AbortController();
588
+ const timeout = dntShim.setTimeout(() => controller.abort(), this.execStartTimeoutMs);
589
+
590
+ try {
591
+ return await fetch(url, { ...init, signal: controller.signal });
592
+ } finally {
593
+ clearTimeout(timeout);
594
+ }
595
+ }
596
+
597
+ private waitForExecStartRetry(): Promise<void> {
598
+ return new Promise((resolve) => dntShim.setTimeout(resolve, this.execStartRetryDelayMs));
599
+ }
600
+
601
+ private async reprovisionAfterExecStartFailure(): Promise<void> {
602
+ const sessionId = this.sessionId;
603
+ if (!sessionId) return;
604
+
605
+ await this.deleteSession(sessionId);
606
+ this.resetSessionState(sessionId);
607
+ }
608
+
609
+ private resolveRuntimeEndpoint(): string {
610
+ const endpoint = this.requireEndpoint();
611
+ const sessionId = this.requireSessionId();
612
+ return this.resolveRuntimeEndpointOption?.({ endpoint, sessionId }) ?? endpoint;
613
+ }
614
+
615
+ private requireSessionId(): string {
616
+ if (!this.sessionId) {
617
+ throw new Error("Sandbox session unavailable");
618
+ }
619
+
620
+ return this.sessionId;
621
+ }
622
+
523
623
  private authHeaders(): HeadersInit {
524
624
  return { Authorization: `Bearer ${this.authToken}` };
525
625
  }
@@ -532,6 +632,28 @@ export class LazySandbox {
532
632
  }
533
633
  }
534
634
 
635
+ function isRetryableExecStartStatus(status: number): boolean {
636
+ return status === 502 || status === 503 || status === 504;
637
+ }
638
+
639
+ function isRetryableExecStartError(error: unknown): boolean {
640
+ return error instanceof Error && /fetch failed/i.test(error.message);
641
+ }
642
+
643
+ function shouldReprovisionAfterExecStartFailure(error: unknown): boolean {
644
+ if (!(error instanceof Error)) {
645
+ return false;
646
+ }
647
+
648
+ const cause = error.cause;
649
+ if (typeof cause !== "object" || cause === null || !("code" in cause)) {
650
+ return false;
651
+ }
652
+
653
+ return typeof cause.code === "string" &&
654
+ REPROVISIONABLE_EXEC_START_ERROR_CODES.has(cause.code);
655
+ }
656
+
535
657
  function mapCommandJob(json: Record<string, unknown>): CommandJob {
536
658
  return {
537
659
  id: json.id as string,
@@ -1,3 +1,3 @@
1
1
  // Keep in sync with deno.json version.
2
2
  // scripts/release.ts updates this constant during releases.
3
- export const VERSION = "0.1.280";
3
+ export const VERSION = "0.1.281";