@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.
- package/dist/adapters/BaseSandboxAdapter.d.ts +1 -2
- package/dist/adapters/OpenSandboxAdapter/index.d.ts +1 -2
- package/dist/adapters/SealosDevboxAdapter/api.d.ts +6 -0
- package/dist/adapters/SealosDevboxAdapter/index.d.ts +8 -3
- package/dist/index.cjs +86 -88
- package/dist/index.js +86 -88
- package/dist/interfaces/ISandbox.d.ts +1 -3
- package/dist/types/index.d.ts +1 -1
- package/dist/types/sandbox.d.ts +1 -9
- package/package.json +1 -1
|
@@ -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,
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -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,
|
|
3
|
+
export type { Endpoint, ImageSpec, KubeAccessPolicy, LabelSpec, LifecyclePolicy, NetworkPolicy, ResourceLimits, SandboxCreateSpec, SandboxEndpointSelector, SandboxId, SandboxInfo, SandboxMetrics, SandboxState, SandboxStatus } from './sandbox';
|
package/dist/types/sandbox.d.ts
CHANGED
|
@@ -105,12 +105,4 @@ export interface SandboxCreateSpec {
|
|
|
105
105
|
readyTimeoutSeconds?: number;
|
|
106
106
|
healthCheckPollingInterval?: number;
|
|
107
107
|
}
|
|
108
|
-
export type
|
|
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.
|
|
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",
|