instavm 0.16.0 → 0.17.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.
Files changed (50) hide show
  1. package/README.md +86 -9
  2. package/dist/InstaVM-DjkmUcaP.d.mts +1393 -0
  3. package/dist/InstaVM-DjkmUcaP.d.ts +1393 -0
  4. package/dist/_instavmToolsCore-34H4iqVZ.d.mts +26 -0
  5. package/dist/_instavmToolsCore-BuaJyxXB.d.ts +26 -0
  6. package/dist/cli.js +5821 -2296
  7. package/dist/cli.js.map +1 -1
  8. package/dist/index.d.mts +12 -919
  9. package/dist/index.d.ts +12 -919
  10. package/dist/index.js +1464 -140
  11. package/dist/index.js.map +1 -1
  12. package/dist/index.mjs +1449 -135
  13. package/dist/index.mjs.map +1 -1
  14. package/dist/integrations/azure-openai.d.mts +18 -0
  15. package/dist/integrations/azure-openai.d.ts +18 -0
  16. package/dist/integrations/azure-openai.js +332 -0
  17. package/dist/integrations/azure-openai.js.map +1 -0
  18. package/dist/integrations/azure-openai.mjs +299 -0
  19. package/dist/integrations/azure-openai.mjs.map +1 -0
  20. package/dist/integrations/langchain.d.mts +7 -0
  21. package/dist/integrations/langchain.d.ts +7 -0
  22. package/dist/integrations/langchain.js +364 -0
  23. package/dist/integrations/langchain.js.map +1 -0
  24. package/dist/integrations/langchain.mjs +327 -0
  25. package/dist/integrations/langchain.mjs.map +1 -0
  26. package/dist/integrations/llamaindex.d.mts +11 -0
  27. package/dist/integrations/llamaindex.d.ts +11 -0
  28. package/dist/integrations/llamaindex.js +415 -0
  29. package/dist/integrations/llamaindex.js.map +1 -0
  30. package/dist/integrations/llamaindex.mjs +378 -0
  31. package/dist/integrations/llamaindex.mjs.map +1 -0
  32. package/dist/integrations/ollama.d.mts +35 -0
  33. package/dist/integrations/ollama.d.ts +35 -0
  34. package/dist/integrations/ollama.js +421 -0
  35. package/dist/integrations/ollama.js.map +1 -0
  36. package/dist/integrations/ollama.mjs +391 -0
  37. package/dist/integrations/ollama.mjs.map +1 -0
  38. package/dist/integrations/openai.d.mts +19 -0
  39. package/dist/integrations/openai.d.ts +19 -0
  40. package/dist/integrations/openai.js +302 -0
  41. package/dist/integrations/openai.js.map +1 -0
  42. package/dist/integrations/openai.mjs +272 -0
  43. package/dist/integrations/openai.mjs.map +1 -0
  44. package/package.json +44 -3
  45. package/dist/integrations/openai/index.d.mts +0 -16
  46. package/dist/integrations/openai/index.d.ts +0 -16
  47. package/dist/integrations/openai/index.js +0 -38
  48. package/dist/integrations/openai/index.js.map +0 -1
  49. package/dist/integrations/openai/index.mjs +0 -12
  50. package/dist/integrations/openai/index.mjs.map +0 -1
package/dist/index.mjs CHANGED
@@ -31,6 +31,11 @@ var QuotaExceededError = class extends InstaVMError {
31
31
  super(message, { ...options, statusCode: 402 });
32
32
  }
33
33
  };
