instavm 0.8.0 → 0.8.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
@@ -64,7 +64,7 @@ const client = new InstaVM('', {
64
64
 
65
65
  // Execute code locally without session management
66
66
  const result = await client.execute("print('Hello from local container!')");
67
- console.log(result.output);
67
+ console.log(result.stdout);
68
68
 
69
69
  // Browser automation in local mode (no session required)
70
70
  const content = await client.browser.extractContent({
@@ -94,6 +94,9 @@ import { InstaVM } from 'instavm';
94
94
 
95
95
  const client = new InstaVM('your_api_key');
96
96
 
97
+ // Create a session first (required for upload)
98
+ await client.createSession();
99
+
97
100
  // Upload files to the execution environment
98
101
  const files = [
99
102
  {
@@ -110,7 +113,7 @@ console.log(result);
110
113
  const execution = await client.execute('python /remote/path/script.py', {
111
114
  language: 'bash'
112
115
  });
113
- console.log(execution.output);
116
+ console.log(execution.stdout);
114
117
  ```
115
118
 
116
119
  ### File Download
@@ -170,13 +173,18 @@ import { InstaVM } from 'instavm';
170
173
 
171
174
  const client = new InstaVM('your_api_key');
172
175
 
173
- // Execute command asynchronously (returns task info)
176
+ // Execute command asynchronously (returns task ID)
174
177
  const result = await client.executeAsync("sleep 5 && echo 'Long task complete!'", {
175
178
  language: 'bash'
176
179
  });
177
-
178
- console.log(`Task ${result.taskId} status: ${result.status}`);
179
- console.log(`Output so far: ${result.output}`);
180
+ const taskId = result.taskId;
181
+ console.log(`Task ${taskId} is running in background...`);
182
+
183
+ // Poll for task result
184
+ const taskResult = await client.getTaskResult(taskId, 2, 30);
185
+ console.log('Task complete!');
186
+ console.log(`Stdout: ${taskResult.stdout}`);
187
+ console.log(`Stderr: ${taskResult.stderr}`);
180
188
  ```
181
189
 
182
190
  ## Browser Automation
@@ -319,7 +327,7 @@ data = {"terms": search_terms, "timestamp": "2024-01-01"}
319
327
  print(json.dumps(data))
320
328
  `);
321
329
 
322
- const searchData = JSON.parse(dataPrep.output.trim());
330
+ const searchData = JSON.parse(dataPrep.stdout.trim());
323
331
 
324
332
  // Use browser to search and collect results
325
333
  const session = await client.browser.createSession();
@@ -359,7 +367,7 @@ top_words = sorted(word_counts.items(), key=lambda x: x[1], reverse=True)[:10]
359
367
  print("Top words:", top_words)
360
368
  `);
361
369
 
362
- console.log(analysis.output);
370
+ console.log(analysis.stdout);
363
371
  await client.dispose();
364
372
  ```
365
373
 
@@ -385,7 +393,7 @@ print(f"Std: {data['numbers'].std():.2f}")
385
393
  print(f"Categories: {data['categories'].value_counts().to_dict()}")
386
394
  `);
387
395
 
388
- console.log(result.output);
396
+ console.log(result.stdout);
389
397
  ```
390
398
 
391
399
  ### Bash Commands
@@ -404,7 +412,7 @@ echo "Memory Info:"
404
412
  free -h
405
413
  `, { language: 'bash' });
406
414
 
407
- console.log(sysInfo.output);
415
+ console.log(sysInfo.stdout);
408
416
  ```
409
417
 
410
418
  ### Session Persistence
@@ -415,7 +423,19 @@ await client.execute('data = [1, 2, 3, 4, 5]');
415
423
  await client.execute('total = sum(data)');
416
424
 
417
425
  const result = await client.execute('print(f"Total: {total}, Average: {total/len(data)}")');
418
- console.log(result.output); // Output: Total: 15, Average: 3.0
426
+ console.log(result.stdout); // Output: Total: 15, Average: 3.0
427
+ ```
428
+
429
+ ### Session Status Check
430
+
431
+ ```typescript
432
+ // Check if current session is still active
433
+ const isActive = await client.isSessionActive();
434
+ console.log(`Session active: ${isActive}`);
435
+
436
+ // Check specific session
437
+ const isOtherActive = await client.isSessionActive('session-id-123');
438
+ console.log(`Other session active: ${isOtherActive}`);
419
439
  ```
420
440
 
421
441
  ## Advanced Features
@@ -583,11 +603,12 @@ const client = new InstaVM('your_api_key', {
583
603
  ```typescript
584
604
  class InstaVM {
585
605
  constructor(apiKey: string, options?: InstaVMOptions)
586
-
606
+
587
607
  // Code execution
588
608
  execute(command: string, options?: ExecuteOptions): Promise<ExecutionResult>
589
609
  executeAsync(command: string, options?: ExecuteOptions): Promise<AsyncExecutionResult>
590
-
610
+ getTaskResult(taskId: string, pollInterval?: number, timeout?: number): Promise<TaskResult>
611
+
591
612
  // File operations
592
613
  upload(files: FileUpload[], options?: UploadOptions): Promise<UploadResult>
593
614
  download(filename: string, options?: DownloadOptions): Promise<DownloadResult>
@@ -595,14 +616,15 @@ class InstaVM {
595
616
  // Session management
596
617
  createSession(): Promise<string>
597
618
  closeSession(sessionId?: string): Promise<void>
619
+ isSessionActive(sessionId?: string): Promise<boolean>
598
620
  getUsage(sessionId?: string): Promise<UsageStats>
599
-
621
+
600
622
  // Browser automation
601
623
  browser: BrowserManager
602
-
624
+
603
625
  // Properties
604
626
  readonly sessionId: string | null
605
-
627
+
606
628
  // Cleanup
607
629
  dispose(): Promise<void>
608
630
  }
@@ -681,13 +703,24 @@ class BrowserSession extends EventEmitter {
681
703
 
682
704
  ```typescript
683
705
  interface ExecutionResult {
684
- output: string;
706
+ stdout: string;
707
+ stderr: string;
685
708
  success: boolean;
686
- executionTime: number;
709
+ executionTime?: number;
710
+ cpuTime?: number;
711
+ createdAt?: string;
687
712
  sessionId?: string;
688
713
  error?: string;
689
714
  }
690
715
 
716
+ interface TaskResult {
717
+ stdout: string;
718
+ stderr: string;
719
+ executionTime?: number;
720
+ cpuTime?: number;
721
+ createdAt?: string;
722
+ }
723
+
691
724
  interface NavigationResult {
692
725
  success: boolean;
693
726
  url: string;
@@ -734,7 +767,7 @@ async function withInstaVM(apiKey: string, callback: (client: InstaVM) => Promis
734
767
 
735
768
  await withInstaVM('your_api_key', async (client) => {
736
769
  const result = await client.execute('print("Hello, World!")');
737
- console.log(result.output);
770
+ console.log(result.stdout);
738
771
  });
739
772
  ```
740
773
 
@@ -828,7 +861,7 @@ async function main() {
828
861
 
829
862
  try {
830
863
  const result = await client.execute('print("Hello from CommonJS!")');
831
- console.log(result.output);
864
+ console.log(result.stdout);
832
865
  } catch (error) {
833
866
  if (error instanceof AuthenticationError) {
834
867
  console.error('Authentication failed');
@@ -886,6 +919,17 @@ All rights reserved. No redistribution or modification permitted.
886
919
 
887
920
  ## Changelog
888
921
 
922
+ ### Version 0.8.3
923
+
924
+ - ✅ **NEW**: `getTaskResult()` method - Poll for async task completion with configurable intervals
925
+ - ✅ **NEW**: `isSessionActive()` method - Check if session is still active by querying VM status
926
+ - ✅ **IMPROVED**: Execution result format - Now returns `stdout` and `stderr` separately (matching Python SDK)
927
+ - ✅ **IMPROVED**: Enhanced execution results - Added `cpuTime`, `executionTime`, and `createdAt` fields
928
+ - ✅ **IMPROVED**: Better error messages - Rate limit errors now show `detail` field from API response
929
+ - ✅ **FIXED**: Session status check - Now uses `/v1/sessions/status/` endpoint for accurate VM status
930
+ - ✅ **UPDATED**: closeSession behavior - Sessions auto-expire on server, no longer makes DELETE API call
931
+ - ✅ Full parity with Python SDK v0.8.3
932
+
889
933
  ### Version 0.4.0
890
934
 
891
935
  - ✅ **NEW**: Local execution mode support - Run code execution against local containers
package/dist/index.d.mts CHANGED
@@ -53,6 +53,10 @@ declare class HTTPClient {
53
53
  * POST request with form data (for file uploads)
54
54
  */
55
55
  postFormData<T = any>(url: string, formData: FormData, headers?: Record<string, string>): Promise<T>;
56
+ /**
57
+ * POST request with URL-encoded form data (like Python requests data= parameter)
58
+ */
59
+ postFormUrlEncoded<T = any>(url: string, data: any, headers?: Record<string, string>): Promise<T>;
56
60
  /**
57
61
  * GET request
58
62
  */
@@ -312,9 +316,12 @@ interface ExecuteOptions {
312
316
  sessionId?: string;
313
317
  }
314
318
  interface ExecutionResult {
315
- output: string;
319
+ stdout: string;
320
+ stderr: string;
316
321
  success: boolean;
317
- executionTime: number;
322
+ executionTime?: number;
323
+ cpuTime?: number;
324
+ createdAt?: string;
318
325
  sessionId?: string;
319
326
  error?: string;
320
327
  }
@@ -327,6 +334,13 @@ interface AsyncExecutionResult {
327
334
  sessionId?: string;
328
335
  error?: string;
329
336
  }
337
+ interface TaskResult {
338
+ stdout: string;
339
+ stderr: string;
340
+ executionTime?: number;
341
+ cpuTime?: number;
342
+ createdAt?: string;
343
+ }
330
344
  interface FileUpload {
331
345
  name: string;
332
346
  content: Buffer | string;
@@ -397,6 +411,16 @@ declare class InstaVM {
397
411
  * Execute code asynchronously
398
412
  */
399
413
  executeAsync(command: string, options?: ExecuteOptions): Promise<AsyncExecutionResult>;
414
+ /**
415
+ * Poll for async task result
416
+ *
417
+ * @param taskId - The task ID from executeAsync
418
+ * @param pollInterval - Seconds between polls (default: 1)
419
+ * @param timeout - Maximum seconds to wait (default: 60)
420
+ * @returns Task result with stdout, stderr, execution time, etc.
421
+ * @throws Error if task doesn't complete within timeout
422
+ */
423
+ getTaskResult(taskId: string, pollInterval?: number, timeout?: number): Promise<TaskResult>;
400
424
  /**
401
425
  * Upload files to the execution environment
402
426
  */
@@ -407,8 +431,15 @@ declare class InstaVM {
407
431
  createSession(): Promise<string>;
408
432
  /**
409
433
  * Close a session
434
+ * Note: Sessions auto-expire on the server side. This just clears local state.
410
435
  */
411
436
  closeSession(sessionId?: string): Promise<void>;
437
+ /**
438
+ * Check if current session is still active by checking VM status
439
+ *
440
+ * @returns true if session is active, false otherwise
441
+ */
442
+ isSessionActive(sessionId?: string): Promise<boolean>;
412
443
  /**
413
444
  * Get usage statistics for a session
414
445
  */
package/dist/index.d.ts CHANGED
@@ -53,6 +53,10 @@ declare class HTTPClient {
53
53
  * POST request with form data (for file uploads)
54
54
  */
55
55
  postFormData<T = any>(url: string, formData: FormData, headers?: Record<string, string>): Promise<T>;
56
+ /**
57
+ * POST request with URL-encoded form data (like Python requests data= parameter)
58
+ */
59
+ postFormUrlEncoded<T = any>(url: string, data: any, headers?: Record<string, string>): Promise<T>;
56
60
  /**
57
61
  * GET request
58
62
  */
@@ -312,9 +316,12 @@ interface ExecuteOptions {
312
316
  sessionId?: string;
313
317
  }
314
318
  interface ExecutionResult {
315
- output: string;
319
+ stdout: string;
320
+ stderr: string;
316
321
  success: boolean;
317
- executionTime: number;
322
+ executionTime?: number;
323
+ cpuTime?: number;
324
+ createdAt?: string;
318
325
  sessionId?: string;
319
326
  error?: string;
320
327
  }
@@ -327,6 +334,13 @@ interface AsyncExecutionResult {
327
334
  sessionId?: string;
328
335
  error?: string;
329
336
  }
337
+ interface TaskResult {
338
+ stdout: string;
339
+ stderr: string;
340
+ executionTime?: number;
341
+ cpuTime?: number;
342
+ createdAt?: string;
343
+ }
330
344
  interface FileUpload {
331
345
  name: string;
332
346
  content: Buffer | string;
@@ -397,6 +411,16 @@ declare class InstaVM {
397
411
  * Execute code asynchronously
398
412
  */
399
413
  executeAsync(command: string, options?: ExecuteOptions): Promise<AsyncExecutionResult>;
414
+ /**
415
+ * Poll for async task result
416
+ *
417
+ * @param taskId - The task ID from executeAsync
418
+ * @param pollInterval - Seconds between polls (default: 1)
419
+ * @param timeout - Maximum seconds to wait (default: 60)
420
+ * @returns Task result with stdout, stderr, execution time, etc.
421
+ * @throws Error if task doesn't complete within timeout
422
+ */
423
+ getTaskResult(taskId: string, pollInterval?: number, timeout?: number): Promise<TaskResult>;
400
424
  /**
401
425
  * Upload files to the execution environment
402
426
  */
@@ -407,8 +431,15 @@ declare class InstaVM {
407
431
  createSession(): Promise<string>;
408
432
  /**
409
433
  * Close a session
434
+ * Note: Sessions auto-expire on the server side. This just clears local state.
410
435
  */
411
436
  closeSession(sessionId?: string): Promise<void>;
437
+ /**
438
+ * Check if current session is still active by checking VM status
439
+ *
440
+ * @returns true if session is active, false otherwise
441
+ */
442
+ isSessionActive(sessionId?: string): Promise<boolean>;
412
443
  /**
413
444
  * Get usage statistics for a session
414
445
  */
package/dist/index.js CHANGED
@@ -211,7 +211,7 @@ var HTTPClient = class {
211
211
  const axiosError = error;
212
212
  const status = axiosError.response?.status;
213
213
  const data = axiosError.response?.data;
214
- const message = data?.message || data?.error || axiosError.message;
214
+ const message = data?.message || data?.error || data?.detail || axiosError.message;
215
215
  switch (status) {
216
216
  case 401:
217
217
  throw new AuthenticationError(message, {
@@ -223,14 +223,16 @@ var HTTPClient = class {
223
223
  statusCode: status,
224
224
  response: data
225
225
  });
226
- case 429:
226
+ case 429: {
227
227
  const retryAfter = parseInt(
228
228
  axiosError.response?.headers["retry-after"] || "60"
229
229
  );
230
- throw new RateLimitError(message, retryAfter, {
230
+ const rateLimitMessage = data?.detail || message;
231
+ throw new RateLimitError(rateLimitMessage, retryAfter, {
231
232
  statusCode: status,
232
233
  response: data
233
234
  });
235
+ }
234
236
  case 500:
235
237
  case 502:
236
238
  case 503:
@@ -308,6 +310,7 @@ var HTTPClient = class {
308
310
  */
309
311
  async postFormData(url, formData, headers) {
310
312
  const requestHeaders = {
313
+ "X-API-Key": this.config.apiKey,
311
314
  ...formData.getHeaders(),
312
315
  ...headers
313
316
  };
@@ -318,6 +321,26 @@ var HTTPClient = class {
318
321
  headers: requestHeaders
319
322
  });
320
323
  }
324
+ /**
325
+ * POST request with URL-encoded form data (like Python requests data= parameter)
326
+ */
327
+ async postFormUrlEncoded(url, data, headers) {
328
+ const params = new URLSearchParams();
329
+ for (const [key, value] of Object.entries(data)) {
330
+ params.append(key, String(value));
331
+ }
332
+ const requestHeaders = {
333
+ "X-API-Key": this.config.apiKey,
334
+ "Content-Type": "application/x-www-form-urlencoded",
335
+ ...headers
336
+ };
337
+ return this.request({
338
+ method: "POST",
339
+ url,
340
+ data: params.toString(),
341
+ headers: requestHeaders
342
+ });
343
+ }
321
344
  /**
322
345
  * GET request
323
346
  */
@@ -970,9 +993,12 @@ var InstaVM = class {
970
993
  this._sessionId = response.session_id;
971
994
  }
972
995
  return {
973
- output: response.stdout || response.output || "",
996
+ stdout: response.stdout || "",
997
+ stderr: response.stderr || "",
974
998
  success: response.success !== false,
975
- executionTime: response.execution_time || 0,
999
+ executionTime: response.execution_time,
1000
+ cpuTime: response.cpu_time,
1001
+ createdAt: response.created_at,
976
1002
  sessionId: response.session_id,
977
1003
  error: response.error
978
1004
  };
@@ -1028,11 +1054,58 @@ var InstaVM = class {
1028
1054
  );
1029
1055
  }
1030
1056
  }
1057
+ /**
1058
+ * Poll for async task result
1059
+ *
1060
+ * @param taskId - The task ID from executeAsync
1061
+ * @param pollInterval - Seconds between polls (default: 1)
1062
+ * @param timeout - Maximum seconds to wait (default: 60)
1063
+ * @returns Task result with stdout, stderr, execution time, etc.
1064
+ * @throws Error if task doesn't complete within timeout
1065
+ */
1066
+ async getTaskResult(taskId, pollInterval = 1, timeout = 60) {
1067
+ this.ensureNotLocal("Async task result retrieval");
1068
+ const startTime = Date.now();
1069
+ while (Date.now() - startTime < timeout * 1e3) {
1070
+ try {
1071
+ const response = await this.httpClient.get(
1072
+ `/v1/executions/${taskId}`,
1073
+ void 0,
1074
+ { "X-API-Key": this.httpClient.apiKey }
1075
+ );
1076
+ if (response.is_complete) {
1077
+ return {
1078
+ stdout: response.serialized_stdout || "",
1079
+ stderr: response.serialized_stderr || "",
1080
+ cpuTime: response.cpu_time,
1081
+ executionTime: response.execution_time,
1082
+ createdAt: response.created_at
1083
+ };
1084
+ }
1085
+ await new Promise((resolve) => setTimeout(resolve, pollInterval * 1e3));
1086
+ } catch (error) {
1087
+ if (error instanceof AuthenticationError || error instanceof RateLimitError || error instanceof NetworkError) {
1088
+ throw error;
1089
+ }
1090
+ if (Date.now() - startTime >= timeout * 1e3) {
1091
+ throw new ExecutionError(
1092
+ `Failed to get task result: ${error instanceof Error ? error.message : String(error)}`
1093
+ );
1094
+ }
1095
+ await new Promise((resolve) => setTimeout(resolve, pollInterval * 1e3));
1096
+ }
1097
+ }
1098
+ throw new ExecutionError(`Task ${taskId} timed out after ${timeout}s`);
1099
+ }
1031
1100
  /**
1032
1101
  * Upload files to the execution environment
1033
1102
  */
1034
1103
  async upload(files, options = {}) {
1035
1104
  this.ensureNotLocal("File upload");
1105
+ const sessionId = options.sessionId || this._sessionId;
1106
+ if (!sessionId) {
1107
+ throw new SessionError("Session ID not set. Please create a session first using createSession().");
1108
+ }
1036
1109
  const formData = new import_form_data.default();
1037
1110
  for (const file of files) {
1038
1111
  if (Buffer.isBuffer(file.content)) {
@@ -1044,9 +1117,7 @@ var InstaVM = class {
1044
1117
  formData.append("paths", file.path);
1045
1118
  }
1046
1119
  }
1047
- if (options.sessionId || this._sessionId) {
1048
- formData.append("session_id", options.sessionId || this._sessionId);
1049
- }
1120
+ formData.append("session_id", sessionId);
1050
1121
  if (options.remotePath) {
1051
1122
  formData.append("remote_path", options.remotePath);
1052
1123
  }
@@ -1096,20 +1167,39 @@ var InstaVM = class {
1096
1167
  }
1097
1168
  /**
1098
1169
  * Close a session
1170
+ * Note: Sessions auto-expire on the server side. This just clears local state.
1099
1171
  */
1100
1172
  async closeSession(sessionId) {
1101
1173
  const targetSessionId = sessionId || this._sessionId;
1102
1174
  if (!targetSessionId) {
1103
1175
  return;
1104
1176
  }
1177
+ if (targetSessionId === this._sessionId) {
1178
+ this._sessionId = null;
1179
+ }
1180
+ }
1181
+ /**
1182
+ * Check if current session is still active by checking VM status
1183
+ *
1184
+ * @returns true if session is active, false otherwise
1185
+ */
1186
+ async isSessionActive(sessionId) {
1187
+ const targetSessionId = sessionId || this._sessionId;
1188
+ if (!targetSessionId) {
1189
+ return false;
1190
+ }
1105
1191
  try {
1106
- await this.httpClient.delete(`/v1/sessions/${targetSessionId}`);
1107
- if (targetSessionId === this._sessionId) {
1108
- this._sessionId = null;
1109
- }
1192
+ const response = await this.httpClient.get(
1193
+ `/v1/sessions/status/${targetSessionId}`,
1194
+ void 0,
1195
+ { "X-API-Key": this.httpClient.apiKey }
1196
+ );
1197
+ return response.vm_status === "active";
1110
1198
  } catch (error) {
1111
- const errorMessage = error instanceof Error ? error.message : String(error);
1112
- console.warn(`Failed to close session ${targetSessionId}:`, errorMessage);
1199
+ if (error instanceof SessionError || error instanceof AuthenticationError || error instanceof NetworkError) {
1200
+ return false;
1201
+ }
1202
+ return false;
1113
1203
  }
1114
1204
  }
1115
1205
  /**
@@ -1149,11 +1239,12 @@ var InstaVM = class {
1149
1239
  throw new SessionError("No active session to download from");
1150
1240
  }
1151
1241
  try {
1152
- const response = await this.httpClient.postRaw("/download", {
1242
+ const response = await this.httpClient.postFormUrlEncoded("/download", {
1153
1243
  filename,
1154
1244
  session_id: targetSessionId
1155
1245
  });
1156
- const content = Buffer.from(response);
1246
+ const encodedContent = response.content || "";
1247
+ const content = encodedContent ? Buffer.from(encodedContent, "base64") : Buffer.from(response);
1157
1248
  return {
1158
1249
  success: true,
1159
1250
  filename,