@cf-vibesdk/sdk 0.0.2 → 0.0.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @cf-vibesdk/sdk
2
2
 
3
- Type-safe client SDK for the VibeSDK platform.
3
+ Client SDK for the VibeSDK platform.
4
4
 
5
5
  ## Install
6
6
 
package/dist/index.d.ts CHANGED
@@ -1326,6 +1326,12 @@ export interface AgentConnectionData {
1326
1326
  agentId: string;
1327
1327
  }
1328
1328
  export type AgentPreviewResponse = PreviewType;
1329
+ type RetryConfig = {
1330
+ enabled?: boolean;
1331
+ initialDelayMs?: number;
1332
+ maxDelayMs?: number;
1333
+ maxRetries?: number;
1334
+ };
1329
1335
  type BehaviorType$1 = BehaviorType;
1330
1336
  type ProjectType$1 = ProjectType;
1331
1337
  export type Credentials = NonNullable<CodeGenArgs["credentials"]>;
@@ -1525,6 +1531,8 @@ export type VibeClientOptions = {
1525
1531
  /** Optional WebSocket factory for Node/Bun runtimes. */
1526
1532
  webSocketFactory?: AgentConnectionOptions["webSocketFactory"];
1527
1533
  fetchFn?: typeof fetch;
1534
+ /** HTTP retry configuration for transient failures (5xx, network errors). */
1535
+ retry?: RetryConfig;
1528
1536
  };
