@fastgpt-sdk/sandbox-adapter 0.0.40-beta.4 → 0.0.40-beta.6

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.
@@ -1,6 +1,6 @@
1
1
  import type { ISandbox } from '../interfaces/ISandbox';
2
2
  import { CommandPolyfillService } from '../polyfill/CommandPolyfillService';
3
- import type { ContentReplaceEntry, DirectoryEntry, Endpoint, ExecuteOptions, ExecuteResult, FileDeleteResult, FileInfo, FileReadResult, FileWriteEntry, FileWriteResult, MoveEntry, PermissionEntry, ReadFileOptions, SandboxEndpointSelector, SandboxId, SandboxInfo, SandboxMetrics, SandboxProxyService, SandboxProxyTarget, SandboxStatus, SearchResult, StreamHandlers } from '../types';
3
+ import type { ContentReplaceEntry, DirectoryEntry, Endpoint, ExecuteOptions, ExecuteResult, FileDeleteResult, FileInfo, FileReadResult, FileWriteEntry, FileWriteResult, MoveEntry, PermissionEntry, ReadFileOptions, SandboxEndpointSelector, SandboxId, SandboxInfo, SandboxMetrics, SandboxStatus, SearchResult, StreamHandlers } from '../types';
4
4
  /**
5
5
  * Abstract base class for all sandbox adapters.
6
6
  *
@@ -38,7 +38,6 @@ export declare abstract class BaseSandboxAdapter implements ISandbox {
38
38
  waitUntilDeleted(timeoutMs?: number): Promise<void>;
39
39
  renewExpiration(_additionalSeconds: number): Promise<void>;
40
40
  getEndpoint(_selector: SandboxEndpointSelector): Promise<Endpoint>;
41
- getProxyTarget(_service?: SandboxProxyService): Promise<SandboxProxyTarget>;
42
41
  abstract execute(command: string, options?: ExecuteOptions): Promise<ExecuteResult>;
43
42
  executeStream(command: string, handlers: StreamHandlers, options?: ExecuteOptions): Promise<void>;
44
43
  executeBackground(_command: string, _options?: ExecuteOptions): Promise<{
@@ -1,4 +1,4 @@
1
- import type { Endpoint, ExecuteOptions, ExecuteResult, FileWriteEntry, FileWriteResult, SandboxEndpointSelector, SandboxId, SandboxInfo, SandboxMetrics, SandboxProxyService, SandboxProxyTarget, StreamHandlers } from '@/types';
1
+ import type { Endpoint, ExecuteOptions, ExecuteResult, FileWriteEntry, FileWriteResult, SandboxEndpointSelector, SandboxId, SandboxInfo, SandboxMetrics, StreamHandlers } from '@/types';
2
2
  import { BaseSandboxAdapter } from '../BaseSandboxAdapter';
3
3
  import type { OpenSandboxConfigType } from './type';
4
4
  export type { OpenSandboxConfigType } from './type';
@@ -94,7 +94,6 @@ export declare class OpenSandboxAdapter extends BaseSandboxAdapter {
94
94
  */
95
95
  getEndpoint(selector: SandboxEndpointSelector): Promise<Endpoint>;
96
96
  private getOpenSandboxEndpoint;
97
- getProxyTarget(service?: SandboxProxyService): Promise<SandboxProxyTarget>;
98
97
  private getDirectEndpointOrigin;
99
98
  getInfo(): Promise<SandboxInfo | null>;
100
99
  renewExpiration(additionalSeconds: number): Promise<void>;
@@ -1,4 +1,10 @@
1
1
  import { type DevboxApiConfig, type DevboxApiResponse, type DevboxCreateRequest, type DevboxInfoData, type DevboxMutationData, type DownloadFileParams, type ExecRequest, type ExecResponseData, type UploadFileParams, type UploadResponseData } from './type';
2
+ export declare class DevboxApiError extends Error {
3
+ readonly status: number;
4
+ readonly rawBody: string;
5
+ readonly url: string;
6
+ constructor(message: string, status: number, rawBody: string, url: string);
7
+ }
2
8
  /**
3
9
  * HTTP client for the Sealos Devbox REST API.
4
10
  *
@@ -1,4 +1,4 @@
1
- import type { Endpoint, ExecuteOptions, ExecuteResult, ImageSpec, KubeAccessPolicy, LabelSpec, LifecyclePolicy, SandboxEndpointSelector, SandboxId, SandboxInfo, SandboxProxyService, SandboxProxyTarget } from '../../types';
1
+ import type { Endpoint, ExecuteOptions, ExecuteResult, ImageSpec, KubeAccessPolicy, LabelSpec, LifecyclePolicy, SandboxEndpointSelector, SandboxId, SandboxInfo } from '../../types';
2
2
  import { BaseSandboxAdapter } from '../BaseSandboxAdapter';
3
3
  /**
4
4
  * Configuration for Sealos Devbox Adapter.
@@ -38,6 +38,13 @@ export declare class SealosDevboxAdapter extends BaseSandboxAdapter {
38
38
  private removeUndefined;
39
39
  private assertMutationSuccess;
40
40
  private isNotFoundResponse;
41
+ private isRetryableGetInfoError;
42
+ /**
43
+ * Devbox gateway can briefly return 502/503/504 during restart before the sandbox
44
+ * status is readable. Limit retry to the initial info probe to avoid repeating
45
+ * lifecycle mutations such as create/resume/delete.
46
+ */
47
+ private getInfoWithProviderRetry;
41
48
  getInfo(): Promise<SandboxInfo | null>;
42
49
  ensureRunning(): Promise<void>;
43
50
  create(): Promise<void>;
@@ -46,8 +53,6 @@ export declare class SealosDevboxAdapter extends BaseSandboxAdapter {
46
53
  delete(sandboxId?: SandboxId): Promise<void>;
47
54
  execute(command: string, options?: ExecuteOptions): Promise<ExecuteResult>;
48
55
  getEndpoint(selector: SandboxEndpointSelector): Promise<Endpoint>;
49
- getProxyTarget(service?: SandboxProxyService): Promise<SandboxProxyTarget>;
50
- private waitForCodeServerHealthz;
51
56
  private getHttpgateTarget;
52
57
  private getGatewayUniqueID;
53
58
  private getHttpgateDomain;
package/dist/index.cjs CHANGED
@@ -45192,7 +45192,10 @@ POLYFILL_EOF`);
45192
45192
  }
45193
45193
  }
45194
45194
  async listDirectory(path) {
45195
- const result = await this.executor.execute(`ls -la "${this.escapePath(path)}" --time-style=+"%Y-%m-%dT%H:%M:%S" 2>/dev/null || echo "DIRECTORY_NOT_FOUND"`);
45195
+ let result = await this.executor.execute(`ls -la "${this.escapePath(path)}" --time-style=+"%Y-%m-%dT%H:%M:%S" 2>/dev/null || echo "DIRECTORY_NOT_FOUND"`);
45196
+ if (result.stdout.includes("DIRECTORY_NOT_FOUND") || result.exitCode !== 0) {
45197
+ result = await this.executor.execute(`ls -la "${this.escapePath(path)}" 2>/dev/null || echo "DIRECTORY_NOT_FOUND"`);
45198
+ }
45196
45199
  if (result.stdout.includes("DIRECTORY_NOT_FOUND")) {
45197
45200
  throw new FileOperationError("Directory not found", path, "FILE_NOT_FOUND");
45198
45201
  }
@@ -45323,7 +45326,7 @@ POLYFILL_EOF`);
45323
45326
  if (line.startsWith("total") || !line.trim()) {
45324
45327
  continue;
45325
45328
  }
45326
- const match = line.match(/^([-dl])([-rwxsStT]{9})\s+\d+\s+\S+\s+\S+\s+(\d+)\s+(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})\s+(.+)$/);
45329
+ let match = line.match(/^([-dl])([-rwxsStT]{9})\s+\d+\s+\S+\s+\S+\s+(\d+)\s+(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})\s+(.+)$/);
45327
45330
  if (match) {
45328
45331
  const [, type, , size, dateStr, name] = match;
45329
45332
  if (name === "." || name === "..") {
@@ -45339,6 +45342,24 @@ POLYFILL_EOF`);
45339
45342
  size: Number.parseInt(size, 10) || undefined,
45340
45343
  modifiedAt: new Date(dateStr)
45341
45344
  });
45345
+ continue;
45346
+ }
45347
+ const parts = line.trim().split(/\s+/);
45348
+ if (parts.length >= 8) {
45349
+ const permissions = parts[0];
45350
+ const type = permissions[0];
45351
+ const size = parts[4];
45352
+ const name = parts.slice(8).join(" ");
45353
+ if (name === "." || name === "..") {
45354
+ continue;
45355
+ }
45356
+ entries.push({
45357
+ name,
45358
+ path: `${basePath}/${name}`,
45359
+ isDirectory: type === "d" || type === "l",
45360
+ isFile: type === "-",
45361
+ size: Number.parseInt(size, 10) || undefined
45362
+ });
45342
45363
  }
45343
45364
  }
45344
45365
  return entries;
@@ -45412,9 +45433,6 @@ class BaseSandboxAdapter {
45412
45433
  async getEndpoint(_selector) {
45413
45434
  throw new FeatureNotSupportedError("Endpoint resolution not supported by this provider", "getEndpoint", this.provider);
45414
45435
  }
45415
- async getProxyTarget(_service = "code-server") {
45416
- throw new FeatureNotSupportedError("Proxy target resolution not supported by this provider", "getProxyTarget", this.provider);
45417
- }
45418
45436
  async executeStream(command, handlers, options) {
45419
45437
  const result = await this.execute(command, options);
45420
45438
  if (handlers.onStdout && result.stdout) {
@@ -45625,6 +45643,20 @@ class BaseSandboxAdapter {
45625
45643
  }
45626
45644
 
45627
45645
  // src/adapters/SealosDevboxAdapter/api.ts
45646
+ class DevboxApiError extends Error {
45647
+ status;
45648
+ rawBody;
45649
+ url;
45650
+ constructor(message, status, rawBody, url) {
45651
+ super(message);
45652
+ this.status = status;
45653
+ this.rawBody = rawBody;
45654
+ this.url = url;
45655
+ this.name = "DevboxApiError";
45656
+ Object.setPrototypeOf(this, DevboxApiError.prototype);
45657
+ }
45658
+ }
45659
+
45628
45660
  class DevboxApi {
45629
45661
  baseUrl;
45630
45662
  token;
@@ -45648,7 +45680,13 @@ class DevboxApi {
45648
45680
  headers.set("Content-Type", "application/json");
45649
45681
  }
45650
45682
  const res = await fetch(input, { ...init, headers });
45651
- const result = await res.json();
45683
+ const rawBody = typeof res.text === "function" ? await res.text() : JSON.stringify(await res.json());
45684
+ let result;
45685
+ try {
45686
+ result = JSON.parse(rawBody);
45687
+ } catch {
45688
+ throw new DevboxApiError(`Devbox API returned non-JSON response (${res.status}): ${rawBody || res.statusText || res.status}`, res.status, rawBody, input);
45689
+ }
45652
45690
  return {
45653
45691
  ...result,
45654
45692
  code: result.code ?? res.status
@@ -45720,11 +45758,6 @@ class DevboxApi {
45720
45758
  }
45721
45759
  }
45722
45760
 
45723
- // src/adapters/ports.ts
45724
- var OPEN_SANDBOX_EXECD_PORT = 44772;
45725
- var OPEN_SANDBOX_CODE_SERVER_PORT = 8080;
45726
- var SEALOS_DEVBOX_CODE_SERVER_PORT = 1318;
45727
-
45728
45761
  // src/utils/image.ts
45729
45762
  function formatImageSpec(image) {
45730
45763
  const parts = [image.repository];
@@ -45762,6 +45795,9 @@ function joinUrlPath(url, path) {
45762
45795
  }
45763
45796
 
45764
45797
  // src/adapters/SealosDevboxAdapter/index.ts
45798
+ var GET_INFO_RETRY_TIMEOUT_MS = 30000;
45799
+ var GET_INFO_RETRY_INTERVAL_MS = 1000;
45800
+
45765
45801
  class SealosDevboxAdapter extends BaseSandboxAdapter {
45766
45802
  config;
45767
45803
  createConfig;
@@ -45832,6 +45868,33 @@ class SealosDevboxAdapter extends BaseSandboxAdapter {
45832
45868
  isNotFoundResponse(res) {
45833
45869
  return res.code === 404 || String(res.message ?? "").toLowerCase().includes("not found");
45834
45870
  }
45871
+ isRetryableGetInfoError(error) {
45872
+ let current = error;
45873
+ while (current instanceof Error) {
45874
+ if (current instanceof DevboxApiError) {
45875
+ const rawBody = current.rawBody.toLowerCase();
45876
+ return [502, 503, 504].includes(current.status) || rawBody.includes("no healthy upstream");
45877
+ }
45878
+ current = current.cause;
45879
+ }
45880
+ return false;
45881
+ }
45882
+ async getInfoWithProviderRetry(timeoutMs = GET_INFO_RETRY_TIMEOUT_MS, intervalMs = GET_INFO_RETRY_INTERVAL_MS) {
45883
+ const startTime = Date.now();
45884
+ let lastError;
45885
+ while (Date.now() - startTime < timeoutMs) {
45886
+ try {
45887
+ return await this.getInfo();
45888
+ } catch (error) {
45889
+ lastError = error;
45890
+ if (!this.isRetryableGetInfoError(error)) {
45891
+ throw error;
45892
+ }
45893
+ await this.sleep(intervalMs);
45894
+ }
45895
+ }
45896
+ throw lastError;
45897
+ }
45835
45898
  async getInfo() {
45836
45899
  try {
45837
45900
  const res = await this.api.info(this._id);
@@ -45858,7 +45921,7 @@ class SealosDevboxAdapter extends BaseSandboxAdapter {
45858
45921
  }
45859
45922
  async ensureRunning() {
45860
45923
  try {
45861
- const sandbox = await this.getInfo();
45924
+ const sandbox = await this.getInfoWithProviderRetry();
45862
45925
  if (sandbox) {
45863
45926
  const status = sandbox.status.state;
45864
45927
  switch (status) {
@@ -45960,11 +46023,7 @@ class SealosDevboxAdapter extends BaseSandboxAdapter {
45960
46023
  }
45961
46024
  }
45962
46025
  async getEndpoint(selector) {
45963
- const port = typeof selector === "number" ? selector : SEALOS_DEVBOX_CODE_SERVER_PORT;
45964
- const target = await this.getHttpgateTarget(port);
45965
- if (selector === "code-server") {
45966
- await this.waitForCodeServerHealthz(target);
45967
- }
46026
+ const target = await this.getHttpgateTarget(selector);
45968
46027
  const url = new URL(target.origin);
45969
46028
  return {
45970
46029
  host: url.host,
@@ -45973,61 +46032,11 @@ class SealosDevboxAdapter extends BaseSandboxAdapter {
45973
46032
  url: joinUrlPath(target.origin, target.basePath)
45974
46033
  };
45975
46034
  }
45976
- async getProxyTarget(service = "code-server") {
45977
- if (service !== "code-server") {
45978
- throw new FeatureNotSupportedError(`Proxy service "${service}" is not supported by this provider`, "getProxyTarget", this.provider);
45979
- }
45980
- const target = await this.getHttpgateTarget(SEALOS_DEVBOX_CODE_SERVER_PORT);
45981
- return {
45982
- service,
45983
- origin: target.origin,
45984
- basePath: target.basePath,
45985
- auth: "code-server",
45986
- ...target.password ? { password: target.password } : {}
45987
- };
45988
- }
45989
- async waitForCodeServerHealthz(target) {
45990
- const healthUrl = joinUrlPath(joinUrlPath(target.origin, target.basePath), "/healthz");
45991
- const timeoutMs = 60000;
45992
- const intervalMs = 500;
45993
- const requestTimeoutMs = 3000;
45994
- const deadline = Date.now() + timeoutMs;
45995
- let lastResult = "not checked";
45996
- while (Date.now() < deadline) {
45997
- try {
45998
- const res = await fetch(healthUrl, {
45999
- method: "GET",
46000
- signal: AbortSignal.timeout(requestTimeoutMs)
46001
- });
46002
- if (res.status >= 200 && res.status < 400) {
46003
- return;
46004
- }
46005
- lastResult = `status ${res.status}`;
46006
- } catch (error) {
46007
- lastResult = error instanceof Error ? error.message : String(error);
46008
- }
46009
- await this.sleep(intervalMs);
46010
- }
46011
- throw new ConnectionError(`Devbox code-server health check ${healthUrl} did not become ready within ${timeoutMs}ms. Last result: ${lastResult}`, this.config.baseUrl);
46012
- }
46013
46035
  async getHttpgateTarget(port) {
46014
46036
  const res = await this.api.info(this._id);
46015
46037
  if (res.code !== 200 || !res.data) {
46016
46038
  throw new ConnectionError(`Failed to get devbox info: ${res.message}`, this.config.baseUrl);
46017
46039
  }
46018
- if (port === SEALOS_DEVBOX_CODE_SERVER_PORT) {
46019
- const gateway2 = res.data.codeServerGateway;
46020
- if (!gateway2?.url) {
46021
- throw new ConnectionError("Devbox info does not include codeServerGateway.url; cannot resolve code-server endpoint", this.config.baseUrl);
46022
- }
46023
- const codeServerUrl = new URL(gateway2.url);
46024
- return {
46025
- origin: codeServerUrl.origin,
46026
- basePath: normalizePathPrefix(codeServerUrl.pathname),
46027
- port: gateway2.port ?? port,
46028
- password: gateway2.password
46029
- };
46030
- }
46031
46040
  const gatewayUrl = res.data.gateway?.url;
46032
46041
  if (!gatewayUrl) {
46033
46042
  throw new ConnectionError("Devbox info does not include gateway.url; cannot derive httpgate endpoint", this.config.baseUrl);
@@ -48598,15 +48607,7 @@ class OpenSandboxAdapter extends BaseSandboxAdapter {
48598
48607
  await this.sandbox.close();
48599
48608
  }
48600
48609
  async getEndpoint(selector) {
48601
- const port = typeof selector === "number" ? selector : OPEN_SANDBOX_EXECD_PORT;
48602
- const endpoint = await this.getOpenSandboxEndpoint(port);
48603
- if (selector === "code-server") {
48604
- return {
48605
- ...endpoint,
48606
- url: joinUrlPath(endpoint.url, `/proxy/${OPEN_SANDBOX_CODE_SERVER_PORT}`)
48607
- };
48608
- }
48609
- return endpoint;
48610
+ return this.getOpenSandboxEndpoint(selector);
48610
48611
  }
48611
48612
  async getOpenSandboxEndpoint(port) {
48612
48613
  const sdkEndpoint = await this.sandbox.getEndpoint(port);
@@ -48627,17 +48628,6 @@ class OpenSandboxAdapter extends BaseSandboxAdapter {
48627
48628
  url: `https://${raw}`
48628
48629
  };
48629
48630
  }
