@cf-vibesdk/sdk 0.0.9 → 0.1.0

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
@@ -94,6 +94,53 @@ const session = await client.build('Build a weather dashboard', {
94
94
  });
95
95
  ```
96
96
 
97
+ #### Blueprint Streaming
98
+
99
+ The server streams blueprint chunks as the AI generates the project plan. By default, `build()` waits for all chunks before returning:
100
+
101
+ ```ts
102
+ // Default behavior: waits for blueprint to complete
103
+ const session = await client.build('Build a todo app', {
104
+ onBlueprintChunk: (chunk) => {
105
+ // Called in real-time as chunks arrive
106
+ blueprintText += chunk;
107
+ },
108
+ });
109
+ // Session returned after all blueprint chunks received
110
+ ```
111
+
112
+ For faster startup, set `waitForBlueprint: false` to return immediately and stream chunks in the background:
113
+
114
+ ```ts
115
+ const session = await client.build('Build a todo app', {
116
+ waitForBlueprint: false, // Return immediately after start event
117
+ onBlueprintChunk: (chunk) => {
118
+ // Still called in real-time as chunks arrive
119
+ blueprintText += chunk;
120
+ },
121
+ onBlueprintError: (error) => {
122
+ // Called if streaming fails (session is auto-closed)
123
+ console.error('Blueprint streaming failed:', error);
124
+ },
125
+ });
126
+ // Session returned immediately, blueprint streams in background
127
+ ```
128
+
129
+ Use the `BlueprintStreamParser` utility to convert chunks to readable Markdown:
130
+
131
+ ```ts
132
+ import { BlueprintStreamParser } from '@cf-vibesdk/sdk';
133
+
134
+ const parser = new BlueprintStreamParser();
135
+ const session = await client.build('Build a todo app', {
136
+ waitForBlueprint: false,
137
+ onBlueprintChunk: (chunk) => {
138
+ const markdown = parser.append(chunk);
139
+ console.log(markdown); // Rendered as Markdown
140
+ },
141
+ });
142
+ ```
143
+
97
144
  ### `client.connect(agentId)`
98
145
 
99
146
  Connect to an existing app session. State is automatically restored from the agent, including:
@@ -322,14 +369,14 @@ session.workspace.onChange((change) => {
322
369
 
323
370
  ## WebSocket Reliability
324
371
 
325
- Connections automatically reconnect with exponential backoff.
372
+ Connections automatically reconnect with exponential backoff. The first reconnect attempt is **immediate** (0ms delay) for fast recovery from brief network blips, followed by exponential backoff starting at 200ms.
326
373
 
327
374
  ```ts
328
375
  // Custom retry config