1529
1537
  type GenerationState = {
1530
1538
  status: "idle";
@@ -1593,6 +1601,7 @@ export declare class SessionStateStore {
1593
1601
  onChange(cb: (next: SessionState, prev: SessionState) => void): () => void;
1594
1602
  applyWsMessage(msg: AgentWsServerMessage): void;
1595
1603
  private setState;
1604
+ clear(): void;
1596
1605
  }
1597
1606
  type WorkspaceChange = {
1598
1607
  type: "reset";
@@ -1616,6 +1625,7 @@ export declare class WorkspaceStore {
1616
1625
  /** Apply a single file upsert from WS file events. */
1617
1626
  applyFileUpsert(file: unknown): void;
1618
1627
  applyWsMessage(msg: AgentWsServerMessage): void;
1628
+ clear(): void;
1619
1629
  }
1620
1630
  type WaitUntilReadyOptions = WaitOptions;
1621
1631
  type BuildSessionConnectOptions = AgentConnectionOptions & {
package/dist/index.js CHANGED
@@ -1,10 +1,40 @@
1
1
  // @bun
2
+ // src/retry.ts
3
+ function normalizeRetryConfig(retry, defaults) {
4
+ return {
5
+ enabled: retry?.enabled ?? defaults.enabled,
6
+ initialDelayMs: retry?.initialDelayMs ?? defaults.initialDelayMs,
7
+ maxDelayMs: retry?.maxDelayMs ?? defaults.maxDelayMs,
8
+ maxRetries: retry?.maxRetries ?? defaults.maxRetries
9
+ };
10
+ }
11
+ function computeBackoffMs(attempt, cfg) {
12
+ const base = Math.min(cfg.maxDelayMs, cfg.initialDelayMs * Math.pow(2, Math.max(0, attempt)));
13
+ const jitter = base * 0.2;
14
+ return Math.max(0, Math.floor(base - jitter + Math.random() * jitter * 2));
15
+ }
16
+ function sleep(ms) {
17
+ return new Promise((resolve) => setTimeout(resolve, ms));
18
+ }
19
+
2
20
  // src/http.ts
21
+ var HTTP_RETRY_DEFAULTS = {
22
+ enabled: true,
23
+ initialDelayMs: 1000,
24
+ maxDelayMs: 1e4,
25
+ maxRetries: 3
26
+ };
27
+ function isRetryableStatus(status) {
28
+ return status >= 500 && status < 600;
29
+ }
30
+
3
31
  class HttpClient {
4
32
  opts;
5
33
  cachedAccessToken = null;
34
+ retryCfg;
6
35
  constructor(opts) {
7
36
  this.opts = opts;
37
+ this.retryCfg = normalizeRetryConfig(opts.retry, HTTP_RETRY_DEFAULTS);
8
38
  }
9
39
  get baseUrl() {
10
40
  return this.opts.baseUrl.replace(/\/$/, "");
@@ -34,7 +64,7 @@ class HttpClient {
34
64
  }
35
65
  });
36
66
  if (!resp.ok) {
37
- const text = await resp.text().catch(() => "");
67
+ const text = (await resp.text().catch(() => "")).slice(0, 1000);
38
68
  if (resp.status === 401) {
39
69
  throw new Error(`HTTP 401 for /api/auth/exchange-api-key: invalid API key (regenerate in Settings \u2192 API Keys). ${text || ""}`.trim());
40
70
  }
@@ -60,21 +90,59 @@ class HttpClient {
60
90
  }
61
91
  async fetchJson(path, init) {
62
92
  const url = `${this.baseUrl}${path}`;
63
- const resp = await this.fetchFn(url, init);
64
- if (!resp.ok) {
65
- const text = await resp.text().catch(() => "");
66
- throw new Error(`HTTP ${resp.status} for ${path}: ${text || resp.statusText}`);
93
+ let lastError = null;
94
+ for (let attempt = 0;attempt <= this.retryCfg.maxRetries; attempt++) {
95
+ try {
96
+ const resp = await this.fetchFn(url, init);
97
+ if (!resp.ok) {
98
+ const text = (await resp.text().catch(() => "")).slice(0, 1000);
99
+ const error = new Error(`HTTP ${resp.status} for ${path}: ${text || resp.statusText}`);
100
+ if (this.retryCfg.enabled && isRetryableStatus(resp.status) && attempt < this.retryCfg.maxRetries) {
101
+ lastError = error;
102
+ await sleep(computeBackoffMs(attempt, this.retryCfg));
103
+ continue;
104
+ }
105
+ throw error;
106
+ }
107
+ return await resp.json();
108
+ } catch (error) {
109
+ if (error instanceof TypeError && this.retryCfg.enabled && attempt < this.retryCfg.maxRetries) {
110
+ lastError = error;
111
+ await sleep(computeBackoffMs(attempt, this.retryCfg));
112
+ continue;
113
+ }
114
+ throw error;
115
+ }
67
116
  }
68
- return await resp.json();
117
+ throw lastError ?? new Error(`Failed after ${this.retryCfg.maxRetries} retries`);
69
118
  }
70
119
  async fetchRaw(path, init) {
71
120
  const url = `${this.baseUrl}${path}`;
72
- const resp = await this.fetchFn(url, init);
73
- if (!resp.ok) {
74
- const text = await resp.text().catch(() => "");
75
- throw new Error(`HTTP ${resp.status} for ${path}: ${text || resp.statusText}`);
121
+ let lastError = null;
122
+ for (let attempt = 0;attempt <= this.retryCfg.maxRetries; attempt++) {
123
+ try {
124
+ const resp = await this.fetchFn(url, init);
125
+ if (!resp.ok) {
126
+ const text = (await resp.text().catch(() => "")).slice(0, 1000);
127
+ const error = new Error(`HTTP ${resp.status} for ${path}: ${text || resp.statusText}`);
128
+ if (this.retryCfg.enabled && isRetryableStatus(resp.status) && attempt < this.retryCfg.maxRetries) {
129
+ lastError = error;
130
+ await sleep(computeBackoffMs(attempt, this.retryCfg));
131
+ continue;
132
+ }
133
+ throw error;
134
+ }
135
+ return resp;
136
+ } catch (error) {
137
+ if (error instanceof TypeError && this.retryCfg.enabled && attempt < this.retryCfg.maxRetries) {
138
+ lastError = error;
139
+ await sleep(computeBackoffMs(attempt, this.retryCfg));
140
+ continue;
141
+ }
142
+ throw error;
143
+ }
76
144
  }
77
- return resp;
145
+ throw lastError ?? new Error(`Failed after ${this.retryCfg.maxRetries} retries`);
78
146
  }
79
147
  }
80
148
 
@@ -137,6 +205,10 @@ class TypedEmitter {
137
205
  for (const cb of set)
138
206
  cb(payload);
139
207
  }
208
+ clear() {
209
+ this.listeners.clear();
210
+ this.anyListeners.clear();
211
+ }
140
212
  }
141
213
 
142
214
  // src/state.ts
@@ -147,9 +219,10 @@ var INITIAL_STATE = {
147
219
  cloudflare: { status: "idle" }
148
220
  };
149
221
  function extractPhaseInfo(msg) {
222
+ const phase = msg?.phase;
150
223
  return {
151
- name: msg.phase?.name,
152
- description: msg.phase?.description
224
+ name: phase?.name,
225
+ description: phase?.description
153
226
  };
154
227
  }
155
228
 
@@ -299,6 +372,10 @@ class SessionStateStore {
299
372
  this.state = next;
300
373
  this.emitter.emit("change", { prev, next });
301
374
  }
375
+ clear() {
376
+ this.state = INITIAL_STATE;
377
+ this.emitter.clear();
378
+ }
302
379
  }
303
380
 
304
381
  // src/ws.ts
@@ -308,23 +385,15 @@ function toWsCloseEvent(ev) {
308
385
  reason: typeof ev.reason === "string" ? ev.reason : ""
309
386
  };
310
387
  }
311
- function normalizeRetryConfig(retry) {
312
- const enabled = retry?.enabled ?? true;
313
- return {
314
- enabled,
315
- initialDelayMs: retry?.initialDelayMs ?? 1000,
316
- maxDelayMs: retry?.maxDelayMs ?? 30000,
317
- maxRetries: retry?.maxRetries ?? Infinity
318
- };
319
- }
320
- function computeBackoffMs(attempt, cfg) {
321
- const base = Math.min(cfg.maxDelayMs, cfg.initialDelayMs * Math.pow(2, Math.max(0, attempt)));
322
- const jitter = base * 0.2;
323
- return Math.max(0, Math.floor(base - jitter + Math.random() * jitter * 2));
324
- }
388
+ var WS_RETRY_DEFAULTS = {
389
+ enabled: true,
390
+ initialDelayMs: 1000,
391
+ maxDelayMs: 30000,
392
+ maxRetries: Infinity
393
+ };
325
394
  function createAgentConnection(url, options = {}) {
326
395
  const emitter = new TypedEmitter;
327
- const retryCfg = normalizeRetryConfig(options.retry);
396
+ const retryCfg = normalizeRetryConfig(options.retry, WS_RETRY_DEFAULTS);
328
397
  const headers = { ...options.headers ?? {} };
329
398
  if (options.origin)
330
399
  headers.Origin = options.origin;
@@ -501,7 +570,7 @@ function createAgentConnection(url, options = {}) {
501
570
  }
502
571
  if (ws.on) {
503
572
  ws.on("open", () => onOpen());
504
- ws.on("close", (code, reason) => onClose({ code, reason }));
573
+ ws.on("close", (code, reason) => onClose({ code: typeof code === "number" ? code : undefined, reason: typeof reason === "string" ? reason : undefined }));
505
574
  ws.on("error", (error) => onError(error));
506
575
  ws.on("message", (data) => onMessage(data));
507
576
  }
@@ -514,8 +583,12 @@ function createAgentConnection(url, options = {}) {
514
583
  return;
515
584
  }
516
585
  pendingSends.push(data);
517
- if (pendingSends.length > maxPendingSends)
586
+ if (pendingSends.length > maxPendingSends) {
518
587
  pendingSends.shift();
588
+ emitter.emit("ws:error", {
589
+ error: new Error(`Message queue overflow: dropped oldest message (queue size: ${maxPendingSends})`)
590
+ });
591
+ }
519
592
  }
520
593
  function close() {
521
594
  closedByUser = true;
@@ -553,25 +626,24 @@ function createAgentConnection(url, options = {}) {
553
626
  function isRecord(value) {
554
627
  return typeof value === "object" && value !== null;
555
628
  }
629
+ function isFileOutputType(value) {
630
+ if (!isRecord(value))
631
+ return false;
632
+ return typeof value.filePath === "string" && typeof value.fileContents === "string";
633
+ }
556
634
  function extractGeneratedFilesFromState(state) {
557
635
  const out = [];
558
636
  for (const file of Object.values(state.generatedFilesMap ?? {})) {
559
- const path = file.filePath;
560
- const content = file.fileContents;
561
- if (typeof path === "string" && typeof content === "string") {
562
- out.push({ path, content });
563
- }
637
+ if (!isFileOutputType(file))
638
+ continue;
639
+ out.push({ path: file.filePath, content: file.fileContents });
564
640
  }
565
641
  return out;
566
642
  }
567
643
  function extractGeneratedFileFromMessageFile(file) {
568
- if (!isRecord(file))
569
- return null;
570
- const path = file.filePath;
571
- const content = file.fileContents;
572
- if (typeof path !== "string" || typeof content !== "string")
644
+ if (!isFileOutputType(file))
573
645
  return null;
574
- return { path, content };
646
+ return { path: file.filePath, content: file.fileContents };
575
647
  }
576
648
 
577
649
  class WorkspaceStore {
@@ -624,6 +696,10 @@ class WorkspaceStore {
624
696
  break;
625
697
  }
626
698
  }
699
+ clear() {
700
+ this.files.clear();
701
+ this.emitter.clear();
702
+ }
627
703
  }
628
704
 
629
705
  // src/session.ts
@@ -843,6 +919,8 @@ class BuildSession {
843
919
  close() {
844
920
  this.connection?.close();
845
921
  this.connection = null;
922
+ this.workspace.clear();
923
+ this.state.clear();
846
924
  }
847
925
  assertConnected() {
848
926
  if (!this.connection) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cf-vibesdk/sdk",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {