@sylphx/sdk 0.0.1 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +247 -260
- package/dist/index.js +229 -65
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +228 -65
- package/dist/index.mjs.map +1 -1
- package/dist/nextjs/index.js.map +1 -1
- package/dist/nextjs/index.mjs.map +1 -1
- package/dist/react/index.js +3668 -12695
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +3671 -12698
- package/dist/react/index.mjs.map +1 -1
- package/dist/server/index.js +40 -39
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +39 -39
- package/dist/server/index.mjs.map +1 -1
- package/dist/web-analytics.js +1 -1
- package/dist/web-analytics.js.map +1 -1
- package/dist/web-analytics.mjs +1 -1
- package/dist/web-analytics.mjs.map +1 -1
- package/package.json +5 -6
- package/dist/index.d.cts +0 -13938
- package/dist/index.d.ts +0 -13938
- package/dist/nextjs/index.d.cts +0 -2089
- package/dist/nextjs/index.d.ts +0 -2089
- package/dist/react/index.d.cts +0 -14894
- package/dist/react/index.d.ts +0 -14894
- package/dist/server/index.d.cts +0 -9908
- package/dist/server/index.d.ts +0 -9908
- package/dist/web-analytics.d.cts +0 -90
- package/dist/web-analytics.d.ts +0 -90
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/constants.ts
|
|
2
2
|
var DEFAULT_PLATFORM_URL = "https://sylphx.com";
|
|
3
|
-
var SDK_API_PATH = `/api/
|
|
3
|
+
var SDK_API_PATH = `/api/v1`;
|
|
4
4
|
var SDK_API_PATH_NEW = `/v1`;
|
|
5
5
|
var DEFAULT_SDK_API_HOST = "api.sylphx.com";
|
|
6
6
|
var SDK_VERSION = "0.1.0";
|
|
@@ -451,6 +451,7 @@ function httpStatusToErrorCode(status) {
|
|
|
451
451
|
return status >= 500 ? "INTERNAL_SERVER_ERROR" : "BAD_REQUEST";
|
|
452
452
|
}
|
|
453
453
|
}
|
|
454
|
+
var REF_PATTERN = /^[a-z0-9]{16}$/;
|
|
454
455
|
function createConfig(input) {
|
|
455
456
|
let secretKey;
|
|
456
457
|
if (input.secretKey) {
|
|
@@ -466,13 +467,22 @@ function createConfig(input) {
|
|
|
466
467
|
}
|
|
467
468
|
secretKey = result.sanitizedKey;
|
|
468
469
|
}
|
|
470
|
+
if (input.ref !== void 0) {
|
|
471
|
+
const trimmedRef = input.ref.trim();
|
|
472
|
+
if (!REF_PATTERN.test(trimmedRef)) {
|
|
473
|
+
throw new SylphxError(
|
|
474
|
+
`[Sylphx] Invalid project ref format: "${input.ref}". Expected a 16-character lowercase alphanumeric string (e.g. "abc123def456ghij"). Get your ref from Platform Console \u2192 Projects \u2192 Your Project \u2192 Overview.`,
|
|
475
|
+
{ code: "BAD_REQUEST" }
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
469
479
|
let platformUrl;
|
|
470
480
|
let apiBasePath;
|
|
471
481
|
if (input.platformUrl) {
|
|
472
482
|
platformUrl = input.platformUrl.trim();
|
|
473
483
|
apiBasePath = SDK_API_PATH;
|
|
474
484
|
} else if (input.ref) {
|
|
475
|
-
platformUrl = `https://${input.ref}.${DEFAULT_SDK_API_HOST}`;
|
|
485
|
+
platformUrl = `https://${input.ref.trim()}.${DEFAULT_SDK_API_HOST}`;
|
|
476
486
|
apiBasePath = SDK_API_PATH_NEW;
|
|
477
487
|
} else {
|
|
478
488
|
platformUrl = DEFAULT_PLATFORM_URL;
|
|
@@ -776,6 +786,7 @@ function createDeduplicationMiddleware(config = {}) {
|
|
|
776
786
|
deduped._dedupKey = key;
|
|
777
787
|
return deduped;
|
|
778
788
|
}
|
|
789
|
+
;
|
|
779
790
|
request._dedupKey = key;
|
|
780
791
|
return request;
|
|
781
792
|
},
|
|
@@ -799,30 +810,24 @@ function createDeduplicationMiddleware(config = {}) {
|
|
|
799
810
|
var CircuitBreakerOpenError = class extends Error {
|
|
800
811
|
remainingMs;
|
|
801
812
|
constructor(remainingMs) {
|
|
802
|
-
super(
|
|
803
|
-
`Circuit breaker is open. Retry after ${Math.ceil(remainingMs / 1e3)}s`
|
|
804
|
-
);
|
|
813
|
+
super(`Circuit breaker is open. Retry after ${Math.ceil(remainingMs / 1e3)}s`);
|
|
805
814
|
this.name = "CircuitBreakerOpenError";
|
|
806
815
|
this.remainingMs = remainingMs;
|
|
807
816
|
}
|
|
808
817
|
};
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
}
|
|
823
|
-
};
|
|
824
|
-
}
|
|
825
|
-
return circuitBreaker;
|
|
818
|
+
function createCircuitBreakerInstance(config = {}) {
|
|
819
|
+
return {
|
|
820
|
+
state: "CLOSED",
|
|
821
|
+
failures: [],
|
|
822
|
+
openedAt: null,
|
|
823
|
+
config: {
|
|
824
|
+
enabled: config.enabled ?? true,
|
|
825
|
+
failureThreshold: config.failureThreshold ?? CIRCUIT_BREAKER_FAILURE_THRESHOLD,
|
|
826
|
+
windowMs: config.windowMs ?? CIRCUIT_BREAKER_WINDOW_MS,
|
|
827
|
+
openDurationMs: config.openDurationMs ?? CIRCUIT_BREAKER_OPEN_DURATION_MS,
|
|
828
|
+
isFailure: config.isFailure ?? ((status) => status >= 500 || status === 429)
|
|
829
|
+
}
|
|
830
|
+
};
|
|
826
831
|
}
|
|
827
832
|
function recordFailure(cb) {
|
|
828
833
|
const now = Date.now();
|
|
@@ -870,7 +875,8 @@ function createCircuitBreakerMiddleware(config) {
|
|
|
870
875
|
}
|
|
871
876
|
};
|
|
872
877
|
}
|
|
873
|
-
const cb =
|
|
878
|
+
const cb = createCircuitBreakerInstance(config ?? {});
|
|
879
|
+
_lastCircuitBreaker = cb;
|
|
874
880
|
return {
|
|
875
881
|
async onRequest({ request }) {
|
|
876
882
|
if (!cb.config.enabled) {
|
|
@@ -895,15 +901,21 @@ function createCircuitBreakerMiddleware(config) {
|
|
|
895
901
|
}
|
|
896
902
|
};
|
|
897
903
|
}
|
|
904
|
+
var _lastCircuitBreaker = null;
|
|
898
905
|
function resetCircuitBreaker() {
|
|
899
|
-
|
|
906
|
+
if (_lastCircuitBreaker) {
|
|
907
|
+
_lastCircuitBreaker.state = "CLOSED";
|
|
908
|
+
_lastCircuitBreaker.failures = [];
|
|
909
|
+
_lastCircuitBreaker.openedAt = null;
|
|
910
|
+
}
|
|
911
|
+
_lastCircuitBreaker = null;
|
|
900
912
|
}
|
|
901
913
|
function getCircuitBreakerState() {
|
|
902
|
-
if (!
|
|
914
|
+
if (!_lastCircuitBreaker) return null;
|
|
903
915
|
return {
|
|
904
|
-
state:
|
|
905
|
-
failures:
|
|
906
|
-
openedAt:
|
|
916
|
+
state: _lastCircuitBreaker.state,
|
|
917
|
+
failures: _lastCircuitBreaker.failures.length,
|
|
918
|
+
openedAt: _lastCircuitBreaker.openedAt
|
|
907
919
|
};
|
|
908
920
|
}
|
|
909
921
|
var etagCache = /* @__PURE__ */ new Map();
|
|
@@ -995,6 +1007,7 @@ function createETagMiddleware(config) {
|
|
|
995
1007
|
}
|
|
996
1008
|
};
|
|
997
1009
|
}
|
|
1010
|
+
var retryBodyMap = /* @__PURE__ */ new WeakMap();
|
|
998
1011
|
function createRetryMiddleware(retryConfig) {
|
|
999
1012
|
if (retryConfig === false) {
|
|
1000
1013
|
return {
|
|
@@ -1010,27 +1023,22 @@ function createRetryMiddleware(retryConfig) {
|
|
|
1010
1023
|
shouldRetry = isRetryableStatus,
|
|
1011
1024
|
timeout = DEFAULT_TIMEOUT_MS
|
|
1012
1025
|
} = retryConfig ?? {};
|
|
1013
|
-
let originalBody = null;
|
|
1014
1026
|
return {
|
|
1015
1027
|
async onRequest({ request }) {
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
body: originalBody,
|
|
1028
|
-
signal: controller.signal
|
|
1029
|
-
});
|
|
1030
|
-
}
|
|
1031
|
-
return request;
|
|
1028
|
+
const body = request.body ? await request.clone().text() : null;
|
|
1029
|
+
const controller = new AbortController();
|
|
1030
|
+
setTimeout(() => controller.abort(), timeout);
|
|
1031
|
+
const newRequest = new Request(request.url, {
|
|
1032
|
+
method: request.method,
|
|
1033
|
+
headers: request.headers,
|
|
1034
|
+
body,
|
|
1035
|
+
signal: controller.signal
|
|
1036
|
+
});
|
|
1037
|
+
retryBodyMap.set(newRequest, body);
|
|
1038
|
+
return newRequest;
|
|
1032
1039
|
},
|
|
1033
1040
|
async onResponse({ response, request }) {
|
|
1041
|
+
const originalBody = retryBodyMap.get(request) ?? null;
|
|
1034
1042
|
let attempt = 0;
|
|
1035
1043
|
let currentResponse = response;
|
|
1036
1044
|
while (attempt < maxRetries && shouldRetry(currentResponse.status, attempt)) {
|
|
@@ -1050,16 +1058,19 @@ function createRetryMiddleware(retryConfig) {
|
|
|
1050
1058
|
const newResponse = await fetch(retryRequest);
|
|
1051
1059
|
clearTimeout(timeoutId);
|
|
1052
1060
|
if (newResponse.ok || !shouldRetry(newResponse.status, attempt)) {
|
|
1061
|
+
retryBodyMap.delete(request);
|
|
1053
1062
|
return newResponse;
|
|
1054
1063
|
}
|
|
1055
1064
|
currentResponse = newResponse;
|
|
1056
1065
|
} catch (error) {
|
|
1057
1066
|
clearTimeout(timeoutId);
|
|
1058
1067
|
if (attempt >= maxRetries) {
|
|
1068
|
+
retryBodyMap.delete(request);
|
|
1059
1069
|
throw error;
|
|
1060
1070
|
}
|
|
1061
1071
|
}
|
|
1062
1072
|
}
|
|
1073
|
+
retryBodyMap.delete(request);
|
|
1063
1074
|
return currentResponse;
|
|
1064
1075
|
}
|
|
1065
1076
|
};
|
|
@@ -1148,35 +1159,20 @@ async function signOut(config) {
|
|
|
1148
1159
|
await callApi(config, "/auth/logout", { method: "POST" });
|
|
1149
1160
|
}
|
|
1150
1161
|
async function refreshToken(config, token) {
|
|
1151
|
-
|
|
1162
|
+
return callApi(config, "/auth/token", {
|
|
1152
1163
|
method: "POST",
|
|
1153
|
-
|
|
1154
|
-
body: JSON.stringify({
|
|
1164
|
+
body: {
|
|
1155
1165
|
grant_type: "refresh_token",
|
|
1156
1166
|
refresh_token: token,
|
|
1157
1167
|
client_secret: config.secretKey
|
|
1158
|
-
}
|
|
1168
|
+
}
|
|
1159
1169
|
});
|
|
1160
|
-
if (!response.ok) {
|
|
1161
|
-
const error = await response.json().catch(() => ({ message: "Token refresh failed" }));
|
|
1162
|
-
throw new SylphxError(error.message ?? "Token refresh failed", {
|
|
1163
|
-
code: "UNAUTHORIZED"
|
|
1164
|
-
});
|
|
1165
|
-
}
|
|
1166
|
-
return response.json();
|
|
1167
1170
|
}
|
|
1168
1171
|
async function verifyEmail(config, token) {
|
|
1169
|
-
|
|
1172
|
+
await callApi(config, "/auth/verify-email", {
|
|
1170
1173
|
method: "POST",
|
|
1171
|
-
|
|
1172
|
-
body: JSON.stringify({ token })
|
|
1174
|
+
body: { token }
|
|
1173
1175
|
});
|
|
1174
|
-
if (!response.ok) {
|
|
1175
|
-
const error = await response.json().catch(() => ({ message: "Email verification failed" }));
|
|
1176
|
-
throw new SylphxError(error.message ?? "Email verification failed", {
|
|
1177
|
-
code: "BAD_REQUEST"
|
|
1178
|
-
});
|
|
1179
|
-
}
|
|
1180
1176
|
}
|
|
1181
1177
|
async function forgotPassword(config, email) {
|
|
1182
1178
|
await callApi(config, "/auth/forgot-password", {
|
|
@@ -2856,6 +2852,172 @@ async function captureMessage(config, message, options = {}) {
|
|
|
2856
2852
|
body: request
|
|
2857
2853
|
});
|
|
2858
2854
|
}
|
|
2855
|
+
|
|
2856
|
+
// src/sandbox.ts
|
|
2857
|
+
var DEFAULT_IMAGE = "registry.sylphx.com/library/exec-server:latest";
|
|
2858
|
+
var SandboxClient = class _SandboxClient {
|
|
2859
|
+
id;
|
|
2860
|
+
config;
|
|
2861
|
+
constructor(id, config) {
|
|
2862
|
+
this.id = id;
|
|
2863
|
+
this.config = config;
|
|
2864
|
+
}
|
|
2865
|
+
// ---------------------------------------------------------------------------
|
|
2866
|
+
// Factory
|
|
2867
|
+
// ---------------------------------------------------------------------------
|
|
2868
|
+
/**
|
|
2869
|
+
* Create a new sandbox.
|
|
2870
|
+
*
|
|
2871
|
+
* The sandbox pod starts asynchronously — it will be in `starting` status
|
|
2872
|
+
* after creation. File/run operations will block until the pod is ready.
|
|
2873
|
+
*
|
|
2874
|
+
* @param config - Sylphx config with a secret key (sk_*) and project ref
|
|
2875
|
+
* @param options - Sandbox creation options
|
|
2876
|
+
*/
|
|
2877
|
+
static async create(config, options) {
|
|
2878
|
+
const record = await callApi(config, `/sandboxes`, {
|
|
2879
|
+
method: "POST",
|
|
2880
|
+
body: {
|
|
2881
|
+
image: options?.image ?? DEFAULT_IMAGE,
|
|
2882
|
+
idleTimeoutMs: options?.idleTimeoutMs ?? 3e5,
|
|
2883
|
+
resources: options?.resources,
|
|
2884
|
+
env: options?.env,
|
|
2885
|
+
storage: options?.storageGi !== void 0 ? { enabled: true, sizeGi: options.storageGi } : void 0
|
|
2886
|
+
}
|
|
2887
|
+
});
|
|
2888
|
+
return new _SandboxClient(record.id, config);
|
|
2889
|
+
}
|
|
2890
|
+
/**
|
|
2891
|
+
* Reconnect to an existing sandbox by ID.
|
|
2892
|
+
*
|
|
2893
|
+
* Use this to resume operations on a sandbox created in a previous request.
|
|
2894
|
+
*/
|
|
2895
|
+
static fromId(config, sandboxId) {
|
|
2896
|
+
return new _SandboxClient(sandboxId, config);
|
|
2897
|
+
}
|
|
2898
|
+
// ---------------------------------------------------------------------------
|
|
2899
|
+
// Lifecycle
|
|
2900
|
+
// ---------------------------------------------------------------------------
|
|
2901
|
+
/**
|
|
2902
|
+
* Get the current status of this sandbox.
|
|
2903
|
+
*/
|
|
2904
|
+
async getStatus() {
|
|
2905
|
+
return callApi(
|
|
2906
|
+
this.config,
|
|
2907
|
+
`/sandboxes/${this.id}`,
|
|
2908
|
+
{ method: "GET" }
|
|
2909
|
+
);
|
|
2910
|
+
}
|
|
2911
|
+
/**
|
|
2912
|
+
* Terminate the sandbox immediately.
|
|
2913
|
+
*
|
|
2914
|
+
* Deletes the K8s Pod and Service. PVC (storage) is preserved for reuse.
|
|
2915
|
+
* This operation is idempotent — safe to call multiple times.
|
|
2916
|
+
*/
|
|
2917
|
+
async terminate() {
|
|
2918
|
+
await callApi(
|
|
2919
|
+
this.config,
|
|
2920
|
+
`/sandboxes/${this.id}`,
|
|
2921
|
+
{ method: "DELETE" }
|
|
2922
|
+
);
|
|
2923
|
+
}
|
|
2924
|
+
// ---------------------------------------------------------------------------
|
|
2925
|
+
// File Operations
|
|
2926
|
+
// ---------------------------------------------------------------------------
|
|
2927
|
+
/**
|
|
2928
|
+
* Write a file to the sandbox filesystem.
|
|
2929
|
+
*
|
|
2930
|
+
* @param path - Absolute path inside the sandbox (e.g. '/workspace/file.py')
|
|
2931
|
+
* @param content - File content as string or Buffer
|
|
2932
|
+
* @param encoding - 'utf8' (default) or 'base64' for binary files
|
|
2933
|
+
*/
|
|
2934
|
+
async writeFile(path, content, encoding = "utf8") {
|
|
2935
|
+
const contentStr = Buffer.isBuffer(content) ? content.toString("base64") : content;
|
|
2936
|
+
const effectiveEncoding = Buffer.isBuffer(content) ? "base64" : encoding;
|
|
2937
|
+
await callApi(
|
|
2938
|
+
this.config,
|
|
2939
|
+
`/sandboxes/${this.id}/files`,
|
|
2940
|
+
{
|
|
2941
|
+
method: "POST",
|
|
2942
|
+
body: { path, content: contentStr, encoding: effectiveEncoding }
|
|
2943
|
+
}
|
|
2944
|
+
);
|
|
2945
|
+
}
|
|
2946
|
+
/**
|
|
2947
|
+
* Read a file from the sandbox filesystem.
|
|
2948
|
+
*
|
|
2949
|
+
* @param path - Absolute path inside the sandbox
|
|
2950
|
+
* @returns File content as a string
|
|
2951
|
+
*/
|
|
2952
|
+
async readFile(path) {
|
|
2953
|
+
const result = await callApi(
|
|
2954
|
+
this.config,
|
|
2955
|
+
`/sandboxes/${this.id}/files`,
|
|
2956
|
+
{
|
|
2957
|
+
method: "GET",
|
|
2958
|
+
query: { path }
|
|
2959
|
+
}
|
|
2960
|
+
);
|
|
2961
|
+
return result.content;
|
|
2962
|
+
}
|
|
2963
|
+
/**
|
|
2964
|
+
* Delete a file from the sandbox filesystem.
|
|
2965
|
+
*
|
|
2966
|
+
* @param path - Absolute path inside the sandbox
|
|
2967
|
+
*/
|
|
2968
|
+
async deleteFile(path) {
|
|
2969
|
+
await callApi(
|
|
2970
|
+
this.config,
|
|
2971
|
+
`/sandboxes/${this.id}/files`,
|
|
2972
|
+
{
|
|
2973
|
+
method: "DELETE",
|
|
2974
|
+
query: { path }
|
|
2975
|
+
}
|
|
2976
|
+
);
|
|
2977
|
+
}
|
|
2978
|
+
/**
|
|
2979
|
+
* List files in a directory.
|
|
2980
|
+
*
|
|
2981
|
+
* @param path - Directory path inside the sandbox (default: '/')
|
|
2982
|
+
* @returns Array of file/directory paths
|
|
2983
|
+
*/
|
|
2984
|
+
async listFiles(path = "/") {
|
|
2985
|
+
const result = await callApi(
|
|
2986
|
+
this.config,
|
|
2987
|
+
`/sandboxes/${this.id}/list`,
|
|
2988
|
+
{
|
|
2989
|
+
method: "GET",
|
|
2990
|
+
query: { path }
|
|
2991
|
+
}
|
|
2992
|
+
);
|
|
2993
|
+
return result.files;
|
|
2994
|
+
}
|
|
2995
|
+
// ---------------------------------------------------------------------------
|
|
2996
|
+
// Command Execution
|
|
2997
|
+
// ---------------------------------------------------------------------------
|
|
2998
|
+
/**
|
|
2999
|
+
* Run a command inside the sandbox.
|
|
3000
|
+
*
|
|
3001
|
+
* The command runs synchronously inside the exec pod and returns when complete.
|
|
3002
|
+
*
|
|
3003
|
+
* @param command - Full command + args as array (e.g. ['python3', 'script.py'])
|
|
3004
|
+
* @param options - Optional cwd, env, timeout, stdin
|
|
3005
|
+
* @returns { stdout, stderr, exitCode, durationMs }
|
|
3006
|
+
*
|
|
3007
|
+
* @example
|
|
3008
|
+
* ```typescript
|
|
3009
|
+
* const { stdout, exitCode } = await sandbox.run(['python3', '-c', 'print(1+1)'])
|
|
3010
|
+
* console.log(stdout) // "2\n"
|
|
3011
|
+
* console.log(exitCode) // 0
|
|
3012
|
+
* ```
|
|
3013
|
+
*/
|
|
3014
|
+
async run(command, options) {
|
|
3015
|
+
return callApi(this.config, `/sandboxes/${this.id}/run`, {
|
|
3016
|
+
method: "POST",
|
|
3017
|
+
body: { command, ...options }
|
|
3018
|
+
});
|
|
3019
|
+
}
|
|
3020
|
+
};
|
|
2859
3021
|
export {
|
|
2860
3022
|
ACHIEVEMENT_TIER_CONFIG,
|
|
2861
3023
|
AuthenticationError,
|
|
@@ -2866,6 +3028,7 @@ export {
|
|
|
2866
3028
|
NotFoundError,
|
|
2867
3029
|
RETRYABLE_CODES,
|
|
2868
3030
|
RateLimitError,
|
|
3031
|
+
SandboxClient,
|
|
2869
3032
|
SylphxError,
|
|
2870
3033
|
TimeoutError,
|
|
2871
3034
|
ValidationError,
|