329
376
  await session.connect({
330
377
  retry: {
331
378
  enabled: true, // Default: true
332
- initialDelayMs: 1000, // Default: 1000
379
+ initialDelayMs: 200, // Default: 200 (used from 2nd attempt onward)
333
380
  maxDelayMs: 30000, // Default: 30000
334
381
  maxRetries: 10, // Default: Infinity
335
382
  },
@@ -339,6 +386,35 @@ await session.connect({
339
386
  await session.connect({ retry: { enabled: false } });
340
387
  ```
341
388
 
389
+ ### Auto-Preview on Reconnect
390
+
391
+ When connecting to an **existing app** (via `client.connect()`), the SDK automatically sends a `preview` message on connect and reconnect to ensure the preview deployment stays active:
392
+
393
+ ```ts
394
+ // Existing app - auto-preview is enabled by default
395
+ const session = await client.connect('agent-id');
396
+ await session.connect(); // Sends preview message automatically
397
+
398
+ // Override for new builds or disable
399
+ await session.connect({ autoRequestPreview: false });
400
+
401
+ // Force enable for new builds
402
+ const session = await client.build('Build a todo app');
403
+ await session.connect({ autoRequestPreview: true });
404
+ ```
405
+
406
+ ### Reconnect Events
407
+
408
+ ```ts
409
+ session.on('ws:reconnecting', ({ attempt, delayMs, reason }) => {
410
+ console.log(`Reconnecting (attempt ${attempt}, delay ${delayMs}ms, reason: ${reason})`);
411
+ });
412
+
413
+ session.on('ws:reconnected', () => {
414
+ console.log('Reconnected successfully');
415
+ });
416
+ ```
417
+
342
418
  ## HTTP Retry
343
419
 
344
420
  HTTP requests automatically retry on 5xx errors.
package/dist/index.d.ts CHANGED
@@ -5453,7 +5453,21 @@ type CodeGenArgs$1 = CodeGenArgs;
5453
5453
  export type BuildOptions = Omit<CodeGenArgs$1, "query"> & {
5454
5454
  autoConnect?: boolean;
5455
5455
  autoGenerate?: boolean;
5456
+ /**
5457
+ * Called for each blueprint chunk as it streams from the server.
5458
+ */
5456
5459
  onBlueprintChunk?: (chunk: string) => void;
5460
+ /**
5461
+ * Called if blueprint streaming fails. The session will be closed automatically.
5462
+ * Only relevant when `waitForBlueprint` is false.
5463
+ */
5464
+ onBlueprintError?: (error: Error) => void;
5465
+ /**
5466
+ * If true (default), `build()` waits for all blueprint chunks before returning.
5467
+ * If false, `build()` returns immediately after receiving the start event,
5468
+ * and blueprint chunks stream in the background via `onBlueprintChunk`.
5469
+ */
5470
+ waitForBlueprint?: boolean;
5457
5471
  };
5458
5472
  type TemplateFiles = Record<string, string>;
5459
5473
  export type BuildStartEvent = {
@@ -5554,6 +5568,7 @@ export type AgentEventMap = {
5554
5568
  delayMs: number;
5555
5569
  reason: "close" | "error";
5556
5570
  };
5571
+ "ws:reconnected": undefined;
5557
5572
  "ws:raw": {
5558
5573
  raw: unknown;
5559
5574
  };
@@ -5849,12 +5864,19 @@ type WaitUntilReadyOptions = WaitOptions;
5849
5864
  type BuildSessionConnectOptions = Omit<AgentConnectionOptions, "credentials"> & {
5850
5865
  /** If true (default), send `get_conversation_state` on socket open. */
5851
5866
  autoRequestConversationState?: boolean;
5867
+ /**
5868
+ * If true, send `preview` message on connect and reconnect to ensure preview exists.
5869
+ * Defaults to true for existing apps (via `client.connect()`), false for new builds.
5870
+ */
5871
+ autoRequestPreview?: boolean;
5852
5872
  /** Credentials to send via session_init after connection. */
5853
5873
  credentials?: Credentials;
5854
5874
  };
5855
5875
  type BuildSessionInit = {
5856
5876
  httpClient: HttpClient;
5857
5877
  defaultCredentials?: Credentials;
5878
+ /** True if this session is for an existing app (via client.connect). */
5879
+ isExistingApp?: boolean;
5858
5880
  };
5859
5881
  export declare class BuildSession {
5860
5882
  private init;
@@ -5948,6 +5970,10 @@ export declare class VibeClient {
5948
5970
  get baseUrl(): string;
5949
5971
  /**
5950
5972
  * Creates a new agent/app from a prompt and returns a BuildSession.
5973
+ *
5974
+ * By default, waits for all blueprint chunks before returning. Set
5975
+ * `waitForBlueprint: false` to return immediately after the start event,
5976
+ * with blueprint chunks streaming in the background.
5951
5977
  */
5952
5978
  build(prompt: string, options?: BuildOptions): Promise<BuildSession>;
5953
5979
  /** Connect to an existing agent/app by id. */
package/dist/index.js CHANGED
@@ -409,7 +409,7 @@ class SessionStateStore {
409
409
  const m = msg;
410
410
  const phaseInfo = extractPhaseInfo(m);
411
411
  const phaseFiles = extractPhaseFiles(m);
412
- const phases = this.updateOrAddPhase(phaseInfo, "validating", phaseFiles);
412
+ const phases = this.updateOrAddPhase(phaseInfo, "completed", phaseFiles);
413
413
  this.setState({
414
414
  phase: { status: "implemented", ...phaseInfo },
415
415
  phases
@@ -431,7 +431,7 @@ class SessionStateStore {
431
431
  const m = msg;
432
432
  const phaseInfo = extractPhaseInfo(m);
433
433
  const phaseFiles = extractPhaseFiles(m);
434
- const phases = this.updateOrAddPhase(phaseInfo, "completed", phaseFiles);
434
+ const phases = this.updateOrAddPhase(phaseInfo, "validating", phaseFiles);
435
435
  this.setState({
436
436
  phase: { status: "validated", ...phaseInfo },
437
437
  phases
@@ -618,7 +618,7 @@ async function withTimeout(promise, ms, message = "Operation timed out") {
618
618
  // src/ws.ts
619
619
  var WS_RETRY_DEFAULTS = {
620
620
  enabled: true,
621
- initialDelayMs: 1000,
621
+ initialDelayMs: 200,
622
622
  maxDelayMs: 30000,
623
623
  maxRetries: Infinity
624
624
  };
@@ -630,6 +630,7 @@ function createAgentConnection(getUrl, options = {}) {
630
630
  let closedByUser = false;
631
631
  let reconnectAttempts = 0;
632
632
  let reconnectTimer = null;
633
+ let hasConnectedBefore = false;
633
634
  const pendingSends = [];
634
635
  const maxPendingSends = 1000;
635
636
  function clearReconnectTimer() {
@@ -655,22 +656,34 @@ function createAgentConnection(getUrl, options = {}) {
655
656
  return;
656
657
  if (reconnectTimer)
657
658
  return;
658
- const delayMs = computeBackoffMs(reconnectAttempts, retryCfg);
659
+ const delayMs = reconnectAttempts === 0 ? 0 : computeBackoffMs(reconnectAttempts, retryCfg);
659
660
  emitter.emit("ws:reconnecting", {
660
661
  attempt: reconnectAttempts + 1,
661
662
  delayMs,
662
663
  reason
663
664
  });
664
665
  reconnectAttempts += 1;
665
- reconnectTimer = setTimeout(() => {
666
- reconnectTimer = null;
667
- connectNow();
668
- }, delayMs);
666
+ if (delayMs === 0) {
667
+ reconnectTimer = setTimeout(() => {
668
+ reconnectTimer = null;
669
+ connectNow();
670
+ }, 0);
671
+ } else {
672
+ reconnectTimer = setTimeout(() => {
673
+ reconnectTimer = null;
674
+ connectNow();
675
+ }, delayMs);
676
+ }
669
677
  }
670
678
  function onOpen() {
679
+ const isReconnect = hasConnectedBefore;
680
+ hasConnectedBefore = true;
671
681
  isOpen = true;
672
682
  reconnectAttempts = 0;
673
683
  emitter.emit("ws:open", undefined);
684
+ if (isReconnect) {
685
+ emitter.emit("ws:reconnected", undefined);
686
+ }
674
687
  flushPendingSends();
675
688
  }
676
689
  function onClose(e) {
@@ -1005,7 +1018,7 @@ class BuildSession {
1005
1018
  async connect(options = {}) {
1006
1019
  if (this.connection)
1007
1020
  return this.connection;
1008
- const { autoRequestConversationState, credentials, ...connectionOptions } = options;
1021
+ const { autoRequestConversationState, autoRequestPreview, credentials, ...connectionOptions } = options;
1009
1022
  const getUrl = async () => {
1010
1023
  const { ticket } = await this.init.httpClient.getWsTicket(this.agentId);
1011
1024
  const base = this.websocketUrl;
@@ -1026,6 +1039,7 @@ class BuildSession {
1026
1039
  });
1027
1040
  const sessionCredentials = credentials ?? this.init.defaultCredentials;
1028
1041
  const shouldRequestConversationState = autoRequestConversationState ?? true;
1042
+ const shouldRequestPreview = autoRequestPreview ?? (this.init.isExistingApp ?? false);
1029
1043
  this.connection.on("ws:open", () => {
1030
1044
  if (sessionCredentials) {
1031
1045
  this.connection?.send({
@@ -1036,6 +1050,14 @@ class BuildSession {
1036
1050
  if (shouldRequestConversationState) {
1037
1051
  this.connection?.send({ type: "get_conversation_state" });
1038
1052
  }
1053
+ if (shouldRequestPreview) {
1054
+ this.connection?.send({ type: "preview" });
1055
+ }
1056
+ });
1057
+ this.connection.on("ws:reconnected", () => {
1058
+ if (shouldRequestPreview) {
1059
+ this.connection?.send({ type: "preview" });
1060
+ }
1039
1061
  });
1040
1062
  return this.connection;
1041
1063
  }
@@ -1198,24 +1220,35 @@ class VibeClient {
1198
1220
  if (!resp.body) {
1199
1221
  throw new Error("Missing response body from /api/agent");
1200
1222
  }
1201
- let start = null;
1202
- for await (const obj of parseNdjsonStream(resp.body)) {
1203
- if (!start) {
1204
- start = obj;
1205
- continue;
1206
- }
1207
- const o = obj;
1208
- if (typeof o.chunk === "string") {
1209
- options.onBlueprintChunk?.(o.chunk);
1210
- }
1211
- }
1212
- if (!start) {
1223
+ const iterator = parseNdjsonStream(resp.body)[Symbol.asyncIterator]();
1224
+ const { value: start, done } = await iterator.next();
1225
+ if (done || !start) {
1213
1226
  throw new Error("No start event received from /api/agent");
1214
1227
  }
1215
1228
  const session = new BuildSession(start, {
1216
1229
  httpClient: this.http,
1217
1230
  ...options.credentials ? { defaultCredentials: options.credentials } : {}
1218
1231
  });
1232
+ const waitForBlueprint = options.waitForBlueprint ?? true;
1233
+ const processChunks = async () => {
1234
+ let result = await iterator.next();
1235
+ while (!result.done) {
1236
+ const obj = result.value;
1237
+ if (typeof obj.chunk === "string") {
1238
+ options.onBlueprintChunk?.(obj.chunk);
1239
+ }
1240
+ result = await iterator.next();
1241
+ }
1242
+ };
1243
+ if (waitForBlueprint) {
1244
+ await processChunks();
1245
+ } else {
1246
+ processChunks().catch((error) => {
1247
+ const err = error instanceof Error ? error : new Error(String(error));
1248
+ options.onBlueprintError?.(err);
1249
+ session.close();
1250
+ });
1251
+ }
1219
1252
  if (options.autoConnect ?? true) {
1220
1253
  await session.connect();
1221
1254
  if (options.autoGenerate ?? true) {
@@ -1235,6 +1268,7 @@ class VibeClient {
1235
1268
  };
1236
1269
  return new BuildSession(start, {
1237
1270
  httpClient: this.http,
1271
+ isExistingApp: true,
1238
1272
  ...options.credentials ? { defaultCredentials: options.credentials } : {}
1239
1273
  });
1240
1274
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cf-vibesdk/sdk",
3
- "version": "0.0.9",
3
+ "version": "0.1.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {