@blaxel/core 0.2.57-preview.30 → 0.2.57-preview.33

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.
@@ -3,8 +3,8 @@ import { authentication } from "../authentication/index.js";
3
3
  import { env } from "../common/env.js";
4
4
  import { fs, os, path } from "../common/node.js";
5
5
  // Build info - these placeholders are replaced at build time by build:replace-imports
6
- const BUILD_VERSION = "0.2.57-preview.30";
7
- const BUILD_COMMIT = "56d2203f7145b774778210b79f0641b8e3a5e175";
6
+ const BUILD_VERSION = "0.2.57-preview.33";
7
+ const BUILD_COMMIT = "c169eb11d41a069351c8203a2aad841f8cdef220";
8
8
  const BUILD_SENTRY_DSN = "https://fd5e60e1c9820e1eef5ccebb84a07127@o4508714045276160.ingest.us.sentry.io/4510465864564736";
9
9
  // Cache for config.yaml tracking value
10
10
  let configTrackingValue = null;
@@ -495,7 +495,7 @@ export const getProcess = (options) => {
495
495
  };
496
496
  /**
497
497
  * Execute a command
498
- * Execute a command and return process information
498
+ * Execute a command and return process information. If Accept header is text/event-stream, streams logs in SSE format and returns the process response as a final event.
499
499
  */
