@lizard-build/sdk 0.1.31 → 0.1.32

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/dist/index.d.mts CHANGED
@@ -25,7 +25,9 @@ interface ProcessOpts {
25
25
  envs?: Record<string, string>;
26
26
  user?: string;
27
27
  workdir?: string;
28
+ /** Called with each stdout line as it is produced, rather than at the end. */
28
29
  onStdout?: (data: string) => void;
30
+ /** Called with each stderr line as it is produced, rather than at the end. */
29
31
  onStderr?: (data: string) => void;
30
32
  }
31
33
  /**
@@ -62,6 +64,12 @@ declare class Process {
62
64
  * ```
63
65
  */
64
66
  exec(cmd: string, opts?: ProcessOpts): Promise<ProcessResult>;
67
+ /**
68
+ * Read an SSE exec stream, handing each line to the caller as it arrives while
69
+ * still accumulating the full result. Events are `{stream, line}` for output and
70
+ * `{exitCode}` at the end.
71
+ */
72
+ private consumeStream;
65
73
  }
66
74
 
67
75
  interface FsOpts {
package/dist/index.d.ts CHANGED
@@ -25,7 +25,9 @@ interface ProcessOpts {
25
25
  envs?: Record<string, string>;
26
26
  user?: string;
27
27
  workdir?: string;
28
+ /** Called with each stdout line as it is produced, rather than at the end. */
28
29
  onStdout?: (data: string) => void;
30
+ /** Called with each stderr line as it is produced, rather than at the end. */
29
31
  onStderr?: (data: string) => void;
30
32
  }
31
33
  /**
@@ -62,6 +64,12 @@ declare class Process {
62
64
  * ```
63
65
  */
64
66
  exec(cmd: string, opts?: ProcessOpts): Promise<ProcessResult>;
67
+ /**
68
+ * Read an SSE exec stream, handing each line to the caller as it arrives while
69
+ * still accumulating the full result. Events are `{stream, line}` for output and
70
+ * `{exitCode}` at the end.
71
+ */
72
+ private consumeStream;
65
73
  }
66
74
 