34
+ var ForbiddenError = class extends InstaVMError {
35
+ constructor(message = "Forbidden", options) {
36
+ super(message, { ...options, statusCode: 403 });
37
+ }
38
+ };
34
39
  var NetworkError = class extends InstaVMError {
35
40
  constructor(message = "Network error", options) {
36
41
  super(message, options);
@@ -125,11 +130,17 @@ async function withRetry(fn, options) {
125
130
  throw lastError;
126
131
  }
127
132
 
133
+ // src/version.ts
134
+ var INSTAVM_JS_SDK_VERSION = "0.17.0";
135
+
128
136
  // src/client/HTTPClient.ts
129
137
  var HTTPClient = class {
130
138
  get apiKey() {
131
139
  return this.config.apiKey;
132
140
  }
141
+ get baseURL() {
142
+ return this.config.baseURL;
143
+ }
133
144
  constructor(config) {
134
145
  this.config = config;
135
146
  this.client = axios.create({
@@ -137,7 +148,7 @@ var HTTPClient = class {
137
148
  timeout: config.timeout,
138
149
  headers: {
139
150
  "Content-Type": "application/json",
140
- "User-Agent": "instavm-js-sdk/0.16.0"
151
+ "User-Agent": `instavm-js-sdk/${INSTAVM_JS_SDK_VERSION}`
141
152
  }
142
153
  });
143
154
  this.setupInterceptors();
@@ -170,6 +181,11 @@ var HTTPClient = class {
170
181
  statusCode: status,
171
182
  response: data
172
183
  });
184
+ case 403:
185
+ throw new ForbiddenError(message, {
186
+ statusCode: status,
187
+ response: data
188
+ });
173
189
  case 429: {
174
190
  const retryAfter = parseInt(
175
191
  axiosError.response?.headers["retry-after"] || "60"
@@ -523,12 +539,24 @@ var BrowserSession = class extends EventEmitter {
523
539
  */
524
540
  async scroll(options = {}) {
525
541
  this.ensureActive();
542
+ if (!options.selector && options.x == null && options.y == null) {
543
+ throw new BrowserInteractionError(
544
+ "At least one of 'selector', 'x', or 'y' must be provided for scrolling."
545
+ );
546
+ }
526
547
  const requestData = {
527
548
  session_id: this.sessionId,
528
- x: options.x || 0,
529
- y: options.y || 500,
530
549
  behavior: options.behavior || "auto"
531
550
  };
551
+ if (options.selector) {
552
+ requestData.selector = options.selector;
553
+ }
554
+ if (options.x != null) {
555
+ requestData.x = options.x;
556
+ }
557
+ if (options.y != null) {
558
+ requestData.y = options.y;
559
+ }
532
560
  try {
533
561
  await this.httpClient.post(
534
562
  "/v1/browser/interactions/scroll",
@@ -575,11 +603,13 @@ var BrowserSession = class extends EventEmitter {
575
603
  async extractElements(selector, attributes = ["text"], options = {}) {
576
604
  this.ensureActive();
577
605
  const requestData = {
578
- selector,
579
606
  attributes,
580
607
  session_id: this.sessionId,
581
608
  max_results: options.maxResults || 100
582
609
  };
610
+ if (selector) {
611
+ requestData.selector = selector;
612
+ }
583
613
  try {
584
614
  const response = await this.httpClient.post(
585
615
  "/v1/browser/interactions/extract",
@@ -646,6 +676,29 @@ var BrowserSession = class extends EventEmitter {
646
676
  );
647
677
  }
648
678
  }
679
+ /**
680
+ * Wait for a string condition (parity with Python ``wait_for``).
681
+ */
682
+ async waitFor(condition, selector, timeout = 3e4) {
683
+ this.ensureActive();
684
+ const requestData = {
685
+ session_id: this.sessionId,
686
+ condition,
687
+ timeout
688
+ };
689
+ if (selector) {
690
+ requestData.selector = selector;
691
+ }
692
+ try {
693
+ return await this.httpClient.post("/v1/browser/interactions/wait", requestData);
694
+ } catch (error) {
695
+ const errorMessage = getErrorMessage(error);
696
+ if (errorMessage.includes("timeout")) {
697
+ throw new BrowserTimeoutError(`Wait condition timeout: ${condition}`, { cause: error });
698
+ }
699
+ throw new BrowserInteractionError(`Wait failed: ${errorMessage}`, { cause: error });
700
+ }
701
+ }
649
702
  /**
650
703
  * Wait for a condition
651
704
  */
@@ -715,12 +768,45 @@ var BrowserSession = class extends EventEmitter {
715
768
  }
716
769
  };
717
770
 
771
+ // src/utils/path.ts
772
+ function encodePathSegment(value) {
773
+ const segment = String(value);
774
+ if (segment === "." || segment === "..") {
775
+ throw new Error("Path traversal not allowed in path segment");
776
+ }
777
+ return encodeURIComponent(segment);
778
+ }
779
+ function encodePathSegments(path2) {
780
+ const segments = path2.replace(/^\/+/, "").split("/").filter((segment) => segment.length > 0 && segment !== ".");
781
+ if (segments.some((segment) => segment === "..")) {
782
+ throw new Error("Path traversal not allowed in proxy path");
783
+ }
784
+ return segments.map((segment) => encodeURIComponent(segment)).join("/");
785
+ }
786
+
718
787
  // src/client/BrowserManager.ts
719
788
  var BrowserManager = class {
720
- constructor(httpClient, local = false) {
789
+ constructor(httpClient, local = false, getClient) {
721
790
  this.activeSessions = /* @__PURE__ */ new Map();
791
+ this._defaultBrowserSessionId = null;
792
+ this._defaultSessionPromise = null;
722
793
  this.httpClient = httpClient;
723
794
  this.local = local;
795
+ this.getClient = getClient;
796
+ }
797
+ async ensureDefaultBrowserSessionId() {
798
+ if (this._defaultBrowserSessionId) {
799
+ return this._defaultBrowserSessionId;
800
+ }
801
+ if (!this._defaultSessionPromise) {
802
+ this._defaultSessionPromise = this.createSession().catch((err) => {
803
+ this._defaultSessionPromise = null;
804
+ throw err;
805
+ });
806
+ }
807
+ const session = await this._defaultSessionPromise;
808
+ this._defaultBrowserSessionId = session.sessionId;
809
+ return this._defaultBrowserSessionId;
724
810
  }
725
811
  /**
726
812
  * Create a new browser session
@@ -737,10 +823,7 @@ var BrowserManager = class {
737
823
  user_agent: options.userAgent
738
824
  };
739
825
  try {
740
- const response = await this.httpClient.post(
741
- "/v1/browser/sessions/",
742
- requestData
743
- );
826
+ const response = await this.httpClient.post("/v1/browser/sessions/", requestData);
744
827
  if (!response.session_id) {
745
828
  throw new BrowserSessionError("No session ID returned from server");
746
829
  }
@@ -748,14 +831,17 @@ var BrowserManager = class {
748
831
  this.activeSessions.set(response.session_id, session);
749
832
  session.on("close", () => {
750
833
  this.activeSessions.delete(response.session_id);
834
+ if (this._defaultBrowserSessionId === response.session_id) {
835
+ this._defaultBrowserSessionId = null;
836
+ this._defaultSessionPromise = null;
837
+ }
751
838
  });
752
839
  return session;
753
840
  } catch (error) {
754
841
  const errorMessage = error instanceof Error ? error.message : String(error);
755
- throw new BrowserSessionError(
756
- `Failed to create browser session: ${errorMessage}`,
757
- { cause: error }
758
- );
842
+ throw new BrowserSessionError(`Failed to create browser session: ${errorMessage}`, {
843
+ cause: error
844
+ });
759
845
  }
760
846
  }
761
847
  /**
@@ -763,9 +849,8 @@ var BrowserManager = class {
763
849
  */
764
850
  async getSession(sessionId) {
765
851
  try {
766
- const response = await this.httpClient.get(
767
- `/v1/browser/sessions/${sessionId}`
768
- );
852
+ const safe = encodePathSegment(sessionId);
853
+ const response = await this.httpClient.get(`/v1/browser/sessions/${safe}`);
769
854
  return {
770
855
  sessionId: response.session_id,
771
856
  status: response.status || "active",
@@ -776,10 +861,7 @@ var BrowserManager = class {
776
861
  };
777
862
  } catch (error) {
778
863
  const errorMessage = error instanceof Error ? error.message : String(error);
779
- throw new BrowserSessionError(
780
- `Failed to get session info: ${errorMessage}`,
781
- { cause: error }
782
- );
864
+ throw new BrowserSessionError(`Failed to get session info: ${errorMessage}`, { cause: error });
783
865
  }
784
866
  }
785
867
  /**
@@ -788,10 +870,11 @@ var BrowserManager = class {
788
870
  async listSessions() {
789
871
  try {
790
872
  const response = await this.httpClient.get("/v1/browser/sessions/");
791
- if (!Array.isArray(response.sessions)) {
873
+ const sessions = Array.isArray(response) ? response : response?.sessions;
874
+ if (!Array.isArray(sessions)) {
792
875
  return [];
793
876
  }
794
- return response.sessions.map((session) => ({
877
+ return sessions.map((session) => ({
795
878
  sessionId: session.session_id,
796
879
  status: session.status || "active",
797
880
  createdAt: session.created_at,
@@ -801,52 +884,62 @@ var BrowserManager = class {
801
884
  }));
802
885
  } catch (error) {
803
886
  const errorMessage = error instanceof Error ? error.message : String(error);
804
- throw new BrowserSessionError(
805
- `Failed to list sessions: ${errorMessage}`,
806
- { cause: error }
807
- );
887
+ throw new BrowserSessionError(`Failed to list sessions: ${errorMessage}`, { cause: error });
808
888
  }
809
889
  }
810
- /**
811
- * Get a locally tracked session
812
- */
813
890
  getLocalSession(sessionId) {
814
891
  return this.activeSessions.get(sessionId);
815
892
  }
816
- /**
817
- * Get all locally tracked sessions
818
- */
893
+ attachSession(sessionId) {
894
+ const existing = this.activeSessions.get(sessionId);
895
+ if (existing) {
896
+ return existing;
897
+ }
898
+ const session = new BrowserSession(sessionId, this.httpClient);
899
+ this.activeSessions.set(sessionId, session);
900
+ session.on("close", () => {
901
+ this.activeSessions.delete(sessionId);
902
+ if (this._defaultBrowserSessionId === sessionId) {
903
+ this._defaultBrowserSessionId = null;
904
+ this._defaultSessionPromise = null;
905
+ }
906
+ });
907
+ return session;
908
+ }
819
909
  getLocalSessions() {
820
910
  return Array.from(this.activeSessions.values());
821
911
  }
822
- /**
823
- * Close all active sessions
824
- */
825
912
  async closeAllSessions() {
826
913
  const sessions = Array.from(this.activeSessions.values());
827
- await Promise.allSettled(
828
- sessions.map((session) => session.close())
829
- );
914
+ await Promise.allSettled(sessions.map((session) => session.close()));
830
915
  this.activeSessions.clear();
916
+ this._defaultBrowserSessionId = null;
917
+ this._defaultSessionPromise = null;
831
918
  }
832
919
  /**
833
- * Navigate to a URL (local mode support - no session required)
920
+ * Navigate to a URL (local: no session; cloud: auto default browser session)
834
921
  */
835
922
  async navigate(url, options = {}) {
836
923
  if (!this.local) {
837
- throw new UnsupportedOperationError(
838
- "navigate() without session is only supported in local mode. In cloud mode, create a session first."
839
- );
924
+ const client = this.getClient?.();
925
+ if (!client) {
926
+ throw new UnsupportedOperationError("BrowserManager is missing InstaVM client reference.");
927
+ }
928
+ const sid = await this.ensureDefaultBrowserSessionId();
929
+ const raw = await client.browserNavigate(url, sid, options.waitTimeout ?? 3e4);
930
+ return {
931
+ success: raw.success !== false,
932
+ url: raw.url || url,
933
+ title: raw.title,
934
+ status: raw.status
935
+ };
840
936
  }
841
937
  const requestData = {
842
938
  url,
843
939
  wait_timeout: options.waitTimeout || 3e4
844
940
  };
845
941
  try {
846
- const response = await this.httpClient.post(
847
- "/v1/browser/interactions/navigate",
848
- requestData
849
- );
942
+ const response = await this.httpClient.post("/v1/browser/interactions/navigate", requestData);
850
943
  return {
851
944
  success: response.success !== false,
852
945
  url: response.url || url,
@@ -855,20 +948,112 @@ var BrowserManager = class {
855
948
  };
856
949
  } catch (error) {
857
950
  const errorMessage = error instanceof Error ? error.message : String(error);
858
- throw new BrowserNavigationError(
859
- `Navigation failed: ${errorMessage}`,
860
- { cause: error }
861
- );
951
+ throw new BrowserNavigationError(`Navigation failed: ${errorMessage}`, { cause: error });
952
+ }
953
+ }
954
+ async click(selector, sessionId, force = false, timeout = 3e4) {
955
+ if (!this.local) {
956
+ const client = this.getClient?.();
957
+ if (!client) {
958
+ throw new UnsupportedOperationError("BrowserManager is missing InstaVM client reference.");
959
+ }
960
+ const sid = sessionId ?? await this.ensureDefaultBrowserSessionId();
961
+ await client.browserClick(selector, sid, force, timeout);
962
+ return;
963
+ }
964
+ throw new UnsupportedOperationError("click() without session is only supported in cloud mode via InstaVM.");
965
+ }
966
+ async type(selector, text, sessionId, delay = 100, timeout = 3e4) {
967
+ if (!this.local) {
968
+ const client = this.getClient?.();
969
+ if (!client) {
970
+ throw new UnsupportedOperationError("BrowserManager is missing InstaVM client reference.");
971
+ }
972
+ const sid = sessionId ?? await this.ensureDefaultBrowserSessionId();
973
+ await client.browserType(selector, text, sid, delay, timeout);
974
+ return;
862
975
  }
976
+ throw new UnsupportedOperationError("type() requires cloud mode with InstaVM client.");
977
+ }
978
+ async fill(selector, value, sessionId, timeout = 3e4) {
979
+ if (!this.local) {
980
+ const client = this.getClient?.();
981
+ if (!client) {
982
+ throw new UnsupportedOperationError("BrowserManager is missing InstaVM client reference.");
983
+ }
984
+ const sid = sessionId ?? await this.ensureDefaultBrowserSessionId();
985
+ await client.browserFill(selector, value, sid, timeout);
986
+ return;
987
+ }
988
+ throw new UnsupportedOperationError("fill() requires cloud mode with InstaVM client.");
989
+ }
990
+ async scroll(options = {}) {
991
+ if (!this.local) {
992
+ const client = this.getClient?.();
993
+ if (!client) {
994
+ throw new UnsupportedOperationError("BrowserManager is missing InstaVM client reference.");
995
+ }
996
+ const sid = options.sessionId ?? await this.ensureDefaultBrowserSessionId();
997
+ await client.browserScroll(sid, options.selector ?? null, options.x ?? null, options.y ?? null);
998
+ return;
999
+ }
1000
+ throw new UnsupportedOperationError("scroll() cloud convenience requires InstaVM client.");
1001
+ }
1002
+ async waitFor(condition, selector, sessionId, timeout = 3e4) {
1003
+ if (!this.local) {
1004
+ const client = this.getClient?.();
1005
+ if (!client) {
1006
+ throw new UnsupportedOperationError("BrowserManager is missing InstaVM client reference.");
1007
+ }
1008
+ const sid = sessionId ?? await this.ensureDefaultBrowserSessionId();
1009
+ return client.browserWait(condition, sid, selector ?? null, timeout);
1010
+ }
1011
+ throw new UnsupportedOperationError("waitFor() requires cloud mode with InstaVM client.");
1012
+ }
1013
+ async extractElements(selector, sessionId, attributes, options = {}) {
1014
+ if (!this.local) {
1015
+ const client = this.getClient?.();
1016
+ if (!client) {
1017
+ throw new UnsupportedOperationError("BrowserManager is missing InstaVM client reference.");
1018
+ }
1019
+ const sid = sessionId ?? await this.ensureDefaultBrowserSessionId();
1020
+ const attrs = attributes ?? ["text"];
1021
+ const result = await client.browserExtractElements(sid, selector ?? null, attrs);
1022
+ if (options.maxResults !== void 0 && Array.isArray(result)) {
1023
+ return result.slice(0, options.maxResults);
1024
+ }
1025
+ return result;
1026
+ }
1027
+ throw new UnsupportedOperationError("extractElements() requires cloud mode with InstaVM client.");
863
1028
  }
864
1029
  /**
865
- * Extract LLM-friendly content (local mode support - no session required)
1030
+ * Extract LLM-friendly content (local: URL required; cloud: optional session)
866
1031
  */
867
1032
  async extractContent(options = {}) {
868
1033
  if (!this.local) {
869
- throw new UnsupportedOperationError(
870
- "extractContent() without session is only supported in local mode. In cloud mode, create a session first."
1034
+ const client = this.getClient?.();
1035
+ if (!client) {
1036
+ throw new UnsupportedOperationError("BrowserManager is missing InstaVM client reference.");
1037
+ }
1038
+ let sid = options.sessionId;
1039
+ if (!sid) {
1040
+ sid = await this.ensureDefaultBrowserSessionId();
1041
+ }
1042
+ const raw = await client.browserExtractContent(
1043
+ sid,
1044
+ options.url ?? null,
1045
+ options.includeInteractive !== false,
1046
+ options.includeAnchors !== false,
1047
+ options.maxAnchors ?? 50
871
1048
  );
1049
+ return {
1050
+ readableContent: {
1051
+ ...raw.readable_content || {},
1052
+ content: raw.readable_content?.content || ""
1053
+ },
1054
+ interactiveElements: raw.interactive_elements,
1055
+ contentAnchors: raw.content_anchors
1056
+ };
872
1057
  }
873
1058
  if (!options.url) {
874
1059
  throw new BrowserInteractionError("url is required in local mode");
@@ -880,10 +1065,7 @@ var BrowserManager = class {
880
1065
  max_anchors: options.maxAnchors ?? 50
881
1066
  };
882
1067
  try {
883
- const response = await this.httpClient.post(
884
- "/v1/browser/interactions/content",
885
- requestData
886
- );
1068
+ const response = await this.httpClient.post("/v1/browser/interactions/content", requestData);
887
1069
  return {
888
1070
  readableContent: {
889
1071
  ...response.readable_content || {},
@@ -894,36 +1076,33 @@ var BrowserManager = class {
894
1076
  };
895
1077
  } catch (error) {
896
1078
  const errorMessage = error instanceof Error ? error.message : String(error);
897
- throw new BrowserInteractionError(
898
- `Content extraction failed: ${errorMessage}`,
899
- { cause: error }
900
- );
1079
+ throw new BrowserInteractionError(`Content extraction failed: ${errorMessage}`, { cause: error });
901
1080
  }
902
1081
  }
903
1082
  /**
904
- * Clean up resources
1083
+ * Closes the default browser session if one was created for cloud convenience.
905
1084
  */
1085
+ async close() {
1086
+ if (!this._defaultBrowserSessionId) {
1087
+ return;
1088
+ }
1089
+ const sid = this._defaultBrowserSessionId;
1090
+ const session = this.activeSessions.get(sid);
1091
+ if (session) {
1092
+ try {
1093
+ await session.close();
1094
+ } catch {
1095
+ }
1096
+ }
1097
+ this._defaultBrowserSessionId = null;
1098
+ this._defaultSessionPromise = null;
1099
+ }
906
1100
  async dispose() {
1101
+ await this.close();
907
1102
  await this.closeAllSessions();
908
1103
  }
909
1104
  };
910
1105
 
911
- // src/utils/path.ts
912
- function encodePathSegment(value) {
913
- const segment = String(value);
914
- if (segment === "." || segment === "..") {
915
- throw new Error("Path traversal not allowed in path segment");
916
- }
917
- return encodeURIComponent(segment);
918
- }
919
- function encodePathSegments(path2) {
920
- const segments = path2.replace(/^\/+/, "").split("/").filter((segment) => segment.length > 0 && segment !== ".");
921
- if (segments.some((segment) => segment === "..")) {
922
- throw new Error("Path traversal not allowed in proxy path");
923
- }
924
- return segments.map((segment) => encodeURIComponent(segment)).join("/");
925
- }
926
-
927
1106
  // src/client/VMsManager.ts
928
1107
  var VMsManager = class {
929
1108
  constructor(httpClient, local = false) {
@@ -1622,60 +1801,760 @@ var VolumesManager = class {
1622
1801
  }
1623
1802
  };
1624
1803
 
1625
- // src/client/InstaVM.ts
1626
- import FormData2 from "form-data";
1627
- var InstaVM = class {
1628
- constructor(apiKey, options = {}) {
1629
- this._sessionId = null;
1630
- this._vmUsed = false;
1631
- this.local = options.local || false;
1632
- this.timeout = options.timeout || 3e5;
1633
- this.memory_mb = options.memory_mb;
1634
- this.cpu_count = options.cpu_count;
1635
- this.metadata = options.metadata;
1636
- this.env = options.env;
1637
- if (!this.local && !apiKey) {
1638
- throw new AuthenticationError("API key is required for cloud mode");
1804
+ // src/client/TapesManager.ts
1805
+ var TapesManager = class {
1806
+ constructor(httpClient, local = false) {
1807
+ this.httpClient = httpClient;
1808
+ this.local = local;
1809
+ }
1810
+ ensureCloud(operation) {
1811
+ if (this.local) {
1812
+ throw new UnsupportedOperationError(
1813
+ `${operation} is not supported in local mode.`
1814
+ );
1639
1815
  }
1640
- const config = {
1641
- baseURL: this.local ? options.localURL || "http://coderunner.local:8222" : options.baseURL || "https://api.instavm.io",
1642
- timeout: this.timeout,
1643
- maxRetries: options.maxRetries || 3,
1644
- retryDelay: options.retryDelay || 1e3,
1645
- apiKey: apiKey || ""
1816
+ }
1817
+ authHeaders() {
1818
+ return { "X-API-Key": this.httpClient.apiKey };
1819
+ }
1820
+ async start(sessionId, options = {}) {
1821
+ this.ensureCloud("Tape start");
1822
+ const safe = encodePathSegment(sessionId);
1823
+ return this.httpClient.post(
1824
+ `/v1/sessions/${safe}/tape/start`,
1825
+ {
1826
+ record_egress_content: options.recordEgressContent ?? false,
1827
+ record_fs: options.recordFs ?? true,
1828
+ retention_days: options.retentionDays ?? 7
1829
+ },
1830
+ this.authHeaders()
1831
+ );
1832
+ }
1833
+ async stop(tapeId) {
1834
+ this.ensureCloud("Tape stop");
1835
+ const safe = encodePathSegment(tapeId);
1836
+ return this.httpClient.post(
1837
+ `/v1/tapes/${safe}/stop`,
1838
+ void 0,
1839
+ this.authHeaders()
1840
+ );
1841
+ }
1842
+ async list(query = {}) {
1843
+ this.ensureCloud("Tape listing");
1844
+ const params = {
1845
+ limit: query.limit ?? 50,
1846
+ offset: query.offset ?? 0
1646
1847
  };
1647
- this.httpClient = new HTTPClient(config);
1648
- this.browser = new BrowserManager(this.httpClient, this.local);
1649
- this.vms = new VMsManager(this.httpClient, this.local);
1650
- this.snapshots = new SnapshotsManager(this.httpClient, this.local);
1651
- this.shares = new SharesManager(this.httpClient, this.local, () => this._sessionId);
1652
- this.customDomains = new CustomDomainsManager(this.httpClient, this.local);
1653
- this.computerUse = new ComputerUseManager(this.httpClient, this.local);
1654
- this.apiKeys = new APIKeysManager(this.httpClient, this.local);
1655
- this.audit = new AuditManager(this.httpClient, this.local);
1656
- this.webhooks = new WebhooksManager(this.httpClient, this.local);
1657
- this.volumes = new VolumesManager(this.httpClient, this.local);
1848
+ if (query.sessionId !== void 0) params.session_id = query.sessionId;
1849
+ if (query.vmId !== void 0) params.vm_id = query.vmId;
1850
+ if (query.status !== void 0) params.status = query.status;
1851
+ const result = await this.httpClient.get(
1852
+ "/v1/tapes",
1853
+ params,
1854
+ this.authHeaders()
1855
+ );
1856
+ return Array.isArray(result) ? result : [];
1658
1857
  }
1659
- /**
1660
- * Ensure operation is not called in local mode
1661
- */
1662
- ensureNotLocal(operationName) {
1858
+ async get(tapeId) {
1859
+ this.ensureCloud("Tape lookup");
1860
+ const safe = encodePathSegment(tapeId);
1861
+ return this.httpClient.get(
1862
+ `/v1/tapes/${safe}`,
1863
+ void 0,
1864
+ this.authHeaders()
1865
+ );
1866
+ }
1867
+ async events(tapeId, query = {}) {
1868
+ this.ensureCloud("Tape events");
1869
+ const safe = encodePathSegment(tapeId);
1870
+ const params = { limit: query.limit ?? 200 };
1871
+ if (query.afterStep !== void 0) params.after_step = query.afterStep;
1872
+ if (query.kind !== void 0) params.kind = query.kind;
1873
+ const result = await this.httpClient.get(
1874
+ `/v1/tapes/${safe}/events`,
1875
+ params,
1876
+ this.authHeaders()
1877
+ );
1878
+ return Array.isArray(result) ? result : [];
1879
+ }
1880
+ async appendEvent(tapeId, kind, options = {}) {
1881
+ this.ensureCloud("Tape append event");
1882
+ const safe = encodePathSegment(tapeId);
1883
+ const body = { kind, payload: options.payload || {} };
1884
+ if (options.stepId !== void 0) body.step_id = options.stepId;
1885
+ if (options.parentStepId !== void 0) body.parent_step_id = options.parentStepId;
1886
+ if (options.checkpointId !== void 0) body.checkpoint_id = options.checkpointId;
1887
+ return this.httpClient.post(
1888
+ `/v1/tapes/${safe}/events`,
1889
+ body,
1890
+ this.authHeaders()
1891
+ );
1892
+ }
1893
+ async lanes(tapeId) {
1894
+ this.ensureCloud("Tape lanes");
1895
+ const safe = encodePathSegment(tapeId);
1896
+ return this.httpClient.get(
1897
+ `/v1/tapes/${safe}/lanes`,
1898
+ void 0,
1899
+ this.authHeaders()
1900
+ );
1901
+ }
1902
+ async diff(tapeId, fromStep, toStep) {
1903
+ this.ensureCloud("Tape diff");
1904
+ const safe = encodePathSegment(tapeId);
1905
+ return this.httpClient.get(
1906
+ `/v1/tapes/${safe}/diff`,
1907
+ { from: fromStep, to: toStep },
1908
+ this.authHeaders()
1909
+ );
1910
+ }
1911
+ async branch(tapeId, atStep, options = {}) {
1912
+ this.ensureCloud("Tape branch");
1913
+ const safe = encodePathSegment(tapeId);
1914
+ return this.httpClient.post(
1915
+ `/v1/tapes/${safe}/branch`,
1916
+ { at_step: atStep, mode: options.mode || "live" },
1917
+ this.authHeaders()
1918
+ );
1919
+ }
1920
+ async delete(tapeId) {
1921
+ this.ensureCloud("Tape deletion");
1922
+ const safe = encodePathSegment(tapeId);
1923
+ return this.httpClient.delete(
1924
+ `/v1/tapes/${safe}`,
1925
+ this.authHeaders()
1926
+ );
1927
+ }
1928
+ async export(tapeId) {
1929
+ this.ensureCloud("Tape export");
1930
+ const safe = encodePathSegment(tapeId);
1931
+ return this.httpClient.get(
1932
+ `/v1/tapes/${safe}/export`,
1933
+ void 0,
1934
+ this.authHeaders()
1935
+ );
1936
+ }
1937
+ };
1938
+
1939
+ // src/client/VaultsManager.ts
1940
+ var VaultsManager = class {
1941
+ constructor(httpClient, local = false) {
1942
+ this.httpClient = httpClient;
1943
+ this.local = local;
1944
+ }
1945
+ ensureCloud() {
1663
1946
  if (this.local) {
1664
1947
  throw new UnsupportedOperationError(
1665
- `${operationName} is not supported in local mode. This operation is only available when using the cloud API.`
1948
+ "Vault management is not supported in local mode."
1666
1949
  );
1667
1950
  }
1668
1951
  }
1669
- /**
1670
- * Execute code synchronously
1671
- *
1672
- * @param command - Command to execute
1673
- * @param options - Execution options
1674
- * @param options.timeout - Request timeout in milliseconds (used for HTTP request timeout and sent to API in seconds)
1675
- * @returns Execution result
1676
- */
1677
- async execute(command, options = {}) {
1678
- let sessionId = options.sessionId || this._sessionId;
1952
+ auth() {
1953
+ return { "X-API-Key": this.httpClient.apiKey };
1954
+ }
1955
+ async list() {
1956
+ this.ensureCloud();
1957
+ const result = await this.httpClient.get("/v1/vaults", void 0, this.auth());
1958
+ if (Array.isArray(result)) return result;
1959
+ return result?.vaults || [];
1960
+ }
1961
+ async create(name, description) {
1962
+ this.ensureCloud();
1963
+ const body = { name };
1964
+ if (description !== void 0) body.description = description;
1965
+ return this.httpClient.post("/v1/vaults", body, this.auth());
1966
+ }
1967
+ async get(vaultId) {
1968
+ this.ensureCloud();
1969
+ return this.httpClient.get(
1970
+ `/v1/vaults/${encodePathSegment(vaultId)}`,
1971
+ void 0,
1972
+ this.auth()
1973
+ );
1974
+ }
1975
+ async update(vaultId, updates) {
1976
+ this.ensureCloud();
1977
+ const body = {};
1978
+ if (updates.name !== void 0) body.name = updates.name;
1979
+ if (updates.description !== void 0) body.description = updates.description;
1980
+ return this.httpClient.patch(
1981
+ `/v1/vaults/${encodePathSegment(vaultId)}`,
1982
+ body,
1983
+ this.auth()
1984
+ );
1985
+ }
1986
+ async delete(vaultId) {
1987
+ this.ensureCloud();
1988
+ return this.httpClient.delete(
1989
+ `/v1/vaults/${encodePathSegment(vaultId)}`,
1990
+ this.auth()
1991
+ );
1992
+ }
1993
+ async discover(vaultId) {
1994
+ this.ensureCloud();
1995
+ return this.httpClient.get(
1996
+ `/v1/vaults/${encodePathSegment(vaultId)}/discover`,
1997
+ void 0,
1998
+ this.auth()
1999
+ );
2000
+ }
2001
+ async listCredentials(vaultId) {
2002
+ this.ensureCloud();
2003
+ const result = await this.httpClient.get(
2004
+ `/v1/vaults/${encodePathSegment(vaultId)}/credentials`,
2005
+ void 0,
2006
+ this.auth()
2007
+ );
2008
+ if (Array.isArray(result)) return result;
2009
+ return result?.credentials || [];
2010
+ }
2011
+ async addCredential(vaultId, name, value, options = {}) {
2012
+ this.ensureCloud();
2013
+ const body = {
2014
+ name,
2015
+ value,
2016
+ credential_type: options.credentialType ?? "api_key"
2017
+ };
2018
+ if (options.description !== void 0) body.description = options.description;
2019
+ return this.httpClient.post(
2020
+ `/v1/vaults/${encodePathSegment(vaultId)}/credentials`,
2021
+ body,
2022
+ this.auth()
2023
+ );
2024
+ }
2025
+ async rotateCredential(vaultId, credentialId, value) {
2026
+ this.ensureCloud();
2027
+ return this.httpClient.post(
2028
+ `/v1/vaults/${encodePathSegment(vaultId)}/credentials/${encodePathSegment(credentialId)}/rotate`,
2029
+ { value },
2030
+ this.auth()
2031
+ );
2032
+ }
2033
+ async deleteCredential(vaultId, credentialId) {
2034
+ this.ensureCloud();
2035
+ return this.httpClient.delete(
2036
+ `/v1/vaults/${encodePathSegment(vaultId)}/credentials/${encodePathSegment(credentialId)}`,
2037
+ this.auth()
2038
+ );
2039
+ }
2040
+ async listServices(vaultId) {
2041
+ this.ensureCloud();
2042
+ const result = await this.httpClient.get(
2043
+ `/v1/vaults/${encodePathSegment(vaultId)}/services`,
2044
+ void 0,
2045
+ this.auth()
2046
+ );
2047
+ if (Array.isArray(result)) return result;
2048
+ return result?.services || [];
2049
+ }
2050
+ async addService(vaultId, host, authConfig, options = {}) {
2051
+ this.ensureCloud();
2052
+ const body = {
2053
+ host,
2054
+ auth_config: authConfig,
2055
+ enabled: options.enabled ?? true
2056
+ };
2057
+ if (options.description !== void 0) body.description = options.description;
2058
+ return this.httpClient.post(
2059
+ `/v1/vaults/${encodePathSegment(vaultId)}/services`,
2060
+ body,
2061
+ this.auth()
2062
+ );
2063
+ }
2064
+ async addServicesFromTemplates(vaultId, templateIds) {
2065
+ this.ensureCloud();
2066
+ return this.httpClient.post(
2067
+ `/v1/vaults/${encodePathSegment(vaultId)}/services:from_template`,
2068
+ { template_ids: templateIds },
2069
+ this.auth()
2070
+ );
2071
+ }
2072
+ async deleteService(vaultId, serviceId) {
2073
+ this.ensureCloud();
2074
+ return this.httpClient.delete(
2075
+ `/v1/vaults/${encodePathSegment(vaultId)}/services/${encodePathSegment(serviceId)}`,
2076
+ this.auth()
2077
+ );
2078
+ }
2079
+ async getCatalog() {
2080
+ this.ensureCloud();
2081
+ const result = await this.httpClient.get(
2082
+ "/v1/vaults/catalog",
2083
+ void 0,
2084
+ this.auth()
2085
+ );
2086
+ if (Array.isArray(result)) return result;
2087
+ return result?.templates || [];
2088
+ }
2089
+ async getRequestLogs(vaultId, limit = 100) {
2090
+ this.ensureCloud();
2091
+ const result = await this.httpClient.get(
2092
+ `/v1/vaults/${encodePathSegment(vaultId)}/request-logs`,
2093
+ { limit },
2094
+ this.auth()
2095
+ );
2096
+ if (Array.isArray(result)) return result;
2097
+ return result?.logs || result?.request_logs || [];
2098
+ }
2099
+ };
2100
+
2101
+ // src/client/PTYManager.ts
2102
+ var PTYManager = class {
2103
+ constructor(httpClient, local, getBaseURL) {
2104
+ this.httpClient = httpClient;
2105
+ this.local = local;
2106
+ this.getBaseURL = getBaseURL;
2107
+ }
2108
+ ensureCloud() {
2109
+ if (this.local) {
2110
+ throw new UnsupportedOperationError(
2111
+ "PTY management is not supported in local mode."
2112
+ );
2113
+ }
2114
+ }
2115
+ auth() {
2116
+ return { "X-API-Key": this.httpClient.apiKey };
2117
+ }
2118
+ buildPayload(options) {
2119
+ const payload = {
2120
+ cols: options.cols ?? 80,
2121
+ rows: options.rows ?? 24
2122
+ };
2123
+ if (options.command !== void 0) payload.shell = options.command;
2124
+ if (options.ptyId !== void 0) payload.id = options.ptyId;
2125
+ return payload;
2126
+ }
2127
+ buildWsUrl(path2) {
2128
+ const trimmedBase = this.getBaseURL().replace(/\/+$/, "");
2129
+ const base = trimmedBase.replace(/^http:\/\//, "ws://").replace(/^https:\/\//, "wss://");
2130
+ const apiKey = encodeURIComponent(this.httpClient.apiKey || "");
2131
+ return `${base}${path2}?api_key=${apiKey}`;
2132
+ }
2133
+ // -- Session-scoped --------------------------------------------------------
2134
+ async create(sessionId, options = {}) {
2135
+ this.ensureCloud();
2136
+ return this.httpClient.post(
2137
+ `/v1/sessions/${encodePathSegment(sessionId)}/pty/sessions`,
2138
+ this.buildPayload(options),
2139
+ this.auth()
2140
+ );
2141
+ }
2142
+ async list(sessionId) {
2143
+ this.ensureCloud();
2144
+ const result = await this.httpClient.get(
2145
+ `/v1/sessions/${encodePathSegment(sessionId)}/pty/sessions`,
2146
+ void 0,
2147
+ this.auth()
2148
+ );
2149
+ if (Array.isArray(result)) return result;
2150
+ return result?.sessions || [];
2151
+ }
2152
+ async get(sessionId, ptyId) {
2153
+ this.ensureCloud();
2154
+ return this.httpClient.get(
2155
+ `/v1/sessions/${encodePathSegment(sessionId)}/pty/sessions/${encodePathSegment(ptyId)}`,
2156
+ void 0,
2157
+ this.auth()
2158
+ );
2159
+ }
2160
+ async resize(sessionId, ptyId, cols, rows) {
2161
+ this.ensureCloud();
2162
+ return this.httpClient.post(
2163
+ `/v1/sessions/${encodePathSegment(sessionId)}/pty/sessions/${encodePathSegment(ptyId)}/resize`,
2164
+ { cols, rows },
2165
+ this.auth()
2166
+ );
2167
+ }
2168
+ async kill(sessionId, ptyId) {
2169
+ this.ensureCloud();
2170
+ return this.httpClient.delete(
2171
+ `/v1/sessions/${encodePathSegment(sessionId)}/pty/sessions/${encodePathSegment(ptyId)}`,
2172
+ this.auth()
2173
+ );
2174
+ }
2175
+ wsUrl(sessionId, ptyId) {
2176
+ return this.buildWsUrl(
2177
+ `/v1/sessions/${encodePathSegment(sessionId)}/pty/${encodePathSegment(ptyId)}/connect`
2178
+ );
2179
+ }
2180
+ // -- VM-scoped (docker exec -it pattern) -----------------------------------
2181
+ async createForVm(vmId, options = {}) {
2182
+ this.ensureCloud();
2183
+ return this.httpClient.post(
2184
+ `/v1/vms/${encodePathSegment(vmId)}/pty/sessions`,
2185
+ this.buildPayload(options),
2186
+ this.auth()
2187
+ );
2188
+ }
2189
+ async listForVm(vmId) {
2190
+ this.ensureCloud();
2191
+ const result = await this.httpClient.get(
2192
+ `/v1/vms/${encodePathSegment(vmId)}/pty/sessions`,
2193
+ void 0,
2194
+ this.auth()
2195
+ );
2196
+ if (Array.isArray(result)) return result;
2197
+ return result?.sessions || [];
2198
+ }
2199
+ async getForVm(vmId, ptyId) {
2200
+ this.ensureCloud();
2201
+ return this.httpClient.get(
2202
+ `/v1/vms/${encodePathSegment(vmId)}/pty/sessions/${encodePathSegment(ptyId)}`,
2203
+ void 0,
2204
+ this.auth()
2205
+ );
2206
+ }
2207
+ async resizeForVm(vmId, ptyId, cols, rows) {
2208
+ this.ensureCloud();
2209
+ return this.httpClient.post(
2210
+ `/v1/vms/${encodePathSegment(vmId)}/pty/sessions/${encodePathSegment(ptyId)}/resize`,
2211
+ { cols, rows },
2212
+ this.auth()
2213
+ );
2214
+ }
2215
+ async killForVm(vmId, ptyId) {
2216
+ this.ensureCloud();
2217
+ return this.httpClient.delete(
2218
+ `/v1/vms/${encodePathSegment(vmId)}/pty/sessions/${encodePathSegment(ptyId)}`,
2219
+ this.auth()
2220
+ );
2221
+ }
2222
+ wsUrlForVm(vmId, ptyId) {
2223
+ return this.buildWsUrl(
2224
+ `/v1/vms/${encodePathSegment(vmId)}/pty/${encodePathSegment(ptyId)}/connect`
2225
+ );
2226
+ }
2227
+ };
2228
+
2229
+ // src/client/RecordingsManager.ts
2230
+ import fs2 from "fs";
2231
+ import { stat, unlink } from "fs/promises";
2232
+ import { pipeline } from "stream/promises";
2233
+ import axios2 from "axios";
2234
+ var RecordingsManager = class {
2235
+ constructor(httpClient, local, getBaseURL) {
2236
+ this.httpClient = httpClient;
2237
+ this.local = local;
2238
+ this.getBaseURL = getBaseURL;
2239
+ }
2240
+ ensureCloud() {
2241
+ if (this.local) {
2242
+ throw new UnsupportedOperationError(
2243
+ "Recording management is not supported in local mode."
2244
+ );
2245
+ }
2246
+ }
2247
+ auth() {
2248
+ return { "X-API-Key": this.httpClient.apiKey };
2249
+ }
2250
+ sdkHeaders() {
2251
+ return { "User-Agent": `instavm-js-sdk/${INSTAVM_JS_SDK_VERSION}` };
2252
+ }
2253
+ async list(query = {}) {
2254
+ this.ensureCloud();
2255
+ const params = {
2256
+ limit: query.limit ?? 50,
2257
+ offset: query.offset ?? 0
2258
+ };
2259
+ if (query.sessionId !== void 0) params.session_id = query.sessionId;
2260
+ if (query.status !== void 0) params.status = query.status;
2261
+ const result = await this.httpClient.get("/v1/recordings", params, this.auth());
2262
+ return Array.isArray(result) ? result : [];
2263
+ }
2264
+ async get(recordingId) {
2265
+ this.ensureCloud();
2266
+ return this.httpClient.get(
2267
+ `/v1/recordings/${encodePathSegment(recordingId)}`,
2268
+ void 0,
2269
+ this.auth()
2270
+ );
2271
+ }
2272
+ /**
2273
+ * Resolve the presigned download URL for a recording without following the
2274
+ * 302 redirect. The returned URL is short-lived and pre-authorised.
2275
+ */
2276
+ async getDownloadUrl(recordingId) {
2277
+ this.ensureCloud();
2278
+ const url = `${this.getBaseURL().replace(/\/+$/, "")}/v1/recordings/${encodePathSegment(recordingId)}/download`;
2279
+ const response = await axios2.request({
2280
+ method: "GET",
2281
+ url,
2282
+ headers: {
2283
+ ...this.auth(),
2284
+ ...this.sdkHeaders()
2285
+ },
2286
+ maxRedirects: 0,
2287
+ validateStatus: (status) => status >= 200 && status < 400
2288
+ });
2289
+ if ([301, 302, 303, 307, 308].includes(response.status)) {
2290
+ const location = response.headers?.location || response.headers?.Location;
2291
+ if (!location) {
2292
+ throw new InstaVMError("Recording download response missing Location header");
2293
+ }
2294
+ return location;
2295
+ }
2296
+ throw new InstaVMError(
2297
+ `Server did not redirect to a presigned URL (status=${response.status})`
2298
+ );
2299
+ }
2300
+ /**
2301
+ * Stream a recording to a local file using the presigned URL. The S3 URL
2302
+ * must NOT carry our X-API-Key header — fetch it with a clean axios call.
2303
+ */
2304
+ async download(recordingId, outputPath) {
2305
+ this.ensureCloud();
2306
+ const presignedUrl = await this.getDownloadUrl(recordingId);
2307
+ const response = await axios2.request({
2308
+ method: "GET",
2309
+ url: presignedUrl,
2310
+ responseType: "stream",
2311
+ headers: this.sdkHeaders()
2312
+ });
2313
+ const writer = fs2.createWriteStream(outputPath);
2314
+ try {
2315
+ await pipeline(response.data, writer);
2316
+ } catch (error) {
2317
+ await unlink(outputPath).catch(() => void 0);
2318
+ throw error;
2319
+ }
2320
+ const fileStat = await stat(outputPath);
2321
+ return { recordingId, path: outputPath, bytes: fileStat.size };
2322
+ }
2323
+ async delete(recordingId) {
2324
+ this.ensureCloud();
2325
+ return this.httpClient.delete(
2326
+ `/v1/recordings/${encodePathSegment(recordingId)}`,
2327
+ this.auth()
2328
+ );
2329
+ }
2330
+ };
2331
+
2332
+ // src/client/CreditsManager.ts
2333
+ var CreditsManager = class {
2334
+ constructor(httpClient, local = false) {
2335
+ this.httpClient = httpClient;
2336
+ this.local = local;
2337
+ }
2338
+ ensureCloud() {
2339
+ if (this.local) {
2340
+ throw new UnsupportedOperationError(
2341
+ "Credits API is not supported in local mode."
2342
+ );
2343
+ }
2344
+ }
2345
+ auth() {
2346
+ return { "X-API-Key": this.httpClient.apiKey };
2347
+ }
2348
+ async allocation() {
2349
+ this.ensureCloud();
2350
+ return this.httpClient.get(
2351
+ "/v1/credits/allocation",
2352
+ void 0,
2353
+ this.auth()
2354
+ );
2355
+ }
2356
+ async usage(period = "current_month", options = {}) {
2357
+ this.ensureCloud();
2358
+ const params = { period, limit: options.limit ?? 100 };
2359
+ if (options.usageType !== void 0) params.usage_type = options.usageType;
2360
+ const result = await this.httpClient.get("/v1/credits/usage", params, this.auth());
2361
+ return Array.isArray(result) ? result : [];
2362
+ }
2363
+ async summary(period = "current_month") {
2364
+ this.ensureCloud();
2365
+ return this.httpClient.get(
2366
+ "/v1/credits/summary",
2367
+ { period },
2368
+ this.auth()
2369
+ );
2370
+ }
2371
+ async check(usageType, requiredCredits) {
2372
+ this.ensureCloud();
2373
+ return this.httpClient.get(
2374
+ "/v1/credits/check",
2375
+ { usage_type: usageType, required_credits: requiredCredits },
2376
+ this.auth()
2377
+ );
2378
+ }
2379
+ async rates() {
2380
+ this.ensureCloud();
2381
+ return this.httpClient.get(
2382
+ "/v1/credits/rates",
2383
+ void 0,
2384
+ this.auth()
2385
+ );
2386
+ }
2387
+ async usageTrends(period = "30d", granularity = "daily") {
2388
+ this.ensureCloud();
2389
+ return this.httpClient.get(
2390
+ "/v1/credits/usage/trends",
2391
+ { period, granularity },
2392
+ this.auth()
2393
+ );
2394
+ }
2395
+ async usageForecast() {
2396
+ this.ensureCloud();
2397
+ return this.httpClient.get(
2398
+ "/v1/credits/usage/forecasting",
2399
+ void 0,
2400
+ this.auth()
2401
+ );
2402
+ }
2403
+ };
2404
+
2405
+ // src/client/TapeContext.ts
2406
+ var TapeContext = class {
2407
+ constructor(tapes, sessionId, startOpts) {
2408
+ this.tapes = tapes;
2409
+ this.sessionId = sessionId;
2410
+ this.startOpts = startOpts;
2411
+ this.info = null;
2412
+ }
2413
+ get id() {
2414
+ return this.info?.id;
2415
+ }
2416
+ async start() {
2417
+ if (!this.sessionId) {
2418
+ throw new SessionError("Session ID not set. Please create a session first.");
2419
+ }
2420
+ this.info = await this.tapes.start(this.sessionId, this.startOpts);
2421
+ }
2422
+ async stop() {
2423
+ if (!this.info?.id) {
2424
+ return;
2425
+ }
2426
+ this.info = await this.tapes.stop(this.info.id);
2427
+ }
2428
+ async [Symbol.asyncDispose]() {
2429
+ await this.stop();
2430
+ }
2431
+ };
2432
+
2433
+ // src/client/ToolCallRecorder.ts
2434
+ var ToolCallRecorder = class {
2435
+ constructor(tapes, tapeId) {
2436
+ this.tapes = tapes;
2437
+ this.tapeId = tapeId;
2438
+ }
2439
+ async event(kind, payload, stepId) {
2440
+ return this.tapes.appendEvent(this.tapeId, kind, {
2441
+ payload: payload || {},
2442
+ stepId
2443
+ });
2444
+ }
2445
+ span(name, payload) {
2446
+ return new ToolCallSpan(this, name, payload || {});
2447
+ }
2448
+ };
2449
+ var ToolCallSpan = class {
2450
+ constructor(recorder, name, startPayload) {
2451
+ this.recorder = recorder;
2452
+ this.name = name;
2453
+ this.startPayload = startPayload;
2454
+ this.endPayload = {};
2455
+ }
2456
+ attach(payload) {
2457
+ Object.assign(this.endPayload, payload);
2458
+ }
2459
+ async enter() {
2460
+ const result = await this.recorder.event("tool_call_start", {
2461
+ name: this.name,
2462
+ ...this.startPayload
2463
+ });
2464
+ this.stepId = result.step_id;
2465
+ }
2466
+ async exit(err) {
2467
+ const payload = {
2468
+ name: this.name,
2469
+ ok: err === void 0 || err === null,
2470
+ ...this.endPayload
2471
+ };
2472
+ if (err) {
2473
+ payload.error = err instanceof Error ? err.message : String(err);
2474
+ }
2475
+ await this.recorder.event("tool_call_end", payload, this.stepId);
2476
+ }
2477
+ };
2478
+
2479
+ // src/client/InstaVM.ts
2480
+ import FormData2 from "form-data";
2481
+ var InstaVM = class {
2482
+ constructor(apiKey, options = {}) {
2483
+ this._sessionId = null;
2484
+ this._vmUsed = false;
2485
+ this.local = options.local || false;
2486
+ this.timeout = options.timeout || 3e5;
2487
+ this.memory_mb = options.memory_mb;
2488
+ this.cpu_count = options.cpu_count;
2489
+ this.metadata = options.metadata;
2490
+ this.env = options.env;
2491
+ this._autoStartSession = options.auto_start_session !== false;
2492
+ if (!this.local && !apiKey) {
2493
+ throw new AuthenticationError("API key is required for cloud mode");
2494
+ }
2495
+ const config = {
2496
+ baseURL: this.local ? options.localURL || "http://coderunner.local:8222" : options.baseURL || "https://api.instavm.io",
2497
+ timeout: this.timeout,
2498
+ maxRetries: options.maxRetries || 3,
2499
+ retryDelay: options.retryDelay || 1e3,
2500
+ apiKey: apiKey || ""
2501
+ };
2502
+ this.httpClient = new HTTPClient(config);
2503
+ this.browser = new BrowserManager(this.httpClient, this.local, () => this);
2504
+ this.vms = new VMsManager(this.httpClient, this.local);
2505
+ this.snapshots = new SnapshotsManager(this.httpClient, this.local);
2506
+ this.shares = new SharesManager(this.httpClient, this.local, () => this._sessionId);
2507
+ this.customDomains = new CustomDomainsManager(this.httpClient, this.local);
2508
+ this.computerUse = new ComputerUseManager(this.httpClient, this.local);
2509
+ this.apiKeys = new APIKeysManager(this.httpClient, this.local);
2510
+ this.audit = new AuditManager(this.httpClient, this.local);
2511
+ this.webhooks = new WebhooksManager(this.httpClient, this.local);
2512
+ this.volumes = new VolumesManager(this.httpClient, this.local);
2513
+ this.tapes = new TapesManager(this.httpClient, this.local);
2514
+ this.vaults = new VaultsManager(this.httpClient, this.local);
2515
+ this.pty = new PTYManager(this.httpClient, this.local, () => this.httpClient.baseURL);
2516
+ this.recordings = new RecordingsManager(this.httpClient, this.local, () => this.httpClient.baseURL);
2517
+ this.credits = new CreditsManager(this.httpClient, this.local);
2518
+ if (this._autoStartSession && !this.local && apiKey) {
2519
+ this.sessionBootstrap = this.createSession();
2520
+ void this.sessionBootstrap.catch(() => void 0);
2521
+ }
2522
+ }
2523
+ async awaitSessionBootstrap() {
2524
+ if (this.sessionBootstrap) {
2525
+ try {
2526
+ await this.sessionBootstrap;
2527
+ } finally {
2528
+ this.sessionBootstrap = void 0;
2529
+ }
2530
+ }
2531
+ }
2532
+ /**
2533
+ * Ensure operation is not called in local mode
2534
+ */
2535
+ ensureNotLocal(operationName) {
2536
+ if (this.local) {
2537
+ throw new UnsupportedOperationError(
2538
+ `${operationName} is not supported in local mode. This operation is only available when using the cloud API.`
2539
+ );
2540
+ }
2541
+ }
2542
+ authHeaders() {
2543
+ return { "X-API-Key": this.httpClient.apiKey };
2544
+ }
2545
+ /**
2546
+ * Execute code synchronously
2547
+ *
2548
+ * @param command - Command to execute
2549
+ * @param options - Execution options
2550
+ * @param options.timeout - Request timeout in milliseconds (used for HTTP request timeout and sent to API in seconds)
2551
+ * @returns Execution result
2552
+ */
2553
+ async execute(command, options = {}) {
2554
+ if (!this.local) {
2555
+ await this.awaitSessionBootstrap();
2556
+ }
2557
+ let sessionId = options.sessionId || this._sessionId;
1679
2558
  if (!this.local && !sessionId) {
1680
2559
  sessionId = await this.createSession();
1681
2560
  }
@@ -1728,6 +2607,7 @@ var InstaVM = class {
1728
2607
  */
1729
2608
  async executeAsync(command, options = {}) {
1730
2609
  this.ensureNotLocal("Async execution");
2610
+ await this.awaitSessionBootstrap();
1731
2611
  let sessionId = options.sessionId || this._sessionId;
1732
2612
  if (!sessionId) {
1733
2613
  sessionId = await this.createSession();
@@ -1814,6 +2694,7 @@ var InstaVM = class {
1814
2694
  */
1815
2695
  async upload(files, options = {}) {
1816
2696
  this.ensureNotLocal("File upload");
2697
+ await this.awaitSessionBootstrap();
1817
2698
  const sessionId = options.sessionId || this._sessionId;
1818
2699
  if (!sessionId) {
1819
2700
  throw new SessionError("Session ID not set. Please create a session first using createSession().");
@@ -1880,7 +2761,7 @@ var InstaVM = class {
1880
2761
  "/v1/sessions/session",
1881
2762
  requestBody
1882
2763
  );
1883
- if (response.session_id) {
2764
+ if (response && response.session_id) {
1884
2765
  this._sessionId = response.session_id;
1885
2766
  return response.session_id;
1886
2767
  }
@@ -1946,10 +2827,11 @@ var InstaVM = class {
1946
2827
  if (port !== void 0) {
1947
2828
  params.port = port;
1948
2829
  }
2830
+ const safeSid = encodePathSegment(targetSessionId);
1949
2831
  return this.httpClient.get(
1950
- `/v1/sessions/app-url/${targetSessionId}`,
2832
+ `/v1/sessions/app-url/${safeSid}`,
1951
2833
  params,
1952
- { "X-API-Key": this.httpClient.apiKey }
2834
+ this.authHeaders()
1953
2835
  );
1954
2836
  }
1955
2837
  /**
@@ -1983,10 +2865,11 @@ var InstaVM = class {
1983
2865
  throw new SessionError("No active session to get usage for");
1984
2866
  }
1985
2867
  try {
2868
+ const safeSid = encodePathSegment(targetSessionId);
1986
2869
  const response = await this.httpClient.get(
1987
- `/v1/sessions/usage/${targetSessionId}`,
2870
+ `/v1/sessions/usage/${safeSid}`,
1988
2871
  void 0,
1989
- { "X-API-Key": this.httpClient.apiKey }
2872
+ this.authHeaders()
1990
2873
  );
1991
2874
  return {
1992
2875
  sessionsUsed: response.sessions_used || 0,
@@ -2022,12 +2905,429 @@ var InstaVM = class {
2022
2905
  if (!targetSessionId) {
2023
2906
  throw new SessionError("Session ID not set. Please create a session first.");
2024
2907
  }
2908
+ const safeSid = encodePathSegment(targetSessionId);
2025
2909
  return this.httpClient.get(
2026
- `/v1/sessions/status/${targetSessionId}`,
2910
+ `/v1/sessions/status/${safeSid}`,
2027
2911
  void 0,
2028
- { "X-API-Key": this.httpClient.apiKey }
2912
+ this.authHeaders()
2029
2913
  );
2030
2914
  }
2915
+ /**
2916
+ * Get session record details from ``GET /v1/sessions/session/{session_id}`` (Python: ``get_session_info``).
2917
+ */
2918
+ async getSessionInfo(sessionId) {
2919
+ this.ensureNotLocal("Session management");
2920
+ const sid = sessionId || this._sessionId;
2921
+ if (!sid) {
2922
+ throw new SessionError("Session ID not set. Please create a session first.");
2923
+ }
2924
+ try {
2925
+ const safeSid = encodePathSegment(sid);
2926
+ const result = await this.httpClient.get(
2927
+ `/v1/sessions/session/${safeSid}`,
2928
+ void 0,
2929
+ this.authHeaders()
2930
+ );
2931
+ if (typeof result !== "object" || result === null) {
2932
+ throw new SessionError("Failed to get session info: non-JSON response");
2933
+ }
2934
+ return result;
2935
+ } catch (error) {
2936
+ if (error instanceof InstaVMError) {
2937
+ throw error;
2938
+ }
2939
+ const errorMessage = error instanceof Error ? error.message : String(error);
2940
+ throw new SessionError(`Failed to get session info: ${errorMessage}`, { cause: error });
2941
+ }
2942
+ }
2943
+ /**
2944
+ * Day-wise usage breakdown from ``GET /v1/users/me/usage-breakdown`` (Python: ``get_usage_breakdown``).
2945
+ */
2946
+ async getUsageBreakdown(options = {}) {
2947
+ this.ensureNotLocal("Usage breakdown lookup");
2948
+ const params = { days: options.days ?? 30 };
2949
+ if (options.startDate !== void 0) {
2950
+ params.start_date = options.startDate;
2951
+ }
2952
+ if (options.endDate !== void 0) {
2953
+ params.end_date = options.endDate;
2954
+ }
2955
+ try {
2956
+ const result = await this.httpClient.get(
2957
+ "/v1/users/me/usage-breakdown",
2958
+ params,
2959
+ this.authHeaders()
2960
+ );
2961
+ if (typeof result !== "object" || result === null) {
2962
+ throw new InstaVMError("Failed to fetch usage breakdown: non-JSON response");
2963
+ }
2964
+ return result;
2965
+ } catch (error) {
2966
+ if (error instanceof InstaVMError) {
2967
+ throw error;
2968
+ }
2969
+ const errorMessage = error instanceof Error ? error.message : String(error);
2970
+ throw new InstaVMError(`Failed to fetch usage breakdown: ${errorMessage}`, { cause: error });
2971
+ }
2972
+ }
2973
+ /**
2974
+ * Time-travel tape context manager (Python: ``tape()`` / ``TapeContext``).
2975
+ */
2976
+ tape(sessionId, options = {}) {
2977
+ const sid = sessionId ?? this._sessionId ?? void 0;
2978
+ return new TapeContext(this.tapes, sid, options);
2979
+ }
2980
+ /** Build a tool-call recorder for an active tape id (Python: ``ToolCallRecorder``). */
2981
+ toolCallRecorder(tapeId) {
2982
+ return new ToolCallRecorder(this.tapes, tapeId);
2983
+ }
2984
+ /**
2985
+ * @deprecated Streaming is not supported by the API; yields a single chunk from ``execute``.
2986
+ */
2987
+ async *executeStreaming(command, onOutput) {
2988
+ console.warn(
2989
+ "executeStreaming is deprecated. The API does not support streaming execution. Use execute() or executeAsync() instead."
2990
+ );
2991
+ const result = await this.execute(command);
2992
+ const output = result.stdout || "";
2993
+ if (onOutput) {
2994
+ onOutput(output);
2995
+ }
2996
+ yield output;
2997
+ }
2998
+ // --- Vault (flat API parity with Python InstaVM) ---
2999
+ async listVaults() {
3000
+ return this.vaults.list();
3001
+ }
3002
+ async createVault(name, description) {
3003
+ return this.vaults.create(name, description);
3004
+ }
3005
+ async getVault(vaultId) {
3006
+ return this.vaults.get(vaultId);
3007
+ }
3008
+ async updateVault(vaultId, updates) {
3009
+ return this.vaults.update(vaultId, updates);
3010
+ }
3011
+ async deleteVault(vaultId) {
3012
+ return this.vaults.delete(vaultId);
3013
+ }
3014
+ async discoverVault(vaultId) {
3015
+ return this.vaults.discover(vaultId);
3016
+ }
3017
+ async listVaultCredentials(vaultId) {
3018
+ return this.vaults.listCredentials(vaultId);
3019
+ }
3020
+ async addVaultCredential(vaultId, name, value, options = {}) {
3021
+ return this.vaults.addCredential(vaultId, name, value, options);
3022
+ }
3023
+ async rotateVaultCredential(vaultId, credentialId, value) {
3024
+ return this.vaults.rotateCredential(vaultId, credentialId, value);
3025
+ }
3026
+ async deleteVaultCredential(vaultId, credentialId) {
3027
+ return this.vaults.deleteCredential(vaultId, credentialId);
3028
+ }
3029
+ async listVaultServices(vaultId) {
3030
+ return this.vaults.listServices(vaultId);
3031
+ }
3032
+ async addVaultService(vaultId, host, authConfig, options = {}) {
3033
+ return this.vaults.addService(vaultId, host, authConfig, options);
3034
+ }
3035
+ async deleteVaultService(vaultId, serviceId) {
3036
+ return this.vaults.deleteService(vaultId, serviceId);
3037
+ }
3038
+ async addVaultServicesFromTemplates(vaultId, templateIds) {
3039
+ return this.vaults.addServicesFromTemplates(vaultId, templateIds);
3040
+ }
3041
+ async getVaultCatalog() {
3042
+ return this.vaults.getCatalog();
3043
+ }
3044
+ async getVaultRequestLogs(vaultId, limit = 100) {
3045
+ return this.vaults.getRequestLogs(vaultId, limit);
3046
+ }
3047
+ // --- Browser (client-level parity with Python InstaVM) ---
3048
+ async createBrowserSession(viewportWidth = 1920, viewportHeight = 1080, userAgent) {
3049
+ this.ensureNotLocal("Browser session management");
3050
+ const body = {
3051
+ viewport_width: viewportWidth,
3052
+ viewport_height: viewportHeight
3053
+ };
3054
+ if (userAgent) {
3055
+ body.user_agent = userAgent;
3056
+ }
3057
+ try {
3058
+ const result = await this.httpClient.post(
3059
+ "/v1/browser/sessions/",
3060
+ body,
3061
+ this.authHeaders()
3062
+ );
3063
+ const sid = result.session_id;
3064
+ if (!sid) {
3065
+ throw new BrowserSessionError("No session ID returned from server");
3066
+ }
3067
+ return sid;
3068
+ } catch (error) {
3069
+ const msg = error instanceof Error ? error.message : String(error);
3070
+ throw new BrowserSessionError(`Failed to create browser session: ${msg}`, { cause: error });
3071
+ }
3072
+ }
3073
+ async getBrowserSession(sessionId) {
3074
+ this.ensureNotLocal("Browser session management");
3075
+ const safe = encodePathSegment(sessionId);
3076
+ try {
3077
+ return await this.httpClient.get(
3078
+ `/v1/browser/sessions/${safe}`,
3079
+ void 0,
3080
+ this.authHeaders()
3081
+ );
3082
+ } catch (error) {
3083
+ const status = error.statusCode;
3084
+ if (status === 404) {
3085
+ throw new BrowserSessionError("Browser session not found or expired", { cause: error });
3086
+ }
3087
+ const msg = error instanceof Error ? error.message : String(error);
3088
+ throw new BrowserSessionError(`Failed to get browser session: ${msg}`, { cause: error });
3089
+ }
3090
+ }
3091
+ async closeBrowserSession(sessionId) {
3092
+ this.ensureNotLocal("Browser session management");
3093
+ const safe = encodePathSegment(sessionId);
3094
+ try {
3095
+ await this.httpClient.delete(`/v1/browser/sessions/${safe}`, this.authHeaders());
3096
+ return true;
3097
+ } catch (error) {
3098
+ const status = error.statusCode;
3099
+ if (status === 404) {
3100
+ return true;
3101
+ }
3102
+ const msg = error instanceof Error ? error.message : String(error);
3103
+ throw new BrowserSessionError(`Failed to close browser session: ${msg}`, { cause: error });
3104
+ }
3105
+ }
3106
+ async listBrowserSessions() {
3107
+ this.ensureNotLocal("Browser session management");
3108
+ const result = await this.httpClient.get("/v1/browser/sessions/", void 0, this.authHeaders());
3109
+ if (Array.isArray(result)) {
3110
+ return result;
3111
+ }
3112
+ return result?.sessions || [];
3113
+ }
3114
+ async browserNavigate(url, sessionId, waitTimeout = 3e4) {
3115
+ if (!this.local) {
3116
+ if (!sessionId) {
3117
+ throw new BrowserSessionError("session_id is required in cloud mode");
3118
+ }
3119
+ }
3120
+ const data = { url, wait_timeout: waitTimeout };
3121
+ if (!this.local && sessionId) {
3122
+ data.session_id = sessionId;
3123
+ }
3124
+ try {
3125
+ return await this.httpClient.post(
3126
+ "/v1/browser/interactions/navigate",
3127
+ data,
3128
+ this.authHeaders()
3129
+ );
3130
+ } catch (error) {
3131
+ const status = error.statusCode;
3132
+ if (status === 404) {
3133
+ throw new BrowserSessionError("Browser session not found or expired", { cause: error });
3134
+ }
3135
+ if (status === 408) {
3136
+ throw new BrowserTimeoutError("Navigation timeout", { cause: error });
3137
+ }
3138
+ const msg = error instanceof Error ? error.message : String(error);
3139
+ throw new BrowserNavigationError(`Failed to navigate: ${msg}`, { cause: error });
3140
+ }
3141
+ }
3142
+ async browserClick(selector, sessionId, force = false, timeout = 3e4) {
3143
+ this.ensureNotLocal("Browser click interaction");
3144
+ try {
3145
+ return await this.httpClient.post(
3146
+ "/v1/browser/interactions/click",
3147
+ { selector, session_id: sessionId, force, timeout },
3148
+ this.authHeaders()
3149
+ );
3150
+ } catch (error) {
3151
+ const status = error.statusCode;
3152
+ if (status === 404) {
3153
+ throw new ElementNotFoundError(`Element not found: ${selector}`, selector, { cause: error });
3154
+ }
3155
+ if (status === 408) {
3156
+ throw new BrowserTimeoutError("Click timeout", { cause: error });
3157
+ }
3158
+ const msg = error instanceof Error ? error.message : String(error);
3159
+ throw new BrowserInteractionError(`Failed to click element: ${msg}`, { cause: error });
3160
+ }
3161
+ }
3162
+ async browserType(selector, text, sessionId, delay = 100, timeout = 3e4) {
3163
+ this.ensureNotLocal("Browser type interaction");
3164
+ try {
3165
+ return await this.httpClient.post(
3166
+ "/v1/browser/interactions/type",
3167
+ { selector, text, session_id: sessionId, delay, timeout },
3168
+ this.authHeaders()
3169
+ );
3170
+ } catch (error) {
3171
+ const status = error.statusCode;
3172
+ if (status === 404) {
3173
+ throw new ElementNotFoundError(`Element not found: ${selector}`, selector, { cause: error });
3174
+ }
3175
+ if (status === 408) {
3176
+ throw new BrowserTimeoutError("Type timeout", { cause: error });
3177
+ }
3178
+ const msg = error instanceof Error ? error.message : String(error);
3179
+ throw new BrowserInteractionError(`Failed to type text: ${msg}`, { cause: error });
3180
+ }
3181
+ }
3182
+ async browserFill(selector, value, sessionId, timeout = 3e4) {
3183
+ this.ensureNotLocal("Browser fill interaction");
3184
+ try {
3185
+ return await this.httpClient.post(
3186
+ "/v1/browser/interactions/fill",
3187
+ { selector, value, session_id: sessionId, timeout },
3188
+ this.authHeaders()
3189
+ );
3190
+ } catch (error) {
3191
+ const status = error.statusCode;
3192
+ if (status === 404) {
3193
+ throw new ElementNotFoundError(`Element not found: ${selector}`, selector, { cause: error });
3194
+ }
3195
+ if (status === 408) {
3196
+ throw new BrowserTimeoutError("Fill timeout", { cause: error });
3197
+ }
3198
+ const msg = error instanceof Error ? error.message : String(error);
3199
+ throw new BrowserInteractionError(`Failed to fill form: ${msg}`, { cause: error });
3200
+ }
3201
+ }
3202
+ async browserScroll(sessionId, selector, x, y) {
3203
+ this.ensureNotLocal("Browser scroll interaction");
3204
+ if (selector == null && x == null && y == null) {
3205
+ throw new BrowserInteractionError(
3206
+ "At least one of 'selector', 'x', or 'y' must be provided for scrolling."
3207
+ );
3208
+ }
3209
+ const data = { session_id: sessionId };
3210
+ if (selector) {
3211
+ data.selector = selector;
3212
+ }
3213
+ if (x != null) {
3214
+ data.x = x;
3215
+ }
3216
+ if (y != null) {
3217
+ data.y = y;
3218
+ }
3219
+ try {
3220
+ return await this.httpClient.post(
3221
+ "/v1/browser/interactions/scroll",
3222
+ data,
3223
+ this.authHeaders()
3224
+ );
3225
+ } catch (error) {
3226
+ const msg = error instanceof Error ? error.message : String(error);
3227
+ throw new BrowserInteractionError(`Failed to scroll: ${msg}`, { cause: error });
3228
+ }
3229
+ }
3230
+ async browserWait(condition, sessionId, selector, timeout = 3e4) {
3231
+ this.ensureNotLocal("Browser wait interaction");
3232
+ const data = { condition, session_id: sessionId, timeout };
3233
+ if (selector) {
3234
+ data.selector = selector;
3235
+ }
3236
+ try {
3237
+ return await this.httpClient.post(
3238
+ "/v1/browser/interactions/wait",
3239
+ data,
3240
+ this.authHeaders()
3241
+ );
3242
+ } catch (error) {
3243
+ const status = error.statusCode;
3244
+ if (status === 408) {
3245
+ throw new BrowserTimeoutError(`Wait timeout for condition: ${condition}`, { cause: error });
3246
+ }
3247
+ const msg = error instanceof Error ? error.message : String(error);
3248
+ throw new BrowserInteractionError(`Failed to wait for condition: ${msg}`, { cause: error });
3249
+ }
3250
+ }
3251
+ async browserScreenshot(sessionId, fullPage = true, clip, format = "png", quality) {
3252
+ this.ensureNotLocal("Browser screenshot");
3253
+ const data = {
3254
+ session_id: sessionId,
3255
+ full_page: fullPage,
3256
+ format
3257
+ };
3258
+ if (clip) {
3259
+ data.clip = clip;
3260
+ }
3261
+ if (quality != null) {
3262
+ data.quality = quality;
3263
+ }
3264
+ try {
3265
+ const result = await this.httpClient.post(
3266
+ "/v1/browser/interactions/screenshot",
3267
+ data,
3268
+ this.authHeaders()
3269
+ );
3270
+ return result.screenshot || "";
3271
+ } catch (error) {
3272
+ const msg = error instanceof Error ? error.message : String(error);
3273
+ throw new BrowserInteractionError(`Failed to take screenshot: ${msg}`, { cause: error });
3274
+ }
3275
+ }
3276
+ async browserExtractElements(sessionId, selector, attributes) {
3277
+ this.ensureNotLocal("Browser element extraction");
3278
+ const data = { session_id: sessionId };
3279
+ if (selector) {
3280
+ data.selector = selector;
3281
+ }
3282
+ if (attributes?.length) {
3283
+ data.attributes = attributes;
3284
+ }
3285
+ try {
3286
+ const result = await this.httpClient.post(
3287
+ "/v1/browser/interactions/extract",
3288
+ data,
3289
+ this.authHeaders()
3290
+ );
3291
+ return result.elements || [];
3292
+ } catch (error) {
3293
+ const msg = error instanceof Error ? error.message : String(error);
3294
+ throw new BrowserInteractionError(`Failed to extract elements: ${msg}`, { cause: error });
3295
+ }
3296
+ }
3297
+ async browserExtractContent(sessionId, url, includeInteractive = true, includeAnchors = true, maxAnchors = 50) {
3298
+ if (!this.local) {
3299
+ if (!sessionId) {
3300
+ throw new BrowserSessionError("session_id is required in cloud mode");
3301
+ }
3302
+ } else if (!url) {
3303
+ throw new BrowserInteractionError("url is required in local mode");
3304
+ }
3305
+ const data = {
3306
+ include_interactive: includeInteractive,
3307
+ include_anchors: includeAnchors,
3308
+ max_anchors: maxAnchors
3309
+ };
3310
+ if (!this.local && sessionId) {
3311
+ data.session_id = sessionId;
3312
+ }
3313
+ if (this.local && url) {
3314
+ data.url = url;
3315
+ }
3316
+ try {
3317
+ return await this.httpClient.post(
3318
+ "/v1/browser/interactions/content",
3319
+ data,
3320
+ this.authHeaders()
3321
+ );
3322
+ } catch (error) {
3323
+ const status = error.statusCode;
3324
+ if (status === 404) {
3325
+ throw new BrowserSessionError("Browser session not found or expired", { cause: error });
3326
+ }
3327
+ const msg = error instanceof Error ? error.message : String(error);
3328
+ throw new BrowserInteractionError(`Failed to extract content: ${msg}`, { cause: error });
3329
+ }
3330
+ }
2031
3331
  /**
2032
3332
  * Download a file from the remote VM
2033
3333
  */
@@ -2113,8 +3413,9 @@ var InstaVM = class {
2113
3413
  if (!targetSessionId) {
2114
3414
  throw new SessionError("Session ID not set. Please create a session first.");
2115
3415
  }
3416
+ const safeSid = encodePathSegment(targetSessionId);
2116
3417
  const response = await this.httpClient.post(
2117
- `/v1/egress/session/${targetSessionId}`,
3418
+ `/v1/egress/session/${safeSid}`,
2118
3419
  {
2119
3420
  allow_public_repos: options.allowPackageManagers ?? true,
2120
3421
  allow_http: options.allowHttp ?? true,
@@ -2135,8 +3436,9 @@ var InstaVM = class {
2135
3436
  if (!targetSessionId) {
2136
3437
  throw new SessionError("Session ID not set. Please create a session first.");
2137
3438
  }
3439
+ const safeSid = encodePathSegment(targetSessionId);
2138
3440
  const response = await this.httpClient.get(
2139
- `/v1/egress/session/${targetSessionId}`,
3441
+ `/v1/egress/session/${safeSid}`,
2140
3442
  void 0,
2141
3443
  { "X-API-Key": this.httpClient.apiKey }
2142
3444
  );
@@ -2153,8 +3455,9 @@ var InstaVM = class {
2153
3455
  */
2154
3456
  async setVmEgress(vmId, options = {}) {
2155
3457
  this.ensureNotLocal("Egress policy management");
3458
+ const safeVmId = encodePathSegment(vmId);
2156
3459
  const response = await this.httpClient.post(
2157
- `/v1/egress/vm/${vmId}`,
3460
+ `/v1/egress/vm/${safeVmId}`,
2158
3461
  {
2159
3462
  allow_public_repos: options.allowPackageManagers ?? true,
2160
3463
  allow_http: options.allowHttp ?? true,
@@ -2171,8 +3474,9 @@ var InstaVM = class {
2171
3474
  */
2172
3475
  async getVmEgress(vmId) {
2173
3476
  this.ensureNotLocal("Egress policy management");
3477
+ const safeVmId = encodePathSegment(vmId);
2174
3478
  const response = await this.httpClient.get(
2175
- `/v1/egress/vm/${vmId}`,
3479
+ `/v1/egress/vm/${safeVmId}`,
2176
3480
  void 0,
2177
3481
  { "X-API-Key": this.httpClient.apiKey }
2178
3482
  );
@@ -2260,19 +3564,29 @@ export {
2260
3564
  BrowserSessionError,
2261
3565
  BrowserTimeoutError,
2262
3566
  ComputerUseManager,
3567
+ CreditsManager,
2263
3568
  CustomDomainsManager,
2264
3569
  ElementNotFoundError,
2265
3570
  ExecutionError,
3571
+ ForbiddenError,
3572
+ INSTAVM_JS_SDK_VERSION,
2266
3573
  InstaVM,
2267
3574
  InstaVMError,
2268
3575
  NetworkError,
3576
+ PTYManager,
2269
3577
  QuotaExceededError,
2270
3578
  RateLimitError,
3579
+ RecordingsManager,
2271
3580
  SessionError,
2272
3581
  SharesManager,
2273
3582
  SnapshotsManager,
3583
+ TapeContext,
3584
+ TapesManager,
3585
+ ToolCallRecorder,
3586
+ ToolCallSpan,
2274
3587
  UnsupportedOperationError,
2275
3588
  VMsManager,
3589
+ VaultsManager,
2276
3590
  VolumesManager,
2277
3591
  WebhooksManager
2278
3592
  };