@cf-vibesdk/sdk 0.0.8 → 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 +98 -2
- package/dist/index.d.ts +59 -0
- package/dist/index.js +93 -23
- package/package.json +1 -1
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:
|
|
@@ -250,6 +297,24 @@ if (session.phases.allCompleted()) {
|
|
|
250
297
|
|
|
251
298
|
// Get phase by ID
|
|
252
299
|
const phase = session.phases.get('phase-0');
|
|
300
|
+
|
|
301
|
+
// Subscribe to phase changes
|
|
302
|
+
const unsubscribe = session.phases.onChange((event) => {
|
|
303
|
+
console.log(`Phase ${event.type}:`, event.phase.name);
|
|
304
|
+
console.log(`Status: ${event.phase.status}`);
|
|
305
|
+
console.log(`Total phases: ${event.allPhases.length}`);
|
|
306
|
+
});
|
|
307
|
+
// Later: unsubscribe();
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
The `onChange` callback receives a `PhaseTimelineEvent`:
|
|
311
|
+
|
|
312
|
+
```ts
|
|
313
|
+
type PhaseTimelineEvent = {
|
|
314
|
+
type: 'added' | 'updated'; // New phase vs status/file change
|
|
315
|
+
phase: PhaseInfo; // The affected phase
|
|
316
|
+
allPhases: PhaseInfo[]; // All phases after this change
|
|
317
|
+
};
|
|
253
318
|
```
|
|
254
319
|
|
|
255
320
|
Each phase contains:
|
|
@@ -304,14 +369,14 @@ session.workspace.onChange((change) => {
|
|
|
304
369
|
|
|
305
370
|
## WebSocket Reliability
|
|
306
371
|
|
|
307
|
-
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.
|
|
308
373
|
|
|
309
374
|
```ts
|
|
310
375
|
// Custom retry config
|
|
311
376
|
await session.connect({
|
|
312
377
|
retry: {
|
|
313
378
|
enabled: true, // Default: true
|
|
314
|
-
initialDelayMs:
|
|
379
|
+
initialDelayMs: 200, // Default: 200 (used from 2nd attempt onward)
|
|
315
380
|
maxDelayMs: 30000, // Default: 30000
|
|
316
381
|
maxRetries: 10, // Default: Infinity
|
|
317
382
|
},
|
|
@@ -321,6 +386,35 @@ await session.connect({
|
|
|
321
386
|
await session.connect({ retry: { enabled: false } });
|
|
322
387
|
```
|
|
323
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
|
+
|
|
324
418
|
## HTTP Retry
|
|
325
419
|
|
|
326
420
|
HTTP requests automatically retry on 5xx errors.
|
|
@@ -403,6 +497,8 @@ import type {
|
|
|
403
497
|
PhaseStatus,
|
|
404
498
|
PhaseFileStatus,
|
|
405
499
|
PhaseEventType,
|
|
500
|
+
PhaseTimelineEvent,
|
|
501
|
+
PhaseTimelineChangeType,
|
|
406
502
|
|
|
407
503
|
// API
|
|
408
504
|
ApiResponse,
|
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
|
};
|
|
@@ -5568,6 +5583,8 @@ export type AgentEventMap = {
|
|
|
5568
5583
|
error: {
|
|
5569
5584
|
error: string;
|
|
5570
5585
|
};
|
|
5586
|
+
/** Emitted when the phase timeline changes (phase added or updated). */
|
|
5587
|
+
phases: PhaseTimelineEvent;
|
|
5571
5588
|
};
|
|
5572
5589
|
/**
|
|
5573
5590
|
* URL provider for WebSocket connections.
|
|
@@ -5657,6 +5674,18 @@ export type SessionPhases = {
|
|
|
5657
5674
|
/** Check if all phases are completed. */
|
|
5658
5675
|
allCompleted: () => boolean;
|
|
5659
5676
|
};
|
|
5677
|
+
/**
|
|
5678
|
+
* Event emitted when the phase timeline changes.
|
|
5679
|
+
*/
|
|
5680
|
+
export type PhaseTimelineChangeType = "added" | "updated";
|
|
5681
|
+
export type PhaseTimelineEvent = {
|
|
5682
|
+
/** Type of change: 'added' for new phase, 'updated' for status/file changes. */
|
|
5683
|
+
type: PhaseTimelineChangeType;
|
|
5684
|
+
/** The phase that was added or updated. */
|
|
5685
|
+
phase: PhaseInfo;
|
|
5686
|
+
/** All phases in the timeline after this change. */
|
|
5687
|
+
allPhases: PhaseInfo[];
|
|
5688
|
+
};
|
|
5660
5689
|
export type SessionDeployable = {
|
|
5661
5690
|
files: number;
|
|
5662
5691
|
reason: "generation_complete" | "phase_validated";
|
|
@@ -5759,6 +5788,11 @@ export declare class SessionStateStore {
|
|
|
5759
5788
|
private emitter;
|
|
5760
5789
|
get(): SessionState;
|
|
5761
5790
|
onChange(cb: (next: SessionState, prev: SessionState) => void): () => void;
|
|
5791
|
+
/**
|
|
5792
|
+
* Subscribe to phase timeline changes.
|
|
5793
|
+
* Fires when a phase is added or when a phase's status/files change.
|
|
5794
|
+
*/
|
|
5795
|
+
onPhaseChange(cb: (event: PhaseTimelineEvent) => void): () => void;
|
|
5762
5796
|
setConnection(state: ConnectionState): void;
|
|
5763
5797
|
applyWsMessage(msg: AgentWsServerMessage): void;
|
|
5764
5798
|
/**
|
|
@@ -5770,6 +5804,14 @@ export declare class SessionStateStore {
|
|
|
5770
5804
|
*/
|
|
5771
5805
|
private updateOrAddPhase;
|
|
5772
5806
|
private setState;
|
|
5807
|
+
/**
|
|
5808
|
+
* Compare old and new phases arrays and emit change events.
|
|
5809
|
+
*/
|
|
5810
|
+
private emitPhaseChanges;
|
|
5811
|
+
/**
|
|
5812
|
+
* Check if a phase has meaningfully changed (status or file statuses).
|
|
5813
|
+
*/
|
|
5814
|
+
private hasPhaseChanged;
|
|
5773
5815
|
clear(): void;
|
|
5774
5816
|
}
|
|
5775
5817
|
type WorkspaceChange = {
|
|
@@ -5822,12 +5864,19 @@ type WaitUntilReadyOptions = WaitOptions;
|
|
|
5822
5864
|
type BuildSessionConnectOptions = Omit<AgentConnectionOptions, "credentials"> & {
|
|
5823
5865
|
/** If true (default), send `get_conversation_state` on socket open. */
|
|
5824
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;
|
|
5825
5872
|
/** Credentials to send via session_init after connection. */
|
|
5826
5873
|
credentials?: Credentials;
|
|
5827
5874
|
};
|
|
5828
5875
|
type BuildSessionInit = {
|
|
5829
5876
|
httpClient: HttpClient;
|
|
5830
5877
|
defaultCredentials?: Credentials;
|
|
5878
|
+
/** True if this session is for an existing app (via client.connect). */
|
|
5879
|
+
isExistingApp?: boolean;
|
|
5831
5880
|
};
|
|
5832
5881
|
export declare class BuildSession {
|
|
5833
5882
|
private init;
|
|
@@ -5856,6 +5905,12 @@ export declare class BuildSession {
|
|
|
5856
5905
|
count: () => number;
|
|
5857
5906
|
/** Check if all phases are completed. */
|
|
5858
5907
|
allCompleted: () => boolean;
|
|
5908
|
+
/**
|
|
5909
|
+
* Subscribe to phase timeline changes.
|
|
5910
|
+
* Fires when a phase is added or when a phase's status/files change.
|
|
5911
|
+
* @returns Unsubscribe function.
|
|
5912
|
+
*/
|
|
5913
|
+
onChange: (cb: (event: PhaseTimelineEvent) => void) => (() => void);
|
|
5859
5914
|
};
|
|
5860
5915
|
readonly wait: {
|
|
5861
5916
|
generationStarted: (options?: WaitOptions) => Promise<{
|
|
@@ -5915,6 +5970,10 @@ export declare class VibeClient {
|
|
|
5915
5970
|
get baseUrl(): string;
|
|
5916
5971
|
/**
|
|
5917
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.
|
|
5918
5977
|
*/
|
|
5919
5978
|
build(prompt: string, options?: BuildOptions): Promise<BuildSession>;
|
|
5920
5979
|
/** Connect to an existing agent/app by id. */
|
package/dist/index.js
CHANGED
|
@@ -292,6 +292,9 @@ class SessionStateStore {
|
|
|
292
292
|
onChange(cb) {
|
|
293
293
|
return this.emitter.on("change", ({ prev, next }) => cb(next, prev));
|
|
294
294
|
}
|
|
295
|
+
onPhaseChange(cb) {
|
|
296
|
+
return this.emitter.on("phaseChange", cb);
|
|
297
|
+
}
|
|
295
298
|
setConnection(state) {
|
|
296
299
|
this.setState({ connection: state });
|
|
297
300
|
}
|
|
@@ -406,7 +409,7 @@ class SessionStateStore {
|
|
|
406
409
|
const m = msg;
|
|
407
410
|
const phaseInfo = extractPhaseInfo(m);
|
|
408
411
|
const phaseFiles = extractPhaseFiles(m);
|
|
409
|
-
const phases = this.updateOrAddPhase(phaseInfo, "
|
|
412
|
+
const phases = this.updateOrAddPhase(phaseInfo, "completed", phaseFiles);
|
|
410
413
|
this.setState({
|
|
411
414
|
phase: { status: "implemented", ...phaseInfo },
|
|
412
415
|
phases
|
|
@@ -428,7 +431,7 @@ class SessionStateStore {
|
|
|
428
431
|
const m = msg;
|
|
429
432
|
const phaseInfo = extractPhaseInfo(m);
|
|
430
433
|
const phaseFiles = extractPhaseFiles(m);
|
|
431
|
-
const phases = this.updateOrAddPhase(phaseInfo, "
|
|
434
|
+
const phases = this.updateOrAddPhase(phaseInfo, "validating", phaseFiles);
|
|
432
435
|
this.setState({
|
|
433
436
|
phase: { status: "validated", ...phaseInfo },
|
|
434
437
|
phases
|
|
@@ -524,7 +527,7 @@ class SessionStateStore {
|
|
|
524
527
|
const files = (phaseFiles ?? []).map((f) => ({
|
|
525
528
|
path: f.path,
|
|
526
529
|
purpose: f.purpose,
|
|
527
|
-
status: status === "completed" ? "completed" : "
|
|
530
|
+
status: status === "completed" ? "completed" : "pending"
|
|
528
531
|
}));
|
|
529
532
|
if (existingIndex >= 0) {
|
|
530
533
|
phases[existingIndex] = {
|
|
@@ -549,6 +552,38 @@ class SessionStateStore {
|
|
|
549
552
|
const next = { ...prev, ...patch };
|
|
550
553
|
this.state = next;
|
|
551
554
|
this.emitter.emit("change", { prev, next });
|
|
555
|
+
if (patch.phases && patch.phases !== prev.phases) {
|
|
556
|
+
this.emitPhaseChanges(prev.phases, patch.phases);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
emitPhaseChanges(prevPhases, nextPhases) {
|
|
560
|
+
for (const phase of nextPhases) {
|
|
561
|
+
const prevPhase = prevPhases.find((p) => p.id === phase.id);
|
|
562
|
+
if (!prevPhase) {
|
|
563
|
+
this.emitter.emit("phaseChange", {
|
|
564
|
+
type: "added",
|
|
565
|
+
phase,
|
|
566
|
+
allPhases: nextPhases
|
|
567
|
+
});
|
|
568
|
+
} else if (this.hasPhaseChanged(prevPhase, phase)) {
|
|
569
|
+
this.emitter.emit("phaseChange", {
|
|
570
|
+
type: "updated",
|
|
571
|
+
phase,
|
|
572
|
+
allPhases: nextPhases
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
hasPhaseChanged(prev, next) {
|
|
578
|
+
if (prev.status !== next.status)
|
|
579
|
+
return true;
|
|
580
|
+
if (prev.files.length !== next.files.length)
|
|
581
|
+
return true;
|
|
582
|
+
for (let i = 0;i < prev.files.length; i++) {
|
|
583
|
+
if (prev.files[i].status !== next.files[i].status)
|
|
584
|
+
return true;
|
|
585
|
+
}
|
|
586
|
+
return false;
|
|
552
587
|
}
|
|
553
588
|
clear() {
|
|
554
589
|
this.state = INITIAL_STATE;
|
|
@@ -583,7 +618,7 @@ async function withTimeout(promise, ms, message = "Operation timed out") {
|
|
|
583
618
|
// src/ws.ts
|
|
584
619
|
var WS_RETRY_DEFAULTS = {
|
|
585
620
|
enabled: true,
|
|
586
|
-
initialDelayMs:
|
|
621
|
+
initialDelayMs: 200,
|
|
587
622
|
maxDelayMs: 30000,
|
|
588
623
|
maxRetries: Infinity
|
|
589
624
|
};
|
|
@@ -595,6 +630,7 @@ function createAgentConnection(getUrl, options = {}) {
|
|
|
595
630
|
let closedByUser = false;
|
|
596
631
|
let reconnectAttempts = 0;
|
|
597
632
|
let reconnectTimer = null;
|
|
633
|
+
let hasConnectedBefore = false;
|
|
598
634
|
const pendingSends = [];
|
|
599
635
|
const maxPendingSends = 1000;
|
|
600
636
|
function clearReconnectTimer() {
|
|
@@ -620,22 +656,34 @@ function createAgentConnection(getUrl, options = {}) {
|
|
|
620
656
|
return;
|
|
621
657
|
if (reconnectTimer)
|
|
622
658
|
return;
|
|
623
|
-
const delayMs = computeBackoffMs(reconnectAttempts, retryCfg);
|
|
659
|
+
const delayMs = reconnectAttempts === 0 ? 0 : computeBackoffMs(reconnectAttempts, retryCfg);
|
|
624
660
|
emitter.emit("ws:reconnecting", {
|
|
625
661
|
attempt: reconnectAttempts + 1,
|
|
626
662
|
delayMs,
|
|
627
663
|
reason
|
|
628
664
|
});
|
|
629
665
|
reconnectAttempts += 1;
|
|
630
|
-
|
|
631
|
-
reconnectTimer =
|
|
632
|
-
|
|
633
|
-
|
|
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
|
+
}
|
|
634
677
|
}
|
|
635
678
|
function onOpen() {
|
|
679
|
+
const isReconnect = hasConnectedBefore;
|
|
680
|
+
hasConnectedBefore = true;
|
|
636
681
|
isOpen = true;
|
|
637
682
|
reconnectAttempts = 0;
|
|
638
683
|
emitter.emit("ws:open", undefined);
|
|
684
|
+
if (isReconnect) {
|
|
685
|
+
emitter.emit("ws:reconnected", undefined);
|
|
686
|
+
}
|
|
639
687
|
flushPendingSends();
|
|
640
688
|
}
|
|
641
689
|
function onClose(e) {
|
|
@@ -946,7 +994,8 @@ class BuildSession {
|
|
|
946
994
|
completed: () => this.state.get().phases.filter((p) => p.status === "completed"),
|
|
947
995
|
get: (id) => this.state.get().phases.find((p) => p.id === id),
|
|
948
996
|
count: () => this.state.get().phases.length,
|
|
949
|
-
allCompleted: () => this.state.get().phases.length > 0 && this.state.get().phases.every((p) => p.status === "completed")
|
|
997
|
+
allCompleted: () => this.state.get().phases.length > 0 && this.state.get().phases.every((p) => p.status === "completed"),
|
|
998
|
+
onChange: (cb) => this.state.onPhaseChange(cb)
|
|
950
999
|
};
|
|
951
1000
|
wait = {
|
|
952
1001
|
generationStarted: (options = {}) => this.waitForGenerationStarted(options),
|
|
@@ -969,7 +1018,7 @@ class BuildSession {
|
|
|
969
1018
|
async connect(options = {}) {
|
|
970
1019
|
if (this.connection)
|
|
971
1020
|
return this.connection;
|
|
972
|
-
const { autoRequestConversationState, credentials, ...connectionOptions } = options;
|
|
1021
|
+
const { autoRequestConversationState, autoRequestPreview, credentials, ...connectionOptions } = options;
|
|
973
1022
|
const getUrl = async () => {
|
|
974
1023
|
const { ticket } = await this.init.httpClient.getWsTicket(this.agentId);
|
|
975
1024
|
const base = this.websocketUrl;
|
|
@@ -990,6 +1039,7 @@ class BuildSession {
|
|
|
990
1039
|
});
|
|
991
1040
|
const sessionCredentials = credentials ?? this.init.defaultCredentials;
|
|
992
1041
|
const shouldRequestConversationState = autoRequestConversationState ?? true;
|
|
1042
|
+
const shouldRequestPreview = autoRequestPreview ?? (this.init.isExistingApp ?? false);
|
|
993
1043
|
this.connection.on("ws:open", () => {
|
|
994
1044
|
if (sessionCredentials) {
|
|
995
1045
|
this.connection?.send({
|
|
@@ -1000,6 +1050,14 @@ class BuildSession {
|
|
|
1000
1050
|
if (shouldRequestConversationState) {
|
|
1001
1051
|
this.connection?.send({ type: "get_conversation_state" });
|
|
1002
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
|
+
}
|
|
1003
1061
|
});
|
|
1004
1062
|
return this.connection;
|
|
1005
1063
|
}
|
|
@@ -1162,24 +1220,35 @@ class VibeClient {
|
|
|
1162
1220
|
if (!resp.body) {
|
|
1163
1221
|
throw new Error("Missing response body from /api/agent");
|
|
1164
1222
|
}
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
start = obj;
|
|
1169
|
-
continue;
|
|
1170
|
-
}
|
|
1171
|
-
const o = obj;
|
|
1172
|
-
if (typeof o.chunk === "string") {
|
|
1173
|
-
options.onBlueprintChunk?.(o.chunk);
|
|
1174
|
-
}
|
|
1175
|
-
}
|
|
1176
|
-
if (!start) {
|
|
1223
|
+
const iterator = parseNdjsonStream(resp.body)[Symbol.asyncIterator]();
|
|
1224
|
+
const { value: start, done } = await iterator.next();
|
|
1225
|
+
if (done || !start) {
|
|
1177
1226
|
throw new Error("No start event received from /api/agent");
|
|
1178
1227
|
}
|
|
1179
1228
|
const session = new BuildSession(start, {
|
|
1180
1229
|
httpClient: this.http,
|
|
1181
1230
|
...options.credentials ? { defaultCredentials: options.credentials } : {}
|
|
1182
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
|
+
}
|
|
1183
1252
|
if (options.autoConnect ?? true) {
|
|
1184
1253
|
await session.connect();
|
|
1185
1254
|
if (options.autoGenerate ?? true) {
|
|
@@ -1199,6 +1268,7 @@ class VibeClient {
|
|
|
1199
1268
|
};
|
|
1200
1269
|
return new BuildSession(start, {
|
|
1201
1270
|
httpClient: this.http,
|
|
1271
|
+
isExistingApp: true,
|
|
1202
1272
|
...options.credentials ? { defaultCredentials: options.credentials } : {}
|
|
1203
1273
|
});
|
|
1204
1274
|
}
|