500
500
  export const postProcess = (options) => {
501
501
  return (options.client ?? _heyApiClient).post({
@@ -74,41 +74,36 @@ export class SandboxProcess extends SandboxAction {
74
74
  }
75
75
  async exec(process) {
76
76
  let onLog;
77
+ let onStdout;
78
+ let onStderr;
77
79
  if ('onLog' in process && process.onLog) {
78
80
  onLog = process.onLog;
79
81
  delete process.onLog;
80
82
  }
83
+ if ('onStdout' in process && process.onStdout) {
84
+ onStdout = process.onStdout;
85
+ delete process.onStdout;
86
+ }
87
+ if ('onStderr' in process && process.onStderr) {
88
+ onStderr = process.onStderr;
89
+ delete process.onStderr;
90
+ }
81
91
  // Store original wait_for_completion setting
82
92
  const shouldWaitForCompletion = process.waitForCompletion;
83
- // Always start process without wait_for_completion to avoid server-side blocking
84
- if (shouldWaitForCompletion && onLog) {
85
- process.waitForCompletion = false;
86
- }
87
- const { response, data, error } = await postProcess({
88
- body: process,
89
- baseUrl: this.url,
90
- client: this.client,
91
- });
92
- this.handleResponseError(response, data, error);
93
- let result = data;
94
- // Handle wait_for_completion with parallel log streaming
95
- if (shouldWaitForCompletion && onLog) {
96
- const streamControl = this.streamLogs(result.pid, { onLog });
97
- try {
98
- // Wait for process completion
99
- result = await this.wait(result.pid, { interval: 500, maxWait: 1000 * 60 * 60 });
100
- }
101
- finally {
102
- // Clean up log streaming
103
- if (streamControl) {
104
- streamControl.close();
105
- }
106
- }
93
+ // When waiting for completion with streaming callbacks, use streaming endpoint
94
+ if (shouldWaitForCompletion && (onLog || onStdout || onStderr)) {
95
+ return await this.execWithStreaming(process, { onLog, onStdout, onStderr });
107
96
  }
108
97
  else {
109
- // For non-blocking execution, set up log streaming immediately if requested
110
- if (onLog) {
111
- const streamControl = this.streamLogs(result.pid, { onLog });
98
+ const { response, data, error } = await postProcess({
99
+ body: process,
100
+ baseUrl: this.url,
101
+ client: this.client,
102
+ });
103
+ this.handleResponseError(response, data, error);
104
+ const result = data;
105
+ if (onLog || onStdout || onStderr) {
106
+ const streamControl = this.streamLogs(result.pid, { onLog, onStdout, onStderr });
112
107
  return {
113
108
  ...result,
114
109
  close() {
@@ -118,8 +113,123 @@ export class SandboxProcess extends SandboxAction {
118
113
  },
119
114
  };
120
115
  }
116
+ return result;
117
+ }
118
+ }
119
+ async execWithStreaming(processRequest, options) {
120
+ const headers = this.sandbox.forceUrl ? this.sandbox.headers : settings.headers;
121
+ const controller = new AbortController();
122
+ const response = await fetch(`${this.url}/process`, {
123
+ method: 'POST',
124
+ signal: controller.signal,
125
+ headers: {
126
+ ...headers,
127
+ 'Content-Type': 'application/json',
128
+ 'Accept': 'text/event-stream',
129
+ },
130
+ body: JSON.stringify(processRequest),
131
+ });
132
+ if (!response.ok) {
133
+ const errorText = await response.text();
134
+ throw new Error(`Failed to execute process: ${errorText}`);
121
135
  }
122
- return { ...result, close: () => { } };
136
+ const contentType = response.headers.get('Content-Type') || '';
137
+ const isStreaming = contentType.includes('application/x-ndjson');
138
+ // Fallback: server doesn't support streaming, use legacy approach
139
+ if (!isStreaming) {
140
+ const data = await response.json();
141
+ // If process already completed (server waited), just return with logs
142
+ if (data.status === 'completed' || data.status === 'failed') {
143
+ // Emit any captured logs through callbacks
144
+ if (data.stdout) {
145
+ for (const line of data.stdout.split('\n').filter(l => l)) {
146
+ options.onStdout?.(line);
147
+ }
148
+ }
149
+ if (data.stderr) {
150
+ for (const line of data.stderr.split('\n').filter(l => l)) {
151
+ options.onStderr?.(line);
152
+ }
153
+ }
154
+ if (data.logs) {
155
+ for (const line of data.logs.split('\n').filter(l => l)) {
156
+ options.onLog?.(line);
157
+ }
158
+ }
159
+ return {
160
+ ...data,
161
+ close: () => { },
162
+ };
163
+ }
164
+ return {
165
+ ...data,
166
+ close: () => { },
167
+ };
168
+ }
169
+ // Streaming response handling
170
+ if (!response.body) {
171
+ throw new Error('No response body for streaming');
172
+ }
173
+ const reader = response.body.getReader();
174
+ const decoder = new TextDecoder();
175
+ let buffer = '';
176
+ let result = null;
177
+ while (true) {
178
+ const readResult = await reader.read();
179
+ if (readResult.done)
180
+ break;
181
+ if (readResult.value && readResult.value instanceof Uint8Array) {
182
+ buffer += decoder.decode(readResult.value, { stream: true });
183
+ }
184
+ const lines = buffer.split(/\r?\n/);
185
+ buffer = lines.pop();
186
+ for (const line of lines) {
187
+ const parsed = JSON.parse(line);
188
+ switch (parsed.type) {
189
+ case 'stdout':
190
+ if (parsed.data) {
191
+ options.onStdout?.(parsed.data);
192
+ options.onLog?.(parsed.data);
193
+ }
194
+ break;
195
+ case 'stderr':
196
+ if (parsed.data) {
197
+ options.onStderr?.(parsed.data);
198
+ options.onLog?.(parsed.data);
199
+ }
200
+ break;
201
+ case 'result':
202
+ try {
203
+ result = JSON.parse(parsed.data);
204
+ }
205
+ catch {
206
+ throw new Error(`Failed to parse result JSON: ${parsed.data}`);
207
+ }
208
+ break;
209
+ default:
210
+ break;
211
+ }
212
+ }
213
+ }
214
+ // Process any remaining buffer
215
+ if (buffer.trim()) {
216
+ if (buffer.startsWith('result:')) {
217
+ const jsonStr = buffer.slice(7);
218
+ try {
219
+ result = JSON.parse(jsonStr);
220
+ }
221
+ catch {
222
+ throw new Error(`Failed to parse result JSON: ${jsonStr}`);
223
+ }
224
+ }
225
+ }
226
+ if (!result) {
227
+ throw new Error('No result received from streaming response');
228
+ }
229
+ return {
230
+ ...result,
231
+ close: () => controller.abort(),
232
+ };
123
233
  }
124
234
  async wait(identifier, { maxWait = 60000, interval = 1000 } = {}) {
125
235
  const startTime = Date.now();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blaxel/core",
3
- "version": "0.2.57-preview.30",
3
+ "version": "0.2.57-preview.33",
4
4
  "description": "Blaxel Core SDK for TypeScript",
5
5
  "license": "MIT",
6
6
  "author": "Blaxel, INC (https://blaxel.ai)",