67
75
  interface FsOpts {
package/dist/index.js CHANGED
@@ -163,9 +163,10 @@ var Process = class {
163
163
  * ```
164
164
  */
165
165
  async exec(cmd, opts) {
166
+ const streaming = Boolean(opts?.onStdout || opts?.onStderr);
166
167
  const res = await fetch(`${this.config.apiUrl}/api/sandboxes/${this.sandboxId}/exec`, {
167
168
  method: "POST",
168
- headers: this.config.headers,
169
+ headers: streaming ? { ...this.config.headers, Accept: "text/event-stream" } : this.config.headers,
169
170
  body: JSON.stringify({
170
171
  cmd,
171
172
  envs: opts?.envs,
@@ -176,7 +177,51 @@ var Process = class {
176
177
  signal: AbortSignal.timeout(opts?.timeoutMs ?? 6e4)
177
178
  });
178
179
  if (!res.ok) await handleApiError(res);
179
- return res.json();
180
+ if (!streaming) return res.json();
181
+ return this.consumeStream(res, opts);
182
+ }
183
+ /**
184
+ * Read an SSE exec stream, handing each line to the caller as it arrives while
185
+ * still accumulating the full result. Events are `{stream, line}` for output and
186
+ * `{exitCode}` at the end.
187
+ */
188
+ async consumeStream(res, opts) {
189
+ const reader = res.body?.getReader();
190
+ if (!reader) return { stdout: "", stderr: "", exitCode: 0 };
191
+ const decoder = new TextDecoder();
192
+ let buf = "";
193
+ let stdout = "";
194
+ let stderr = "";
195
+ let exitCode = 0;
196
+ const handle = (raw) => {
197
+ if (!raw.startsWith("data:")) return;
198
+ let ev;
199
+ try {
200
+ ev = JSON.parse(raw.slice(5).trim());
201
+ } catch {
202
+ return;
203
+ }
204
+ if (ev.line !== void 0) {
205
+ if (ev.stream === "stderr") {
206
+ stderr += ev.line + "\n";
207
+ opts.onStderr?.(ev.line);
208
+ } else {
209
+ stdout += ev.line + "\n";
210
+ opts.onStdout?.(ev.line);
211
+ }
212
+ }
213
+ if (ev.exitCode !== void 0) exitCode = ev.exitCode;
214
+ };
215
+ for (; ; ) {
216
+ const { done, value } = await reader.read();
217
+ if (done) break;
218
+ buf += decoder.decode(value, { stream: true });
219
+ const lines = buf.split("\n");
220
+ buf = lines.pop() ?? "";
221
+ for (const l of lines) handle(l);
222
+ }
223
+ if (buf) handle(buf);
224
+ return { stdout: stdout.trimEnd(), stderr: stderr.trimEnd(), exitCode };
180
225
  }
181
226
  };
182
227
 
package/dist/index.mjs CHANGED
@@ -127,9 +127,10 @@ var Process = class {
127
127
  * ```
128
128
  */
129
129
  async exec(cmd, opts) {
130
+ const streaming = Boolean(opts?.onStdout || opts?.onStderr);
130
131
  const res = await fetch(`${this.config.apiUrl}/api/sandboxes/${this.sandboxId}/exec`, {
131
132
  method: "POST",
132
- headers: this.config.headers,
133
+ headers: streaming ? { ...this.config.headers, Accept: "text/event-stream" } : this.config.headers,
133
134
  body: JSON.stringify({
134
135
  cmd,
135
136
  envs: opts?.envs,
@@ -140,7 +141,51 @@ var Process = class {
140
141
  signal: AbortSignal.timeout(opts?.timeoutMs ?? 6e4)
141
142
  });
142
143
  if (!res.ok) await handleApiError(res);
143
- return res.json();
144
+ if (!streaming) return res.json();
145
+ return this.consumeStream(res, opts);
146
+ }
147
+ /**
148
+ * Read an SSE exec stream, handing each line to the caller as it arrives while
149
+ * still accumulating the full result. Events are `{stream, line}` for output and
150
+ * `{exitCode}` at the end.
151
+ */
152
+ async consumeStream(res, opts) {
153
+ const reader = res.body?.getReader();
154
+ if (!reader) return { stdout: "", stderr: "", exitCode: 0 };
155
+ const decoder = new TextDecoder();
156
+ let buf = "";
157
+ let stdout = "";
158
+ let stderr = "";
159
+ let exitCode = 0;
160
+ const handle = (raw) => {
161
+ if (!raw.startsWith("data:")) return;
162
+ let ev;
163
+ try {
164
+ ev = JSON.parse(raw.slice(5).trim());
165
+ } catch {
166
+ return;
167
+ }
168
+ if (ev.line !== void 0) {
169
+ if (ev.stream === "stderr") {
170
+ stderr += ev.line + "\n";
171
+ opts.onStderr?.(ev.line);
172
+ } else {
173
+ stdout += ev.line + "\n";
174
+ opts.onStdout?.(ev.line);
175
+ }
176
+ }
177
+ if (ev.exitCode !== void 0) exitCode = ev.exitCode;
178
+ };
179
+ for (; ; ) {
180
+ const { done, value } = await reader.read();
181
+ if (done) break;
182
+ buf += decoder.decode(value, { stream: true });
183
+ const lines = buf.split("\n");
184
+ buf = lines.pop() ?? "";
185
+ for (const l of lines) handle(l);
186
+ }
187
+ if (buf) handle(buf);
188
+ return { stdout: stdout.trimEnd(), stderr: stderr.trimEnd(), exitCode };
144
189
  }
145
190
  };
146
191
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lizard-build/sdk",
3
- "version": "0.1.31",
3
+ "version": "0.1.32",
4
4
  "description": "Firecracker microVM sandboxes for AI agents — boot Linux in milliseconds, run code, write files, expose ports over HTTPS, then pause a session with memory intact and resume it later. TypeScript SDK for Lizard Sandboxes (lizard.build).",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",