48630
- async getProxyTarget(service = "code-server") {
48631
- if (service !== "code-server") {
48632
- throw new FeatureNotSupportedError(`Proxy service "${service}" is not supported by this provider`, "getProxyTarget", this.provider);
48633
- }
48634
- return {
48635
- service,
48636
- origin: await this.getDirectEndpointOrigin(OPEN_SANDBOX_EXECD_PORT),
48637
- basePath: `/proxy/${OPEN_SANDBOX_CODE_SERVER_PORT}`,
48638
- auth: "code-server"
48639
- };
48640
- }
48641
48631
  async getDirectEndpointOrigin(port) {
48642
48632
  if (!this.id) {
48643
48633
  throw new SandboxStateError("Sandbox not initialized. Call create() or connect() first.", "UnExist", "Running");
@@ -48693,10 +48683,18 @@ class OpenSandboxAdapter extends BaseSandboxAdapter {
48693
48683
  for (const entry of entries) {
48694
48684
  const normalizedPath = this.normalizePath(entry.path);
48695
48685
  try {
48686
+ let data = entry.data;
48687
+ if (data instanceof Uint8Array) {
48688
+ if (data.byteOffset === 0 && data.byteLength === data.buffer.byteLength) {
48689
+ data = data.buffer;
48690
+ } else {
48691
+ data = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
48692
+ }
48693
+ }
48696
48694
  await this.sandbox.files.writeFiles([
48697
48695
  {
48698
48696
  path: normalizedPath,
48699
- data: entry.data,
48697
+ data,
48700
48698
  mode: entry.mode,
48701
48699
  owner: entry.owner,
48702
48700
  group: entry.group
package/dist/index.js CHANGED
@@ -45176,7 +45176,10 @@ POLYFILL_EOF`);
45176
45176
  }
45177
45177
  }
45178
45178
  async listDirectory(path) {
45179
- const result = await this.executor.execute(`ls -la "${this.escapePath(path)}" --time-style=+"%Y-%m-%dT%H:%M:%S" 2>/dev/null || echo "DIRECTORY_NOT_FOUND"`);
45179
+ let result = await this.executor.execute(`ls -la "${this.escapePath(path)}" --time-style=+"%Y-%m-%dT%H:%M:%S" 2>/dev/null || echo "DIRECTORY_NOT_FOUND"`);
45180
+ if (result.stdout.includes("DIRECTORY_NOT_FOUND") || result.exitCode !== 0) {
45181
+ result = await this.executor.execute(`ls -la "${this.escapePath(path)}" 2>/dev/null || echo "DIRECTORY_NOT_FOUND"`);
45182
+ }
45180
45183
  if (result.stdout.includes("DIRECTORY_NOT_FOUND")) {
45181
45184
  throw new FileOperationError("Directory not found", path, "FILE_NOT_FOUND");
45182
45185
  }
@@ -45307,7 +45310,7 @@ POLYFILL_EOF`);
45307
45310
  if (line.startsWith("total") || !line.trim()) {
45308
45311
  continue;
45309
45312
  }
45310
- const match = line.match(/^([-dl])([-rwxsStT]{9})\s+\d+\s+\S+\s+\S+\s+(\d+)\s+(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})\s+(.+)$/);
45313
+ let match = line.match(/^([-dl])([-rwxsStT]{9})\s+\d+\s+\S+\s+\S+\s+(\d+)\s+(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})\s+(.+)$/);
45311
45314
  if (match) {
45312
45315
  const [, type, , size, dateStr, name] = match;
45313
45316
  if (name === "." || name === "..") {
@@ -45323,6 +45326,24 @@ POLYFILL_EOF`);
45323
45326
  size: Number.parseInt(size, 10) || undefined,
45324
45327
  modifiedAt: new Date(dateStr)
45325
45328
  });
45329
+ continue;
45330
+ }
45331
+ const parts = line.trim().split(/\s+/);
45332
+ if (parts.length >= 8) {
45333
+ const permissions = parts[0];
45334
+ const type = permissions[0];
45335
+ const size = parts[4];
45336
+ const name = parts.slice(8).join(" ");
45337
+ if (name === "." || name === "..") {
45338
+ continue;
45339
+ }
45340
+ entries.push({
45341
+ name,
45342
+ path: `${basePath}/${name}`,
45343
+ isDirectory: type === "d" || type === "l",
45344
+ isFile: type === "-",
45345
+ size: Number.parseInt(size, 10) || undefined
45346
+ });
45326
45347
  }
45327
45348
  }
45328
45349
  return entries;
@@ -45396,9 +45417,6 @@ class BaseSandboxAdapter {
45396
45417
  async getEndpoint(_selector) {
45397
45418
  throw new FeatureNotSupportedError("Endpoint resolution not supported by this provider", "getEndpoint", this.provider);
45398
45419
  }
45399
- async getProxyTarget(_service = "code-server") {
45400
- throw new FeatureNotSupportedError("Proxy target resolution not supported by this provider", "getProxyTarget", this.provider);
45401
- }
45402
45420
  async executeStream(command, handlers, options) {
45403
45421
  const result = await this.execute(command, options);
45404
45422
  if (handlers.onStdout && result.stdout) {
@@ -45609,6 +45627,20 @@ class BaseSandboxAdapter {
45609
45627
  }
45610
45628
 
45611
45629
  // src/adapters/SealosDevboxAdapter/api.ts
45630
+ class DevboxApiError extends Error {
45631
+ status;
45632
+ rawBody;
45633
+ url;
45634
+ constructor(message, status, rawBody, url) {
45635
+ super(message);
45636
+ this.status = status;
45637
+ this.rawBody = rawBody;
45638
+ this.url = url;
45639
+ this.name = "DevboxApiError";
45640
+ Object.setPrototypeOf(this, DevboxApiError.prototype);
45641
+ }
45642
+ }
45643
+
45612
45644
  class DevboxApi {
45613
45645
  baseUrl;
45614
45646
  token;
@@ -45632,7 +45664,13 @@ class DevboxApi {
45632
45664
  headers.set("Content-Type", "application/json");
45633
45665
  }
45634
45666
  const res = await fetch(input, { ...init, headers });
45635
- const result = await res.json();
45667
+ const rawBody = typeof res.text === "function" ? await res.text() : JSON.stringify(await res.json());
45668
+ let result;
45669
+ try {
45670
+ result = JSON.parse(rawBody);
45671
+ } catch {
45672
+ throw new DevboxApiError(`Devbox API returned non-JSON response (${res.status}): ${rawBody || res.statusText || res.status}`, res.status, rawBody, input);
45673
+ }
45636
45674
  return {
45637
45675
  ...result,
45638
45676
  code: result.code ?? res.status
@@ -45704,11 +45742,6 @@ class DevboxApi {
45704
45742
  }
45705
45743
  }
45706
45744
 
45707
- // src/adapters/ports.ts
45708
- var OPEN_SANDBOX_EXECD_PORT = 44772;
45709
- var OPEN_SANDBOX_CODE_SERVER_PORT = 8080;
45710
- var SEALOS_DEVBOX_CODE_SERVER_PORT = 1318;
45711
-
45712
45745
  // src/utils/image.ts
45713
45746
  function formatImageSpec(image) {
45714
45747
  const parts = [image.repository];
@@ -45746,6 +45779,9 @@ function joinUrlPath(url, path) {
45746
45779
  }
45747
45780
 
45748
45781
  // src/adapters/SealosDevboxAdapter/index.ts
45782
+ var GET_INFO_RETRY_TIMEOUT_MS = 30000;
45783
+ var GET_INFO_RETRY_INTERVAL_MS = 1000;
45784
+
45749
45785
  class SealosDevboxAdapter extends BaseSandboxAdapter {
45750
45786
  config;
45751
45787
  createConfig;
@@ -45816,6 +45852,33 @@ class SealosDevboxAdapter extends BaseSandboxAdapter {
45816
45852
  isNotFoundResponse(res) {
45817
45853
  return res.code === 404 || String(res.message ?? "").toLowerCase().includes("not found");
45818
45854
  }
45855
+ isRetryableGetInfoError(error) {
45856
+ let current = error;
45857
+ while (current instanceof Error) {
45858
+ if (current instanceof DevboxApiError) {
45859
+ const rawBody = current.rawBody.toLowerCase();
45860
+ return [502, 503, 504].includes(current.status) || rawBody.includes("no healthy upstream");
45861
+ }
45862
+ current = current.cause;
45863
+ }
45864
+ return false;
45865
+ }
45866
+ async getInfoWithProviderRetry(timeoutMs = GET_INFO_RETRY_TIMEOUT_MS, intervalMs = GET_INFO_RETRY_INTERVAL_MS) {
45867
+ const startTime = Date.now();
45868
+ let lastError;
45869
+ while (Date.now() - startTime < timeoutMs) {
45870
+ try {
45871
+ return await this.getInfo();
45872
+ } catch (error) {
45873
+ lastError = error;
45874
+ if (!this.isRetryableGetInfoError(error)) {
45875
+ throw error;
45876
+ }
45877
+ await this.sleep(intervalMs);
45878
+ }
45879
+ }
45880
+ throw lastError;
45881
+ }
45819
45882
  async getInfo() {
45820
45883
  try {
45821
45884
  const res = await this.api.info(this._id);
@@ -45842,7 +45905,7 @@ class SealosDevboxAdapter extends BaseSandboxAdapter {
45842
45905
  }
45843
45906
  async ensureRunning() {
45844
45907
  try {
45845
- const sandbox = await this.getInfo();
45908
+ const sandbox = await this.getInfoWithProviderRetry();
45846
45909
  if (sandbox) {
45847
45910
  const status = sandbox.status.state;
45848
45911
  switch (status) {
@@ -45944,11 +46007,7 @@ class SealosDevboxAdapter extends BaseSandboxAdapter {
45944
46007
  }
45945
46008
  }
45946
46009
  async getEndpoint(selector) {
45947
- const port = typeof selector === "number" ? selector : SEALOS_DEVBOX_CODE_SERVER_PORT;
45948
- const target = await this.getHttpgateTarget(port);
45949
- if (selector === "code-server") {
45950
- await this.waitForCodeServerHealthz(target);
45951
- }
46010
+ const target = await this.getHttpgateTarget(selector);
45952
46011
  const url = new URL(target.origin);
45953
46012
  return {
45954
46013
  host: url.host,
@@ -45957,61 +46016,11 @@ class SealosDevboxAdapter extends BaseSandboxAdapter {
45957
46016
  url: joinUrlPath(target.origin, target.basePath)
45958
46017
  };
45959
46018
  }
45960
- async getProxyTarget(service = "code-server") {
45961
- if (service !== "code-server") {
45962
- throw new FeatureNotSupportedError(`Proxy service "${service}" is not supported by this provider`, "getProxyTarget", this.provider);
45963
- }
45964
- const target = await this.getHttpgateTarget(SEALOS_DEVBOX_CODE_SERVER_PORT);
45965
- return {
45966
- service,
45967
- origin: target.origin,
45968
- basePath: target.basePath,
45969
- auth: "code-server",
45970
- ...target.password ? { password: target.password } : {}
45971
- };
45972
- }
45973
- async waitForCodeServerHealthz(target) {
45974
- const healthUrl = joinUrlPath(joinUrlPath(target.origin, target.basePath), "/healthz");
45975
- const timeoutMs = 60000;
45976
- const intervalMs = 500;
45977
- const requestTimeoutMs = 3000;
45978
- const deadline = Date.now() + timeoutMs;
45979
- let lastResult = "not checked";
45980
- while (Date.now() < deadline) {
45981
- try {
45982
- const res = await fetch(healthUrl, {
45983
- method: "GET",
45984
- signal: AbortSignal.timeout(requestTimeoutMs)
45985
- });
45986
- if (res.status >= 200 && res.status < 400) {
45987
- return;
45988
- }
45989
- lastResult = `status ${res.status}`;
45990
- } catch (error) {
45991
- lastResult = error instanceof Error ? error.message : String(error);
45992
- }
45993
- await this.sleep(intervalMs);
45994
- }
45995
- throw new ConnectionError(`Devbox code-server health check ${healthUrl} did not become ready within ${timeoutMs}ms. Last result: ${lastResult}`, this.config.baseUrl);
45996
- }
45997
46019
  async getHttpgateTarget(port) {
45998
46020
  const res = await this.api.info(this._id);
45999
46021
  if (res.code !== 200 || !res.data) {
46000
46022
  throw new ConnectionError(`Failed to get devbox info: ${res.message}`, this.config.baseUrl);
46001
46023
  }
46002
- if (port === SEALOS_DEVBOX_CODE_SERVER_PORT) {
46003
- const gateway2 = res.data.codeServerGateway;
46004
- if (!gateway2?.url) {
46005
- throw new ConnectionError("Devbox info does not include codeServerGateway.url; cannot resolve code-server endpoint", this.config.baseUrl);
46006
- }
46007
- const codeServerUrl = new URL(gateway2.url);
46008
- return {
46009
- origin: codeServerUrl.origin,
46010
- basePath: normalizePathPrefix(codeServerUrl.pathname),
46011
- port: gateway2.port ?? port,
46012
- password: gateway2.password
46013
- };
46014
- }
46015
46024
  const gatewayUrl = res.data.gateway?.url;
46016
46025
  if (!gatewayUrl) {
46017
46026
  throw new ConnectionError("Devbox info does not include gateway.url; cannot derive httpgate endpoint", this.config.baseUrl);
@@ -48582,15 +48591,7 @@ class OpenSandboxAdapter extends BaseSandboxAdapter {
48582
48591
  await this.sandbox.close();
48583
48592
  }
48584
48593
  async getEndpoint(selector) {
48585
- const port = typeof selector === "number" ? selector : OPEN_SANDBOX_EXECD_PORT;
48586
- const endpoint = await this.getOpenSandboxEndpoint(port);
48587
- if (selector === "code-server") {
48588
- return {
48589
- ...endpoint,
48590
- url: joinUrlPath(endpoint.url, `/proxy/${OPEN_SANDBOX_CODE_SERVER_PORT}`)
48591
- };
48592
- }
48593
- return endpoint;
48594
+ return this.getOpenSandboxEndpoint(selector);
48594
48595
  }
48595
48596
  async getOpenSandboxEndpoint(port) {
48596
48597
  const sdkEndpoint = await this.sandbox.getEndpoint(port);
@@ -48611,17 +48612,6 @@ class OpenSandboxAdapter extends BaseSandboxAdapter {
48611
48612
  url: `https://${raw}`
48612
48613
  };
48613
48614
  }
48614
- async getProxyTarget(service = "code-server") {
48615
- if (service !== "code-server") {
48616
- throw new FeatureNotSupportedError(`Proxy service "${service}" is not supported by this provider`, "getProxyTarget", this.provider);
48617
- }
48618
- return {
48619
- service,
48620
- origin: await this.getDirectEndpointOrigin(OPEN_SANDBOX_EXECD_PORT),
48621
- basePath: `/proxy/${OPEN_SANDBOX_CODE_SERVER_PORT}`,
48622
- auth: "code-server"
48623
- };
48624
- }
48625
48615
  async getDirectEndpointOrigin(port) {
48626
48616
  if (!this.id) {
48627
48617
  throw new SandboxStateError("Sandbox not initialized. Call create() or connect() first.", "UnExist", "Running");
@@ -48677,10 +48667,18 @@ class OpenSandboxAdapter extends BaseSandboxAdapter {
48677
48667
  for (const entry of entries) {
48678
48668
  const normalizedPath = this.normalizePath(entry.path);
48679
48669
  try {
48670
+ let data = entry.data;
48671
+ if (data instanceof Uint8Array) {
48672
+ if (data.byteOffset === 0 && data.byteLength === data.buffer.byteLength) {
48673
+ data = data.buffer;
48674
+ } else {
48675
+ data = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
48676
+ }
48677
+ }
48680
48678
  await this.sandbox.files.writeFiles([
48681
48679
  {
48682
48680
  path: normalizedPath,
48683
- data: entry.data,
48681
+ data,
48684
48682
  mode: entry.mode,
48685
48683
  owner: entry.owner,
48686
48684
  group: entry.group
@@ -2,7 +2,7 @@ import type { ICommandExecution } from './ICommandExecution';
2
2
  import type { IFileSystem } from './IFileSystem';
3
3
  import type { IHealthCheck } from './IHealthCheck';
4
4
  import type { ISandboxLifecycle } from './ISandboxLifecycle';
5
- import type { Endpoint, SandboxEndpointSelector, SandboxProxyService, SandboxProxyTarget } from '../types';
5
+ import type { Endpoint, SandboxEndpointSelector } from '../types';
6
6
  /**
7
7
  * Unified sandbox interface.
8
8
  * Composes all sandbox behaviors into a single interface.
@@ -18,6 +18,4 @@ export interface ISandbox extends ISandboxLifecycle, ICommandExecution, IFileSys
18
18
  readonly provider: string;
19
19
  /** Resolve an endpoint exposed by the sandbox provider. */
20
20
  getEndpoint(selector: SandboxEndpointSelector): Promise<Endpoint>;
21
- /** Resolve the upstream target used by FastGPT sandbox-proxy. */
22
- getProxyTarget(service?: SandboxProxyService): Promise<SandboxProxyTarget>;
23
21
  }
@@ -1,3 +1,3 @@
1
1
  export type { BackgroundExecution, ExecuteOptions, ExecuteResult, OutputMessage, StreamHandlers } from './execution';
2
2
  export type { ContentReplaceEntry, DirectoryEntry, FileDeleteResult, FileInfo, FileReadResult, FileWriteEntry, FileWriteResult, MoveEntry, PermissionEntry, ReadFileOptions, SearchResult } from './filesystem';
3
- export type { Endpoint, ImageSpec, KubeAccessPolicy, LabelSpec, LifecyclePolicy, NetworkPolicy, ResourceLimits, SandboxCreateSpec, SandboxEndpointSelector, SandboxId, SandboxInfo, SandboxMetrics, SandboxProxyService, SandboxProxyTarget, SandboxState, SandboxStatus } from './sandbox';
3
+ export type { Endpoint, ImageSpec, KubeAccessPolicy, LabelSpec, LifecyclePolicy, NetworkPolicy, ResourceLimits, SandboxCreateSpec, SandboxEndpointSelector, SandboxId, SandboxInfo, SandboxMetrics, SandboxState, SandboxStatus } from './sandbox';
@@ -105,12 +105,4 @@ export interface SandboxCreateSpec {
105
105
  readyTimeoutSeconds?: number;
106
106
  healthCheckPollingInterval?: number;
107
107
  }
108
- export type SandboxProxyService = 'code-server';
109
- export type SandboxEndpointSelector = number | SandboxProxyService;
110
- export interface SandboxProxyTarget {
111
- service: SandboxProxyService;
112
- origin: string;
113
- basePath: string;
114
- auth: 'code-server';
115
- password?: string;
116
- }
108
+ export type SandboxEndpointSelector = number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fastgpt-sdk/sandbox-adapter",
3
- "version": "0.0.40-beta.4",
3
+ "version": "0.0.40-beta.6",
4
4
  "description": "Unified abstraction layer for cloud sandbox providers with adapter pattern and feature polyfilling",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",