rl-rock 1.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/dist/index.d.mts +1308 -0
- package/dist/index.d.ts +1308 -0
- package/dist/index.js +2037 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1950 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +61 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1950 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import axios3, { AxiosError } from 'axios';
|
|
3
|
+
import https from 'https';
|
|
4
|
+
import 'os';
|
|
5
|
+
import { join, resolve } from 'path';
|
|
6
|
+
import winston from 'winston';
|
|
7
|
+
import { existsSync, mkdirSync, statSync, readFileSync, appendFileSync } from 'fs';
|
|
8
|
+
import { randomUUID } from 'crypto';
|
|
9
|
+
import { spawn } from 'child_process';
|
|
10
|
+
|
|
11
|
+
// src/types/codes.ts
|
|
12
|
+
var Codes = /* @__PURE__ */ ((Codes2) => {
|
|
13
|
+
Codes2[Codes2["OK"] = 2e3] = "OK";
|
|
14
|
+
Codes2[Codes2["BAD_REQUEST"] = 4e3] = "BAD_REQUEST";
|
|
15
|
+
Codes2[Codes2["INTERNAL_SERVER_ERROR"] = 5e3] = "INTERNAL_SERVER_ERROR";
|
|
16
|
+
Codes2[Codes2["COMMAND_ERROR"] = 6e3] = "COMMAND_ERROR";
|
|
17
|
+
return Codes2;
|
|
18
|
+
})(Codes || {});
|
|
19
|
+
var ReasonPhrases = {
|
|
20
|
+
[2e3 /* OK */]: "OK",
|
|
21
|
+
[4e3 /* BAD_REQUEST */]: "Bad Request",
|
|
22
|
+
[5e3 /* INTERNAL_SERVER_ERROR */]: "Internal Server Error",
|
|
23
|
+
[6e3 /* COMMAND_ERROR */]: "Command Error"
|
|
24
|
+
};
|
|
25
|
+
function getReasonPhrase(code) {
|
|
26
|
+
return ReasonPhrases[code] ?? "";
|
|
27
|
+
}
|
|
28
|
+
function isSuccess(code) {
|
|
29
|
+
return code >= 2e3 && code <= 2999;
|
|
30
|
+
}
|
|
31
|
+
function isClientError(code) {
|
|
32
|
+
return code >= 4e3 && code <= 4999;
|
|
33
|
+
}
|
|
34
|
+
function isServerError(code) {
|
|
35
|
+
return code >= 5e3 && code <= 5999;
|
|
36
|
+
}
|
|
37
|
+
function isCommandError(code) {
|
|
38
|
+
return code >= 6e3 && code <= 6999;
|
|
39
|
+
}
|
|
40
|
+
function isError(code) {
|
|
41
|
+
return code >= 4e3 && code <= 6999;
|
|
42
|
+
}
|
|
43
|
+
var CommandSchema = z.object({
|
|
44
|
+
command: z.union([z.string(), z.array(z.string())]),
|
|
45
|
+
timeout: z.number().optional().default(1200),
|
|
46
|
+
env: z.record(z.string()).optional(),
|
|
47
|
+
cwd: z.string().optional()
|
|
48
|
+
});
|
|
49
|
+
var CreateBashSessionRequestSchema = z.object({
|
|
50
|
+
session: z.string().default("default"),
|
|
51
|
+
startupSource: z.array(z.string()).default([]),
|
|
52
|
+
envEnable: z.boolean().default(false),
|
|
53
|
+
env: z.record(z.string()).optional(),
|
|
54
|
+
remoteUser: z.string().optional()
|
|
55
|
+
});
|
|
56
|
+
var BashActionSchema = z.object({
|
|
57
|
+
command: z.string(),
|
|
58
|
+
session: z.string().default("default"),
|
|
59
|
+
timeout: z.number().optional(),
|
|
60
|
+
check: z.enum(["silent", "raise", "ignore"]).default("raise")
|
|
61
|
+
});
|
|
62
|
+
var WriteFileRequestSchema = z.object({
|
|
63
|
+
content: z.string(),
|
|
64
|
+
path: z.string()
|
|
65
|
+
});
|
|
66
|
+
var ReadFileRequestSchema = z.object({
|
|
67
|
+
path: z.string(),
|
|
68
|
+
encoding: z.string().optional(),
|
|
69
|
+
errors: z.string().optional()
|
|
70
|
+
});
|
|
71
|
+
var UploadRequestSchema = z.object({
|
|
72
|
+
sourcePath: z.string(),
|
|
73
|
+
targetPath: z.string()
|
|
74
|
+
});
|
|
75
|
+
var CloseSessionRequestSchema = z.object({
|
|
76
|
+
session: z.string().default("default")
|
|
77
|
+
});
|
|
78
|
+
var ChownRequestSchema = z.object({
|
|
79
|
+
remoteUser: z.string(),
|
|
80
|
+
paths: z.array(z.string()).default([]),
|
|
81
|
+
recursive: z.boolean().default(false)
|
|
82
|
+
});
|
|
83
|
+
var ChmodRequestSchema = z.object({
|
|
84
|
+
paths: z.array(z.string()).default([]),
|
|
85
|
+
mode: z.string().default("755"),
|
|
86
|
+
recursive: z.boolean().default(false)
|
|
87
|
+
});
|
|
88
|
+
var SandboxResponseSchema = z.object({
|
|
89
|
+
code: z.nativeEnum(Codes).optional(),
|
|
90
|
+
exitCode: z.number().optional(),
|
|
91
|
+
failureReason: z.string().optional()
|
|
92
|
+
});
|
|
93
|
+
var IsAliveResponseSchema = z.object({
|
|
94
|
+
isAlive: z.boolean(),
|
|
95
|
+
message: z.string().default("")
|
|
96
|
+
});
|
|
97
|
+
var SandboxStatusResponseSchema = z.object({
|
|
98
|
+
sandboxId: z.string().optional(),
|
|
99
|
+
status: z.record(z.unknown()).optional(),
|
|
100
|
+
portMapping: z.record(z.unknown()).optional(),
|
|
101
|
+
hostName: z.string().optional(),
|
|
102
|
+
hostIp: z.string().optional(),
|
|
103
|
+
isAlive: z.boolean().default(true),
|
|
104
|
+
image: z.string().optional(),
|
|
105
|
+
gatewayVersion: z.string().optional(),
|
|
106
|
+
sweRexVersion: z.string().optional(),
|
|
107
|
+
userId: z.string().optional(),
|
|
108
|
+
experimentId: z.string().optional(),
|
|
109
|
+
namespace: z.string().optional(),
|
|
110
|
+
cpus: z.number().optional(),
|
|
111
|
+
memory: z.string().optional()
|
|
112
|
+
});
|
|
113
|
+
var CommandResponseSchema = z.object({
|
|
114
|
+
stdout: z.string().default(""),
|
|
115
|
+
stderr: z.string().default(""),
|
|
116
|
+
exitCode: z.number().optional()
|
|
117
|
+
});
|
|
118
|
+
var WriteFileResponseSchema = z.object({
|
|
119
|
+
success: z.boolean().default(false),
|
|
120
|
+
message: z.string().default("")
|
|
121
|
+
});
|
|
122
|
+
var ReadFileResponseSchema = z.object({
|
|
123
|
+
content: z.string().default("")
|
|
124
|
+
});
|
|
125
|
+
var UploadResponseSchema = z.object({
|
|
126
|
+
success: z.boolean().default(false),
|
|
127
|
+
message: z.string().default(""),
|
|
128
|
+
fileName: z.string().optional()
|
|
129
|
+
});
|
|
130
|
+
var ObservationSchema = z.object({
|
|
131
|
+
output: z.string().default(""),
|
|
132
|
+
exitCode: z.number().optional(),
|
|
133
|
+
failureReason: z.string().default(""),
|
|
134
|
+
expectString: z.string().default("")
|
|
135
|
+
});
|
|
136
|
+
var CreateSessionResponseSchema = z.object({
|
|
137
|
+
output: z.string().default("")
|
|
138
|
+
});
|
|
139
|
+
var CloseSessionResponseSchema = z.object({});
|
|
140
|
+
var CloseResponseSchema = z.object({});
|
|
141
|
+
var ChownResponseSchema = z.object({
|
|
142
|
+
success: z.boolean().default(false),
|
|
143
|
+
message: z.string().default("")
|
|
144
|
+
});
|
|
145
|
+
var ChmodResponseSchema = z.object({
|
|
146
|
+
success: z.boolean().default(false),
|
|
147
|
+
message: z.string().default("")
|
|
148
|
+
});
|
|
149
|
+
var ExecuteBashSessionResponseSchema = z.object({
|
|
150
|
+
success: z.boolean().default(false),
|
|
151
|
+
message: z.string().default("")
|
|
152
|
+
});
|
|
153
|
+
var OssSetupResponseSchema = z.object({
|
|
154
|
+
success: z.boolean().default(false),
|
|
155
|
+
message: z.string().default("")
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// src/common/constants.ts
|
|
159
|
+
var RunMode = {
|
|
160
|
+
NORMAL: "normal",
|
|
161
|
+
NOHUP: "nohup"
|
|
162
|
+
};
|
|
163
|
+
var Constants = class {
|
|
164
|
+
static BASE_URL_PRODUCT = "";
|
|
165
|
+
static BASE_URL_ALIYUN = "";
|
|
166
|
+
static BASE_URL_INNER = "";
|
|
167
|
+
static BASE_URL_PRE = "";
|
|
168
|
+
static BASE_URL_LOCAL = "";
|
|
169
|
+
static REQUEST_TIMEOUT_SECONDS = 180;
|
|
170
|
+
};
|
|
171
|
+
var PID_PREFIX = "__ROCK_PID_START__";
|
|
172
|
+
var PID_SUFFIX = "__ROCK_PID_END__";
|
|
173
|
+
|
|
174
|
+
// src/common/exceptions.ts
|
|
175
|
+
var RockException = class extends Error {
|
|
176
|
+
_code = null;
|
|
177
|
+
constructor(message, code) {
|
|
178
|
+
super(message);
|
|
179
|
+
this.name = "RockException";
|
|
180
|
+
this._code = code ?? null;
|
|
181
|
+
}
|
|
182
|
+
get code() {
|
|
183
|
+
return this._code;
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
var InvalidParameterRockException = class extends RockException {
|
|
187
|
+
constructor(message) {
|
|
188
|
+
super(message);
|
|
189
|
+
this.name = "InvalidParameterRockException";
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
var BadRequestRockError = class extends RockException {
|
|
193
|
+
constructor(message, code = 4e3 /* BAD_REQUEST */) {
|
|
194
|
+
super(message, code);
|
|
195
|
+
this.name = "BadRequestRockError";
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
var InternalServerRockError = class extends RockException {
|
|
199
|
+
constructor(message, code = 5e3 /* INTERNAL_SERVER_ERROR */) {
|
|
200
|
+
super(message, code);
|
|
201
|
+
this.name = "InternalServerRockError";
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
var CommandRockError = class extends RockException {
|
|
205
|
+
constructor(message, code = 6e3 /* COMMAND_ERROR */) {
|
|
206
|
+
super(message, code);
|
|
207
|
+
this.name = "CommandRockError";
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
function raiseForCode(code, message) {
|
|
211
|
+
if (code === null || code === void 0 || isSuccessCode(code)) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (isClientErrorCode(code)) {
|
|
215
|
+
throw new BadRequestRockError(message, code);
|
|
216
|
+
}
|
|
217
|
+
if (isServerErrorCode(code)) {
|
|
218
|
+
throw new InternalServerRockError(message, code);
|
|
219
|
+
}
|
|
220
|
+
if (isCommandErrorCode(code)) {
|
|
221
|
+
throw new CommandRockError(message, code);
|
|
222
|
+
}
|
|
223
|
+
throw new RockException(message, code);
|
|
224
|
+
}
|
|
225
|
+
function fromRockException(e) {
|
|
226
|
+
return {
|
|
227
|
+
code: e.code ?? void 0,
|
|
228
|
+
exitCode: 1,
|
|
229
|
+
failureReason: e.message
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
function isSuccessCode(code) {
|
|
233
|
+
return code >= 2e3 && code <= 2999;
|
|
234
|
+
}
|
|
235
|
+
function isClientErrorCode(code) {
|
|
236
|
+
return code >= 4e3 && code <= 4999;
|
|
237
|
+
}
|
|
238
|
+
function isServerErrorCode(code) {
|
|
239
|
+
return code >= 5e3 && code <= 5999;
|
|
240
|
+
}
|
|
241
|
+
function isCommandErrorCode(code) {
|
|
242
|
+
return code >= 6e3 && code <= 6999;
|
|
243
|
+
}
|
|
244
|
+
var HttpUtils = class {
|
|
245
|
+
static defaultTimeout = 3e5;
|
|
246
|
+
// 5 minutes
|
|
247
|
+
static defaultConnectTimeout = 3e5;
|
|
248
|
+
/**
|
|
249
|
+
* Create axios instance with default config
|
|
250
|
+
*/
|
|
251
|
+
static createClient(config) {
|
|
252
|
+
return axios3.create({
|
|
253
|
+
timeout: config?.timeout ?? this.defaultTimeout,
|
|
254
|
+
headers: {
|
|
255
|
+
"Content-Type": "application/json",
|
|
256
|
+
...config?.headers
|
|
257
|
+
},
|
|
258
|
+
httpsAgent: new https.Agent({
|
|
259
|
+
rejectUnauthorized: true
|
|
260
|
+
})
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Send POST request
|
|
265
|
+
*/
|
|
266
|
+
static async post(url, headers, data, readTimeout) {
|
|
267
|
+
const client = this.createClient({
|
|
268
|
+
timeout: readTimeout ?? this.defaultTimeout,
|
|
269
|
+
headers
|
|
270
|
+
});
|
|
271
|
+
try {
|
|
272
|
+
const response = await client.post(url, data);
|
|
273
|
+
return response.data;
|
|
274
|
+
} catch (error) {
|
|
275
|
+
if (error instanceof AxiosError) {
|
|
276
|
+
throw new Error(`Failed to POST ${url}: ${error.message}`);
|
|
277
|
+
}
|
|
278
|
+
throw error;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Send GET request
|
|
283
|
+
*/
|
|
284
|
+
static async get(url, headers) {
|
|
285
|
+
const client = this.createClient({ headers });
|
|
286
|
+
try {
|
|
287
|
+
const response = await client.get(url);
|
|
288
|
+
return response.data;
|
|
289
|
+
} catch (error) {
|
|
290
|
+
if (error instanceof AxiosError) {
|
|
291
|
+
throw new Error(`Failed to GET ${url}: ${error.message}`);
|
|
292
|
+
}
|
|
293
|
+
throw error;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Send multipart/form-data request
|
|
298
|
+
*/
|
|
299
|
+
static async postMultipart(url, headers, data, files) {
|
|
300
|
+
const formData = new FormData();
|
|
301
|
+
if (data) {
|
|
302
|
+
for (const [key, value] of Object.entries(data)) {
|
|
303
|
+
if (value !== void 0 && value !== null) {
|
|
304
|
+
formData.append(key, String(value));
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
if (files) {
|
|
309
|
+
for (const [fieldName, fileData] of Object.entries(files)) {
|
|
310
|
+
if (fileData !== void 0 && fileData !== null) {
|
|
311
|
+
if (Array.isArray(fileData)) {
|
|
312
|
+
const [filename, content, contentType] = fileData;
|
|
313
|
+
const blob = new Blob([content], { type: contentType });
|
|
314
|
+
formData.append(fieldName, blob, filename);
|
|
315
|
+
} else if (fileData instanceof Buffer) {
|
|
316
|
+
const blob = new Blob([fileData], { type: "application/octet-stream" });
|
|
317
|
+
formData.append(fieldName, blob, "file");
|
|
318
|
+
} else if (fileData instanceof File) {
|
|
319
|
+
formData.append(fieldName, fileData);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
const client = this.createClient({
|
|
325
|
+
headers: {
|
|
326
|
+
...headers,
|
|
327
|
+
"Content-Type": "multipart/form-data"
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
try {
|
|
331
|
+
const response = await client.post(url, formData);
|
|
332
|
+
return response.data;
|
|
333
|
+
} catch (error) {
|
|
334
|
+
if (error instanceof AxiosError) {
|
|
335
|
+
throw new Error(`Failed to POST multipart ${url}: ${error.message}`);
|
|
336
|
+
}
|
|
337
|
+
throw error;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Guess MIME type from filename
|
|
342
|
+
*/
|
|
343
|
+
static guessContentType(filename) {
|
|
344
|
+
const ext = filename.split(".").pop()?.toLowerCase();
|
|
345
|
+
const mimeTypes = {
|
|
346
|
+
txt: "text/plain",
|
|
347
|
+
html: "text/html",
|
|
348
|
+
css: "text/css",
|
|
349
|
+
js: "application/javascript",
|
|
350
|
+
json: "application/json",
|
|
351
|
+
xml: "application/xml",
|
|
352
|
+
pdf: "application/pdf",
|
|
353
|
+
zip: "application/zip",
|
|
354
|
+
tar: "application/x-tar",
|
|
355
|
+
gz: "application/gzip",
|
|
356
|
+
png: "image/png",
|
|
357
|
+
jpg: "image/jpeg",
|
|
358
|
+
jpeg: "image/jpeg",
|
|
359
|
+
gif: "image/gif",
|
|
360
|
+
svg: "image/svg+xml"
|
|
361
|
+
};
|
|
362
|
+
return mimeTypes[ext ?? ""] ?? "application/octet-stream";
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
// src/utils/retry.ts
|
|
367
|
+
function retryAsync(fn, options = {}) {
|
|
368
|
+
const {
|
|
369
|
+
maxAttempts = 3,
|
|
370
|
+
delaySeconds = 1,
|
|
371
|
+
backoff = 1,
|
|
372
|
+
jitter = false
|
|
373
|
+
} = options;
|
|
374
|
+
return retryAsyncImpl(fn, {
|
|
375
|
+
maxAttempts,
|
|
376
|
+
delaySeconds,
|
|
377
|
+
backoff,
|
|
378
|
+
jitter
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
async function retryAsyncImpl(fn, options) {
|
|
382
|
+
const { maxAttempts, delaySeconds, backoff, jitter } = options;
|
|
383
|
+
let lastError = null;
|
|
384
|
+
let currentDelay = delaySeconds;
|
|
385
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
386
|
+
try {
|
|
387
|
+
return await fn();
|
|
388
|
+
} catch (e) {
|
|
389
|
+
lastError = e instanceof Error ? e : new Error(String(e));
|
|
390
|
+
if (attempt === maxAttempts) {
|
|
391
|
+
break;
|
|
392
|
+
}
|
|
393
|
+
let sleepTime = currentDelay;
|
|
394
|
+
if (jitter) {
|
|
395
|
+
sleepTime = Math.random() * currentDelay * 2;
|
|
396
|
+
}
|
|
397
|
+
await sleep(sleepTime * 1e3);
|
|
398
|
+
currentDelay *= backoff;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
throw lastError ?? new Error("All retry attempts failed");
|
|
402
|
+
}
|
|
403
|
+
function sleep(ms) {
|
|
404
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
405
|
+
}
|
|
406
|
+
function withRetry(fn, options = {}) {
|
|
407
|
+
return (async (...args) => {
|
|
408
|
+
return retryAsync(() => fn(...args), options);
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// src/utils/deprecated.ts
|
|
413
|
+
function deprecated(reason = "") {
|
|
414
|
+
return function(target, propertyKey, descriptor) {
|
|
415
|
+
const originalMethod = descriptor.value;
|
|
416
|
+
descriptor.value = function(...args) {
|
|
417
|
+
console.warn(
|
|
418
|
+
`${String(propertyKey)} is deprecated. ${reason}`
|
|
419
|
+
);
|
|
420
|
+
return originalMethod.apply(this, args);
|
|
421
|
+
};
|
|
422
|
+
return descriptor;
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
function deprecatedClass(reason = "") {
|
|
426
|
+
return function(constructor) {
|
|
427
|
+
return class extends constructor {
|
|
428
|
+
constructor(...args) {
|
|
429
|
+
console.warn(`${constructor.name} is deprecated. ${reason}`);
|
|
430
|
+
super(...args);
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// src/utils/system.ts
|
|
437
|
+
function isNode() {
|
|
438
|
+
return typeof process !== "undefined" && process.versions != null && process.versions.node != null;
|
|
439
|
+
}
|
|
440
|
+
function isBrowser() {
|
|
441
|
+
return typeof globalThis !== "undefined" && "window" in globalThis && typeof globalThis.window !== "undefined";
|
|
442
|
+
}
|
|
443
|
+
function getEnv(key, defaultValue) {
|
|
444
|
+
if (isNode()) {
|
|
445
|
+
return process.env[key] ?? defaultValue;
|
|
446
|
+
}
|
|
447
|
+
return defaultValue;
|
|
448
|
+
}
|
|
449
|
+
function getRequiredEnv(key) {
|
|
450
|
+
const value = getEnv(key);
|
|
451
|
+
if (value === void 0) {
|
|
452
|
+
throw new Error(`Required environment variable ${key} is not set`);
|
|
453
|
+
}
|
|
454
|
+
return value;
|
|
455
|
+
}
|
|
456
|
+
function isEnvSet(key) {
|
|
457
|
+
if (isNode()) {
|
|
458
|
+
return key in process.env;
|
|
459
|
+
}
|
|
460
|
+
return false;
|
|
461
|
+
}
|
|
462
|
+
var envVars = {
|
|
463
|
+
// Logging
|
|
464
|
+
get ROCK_LOGGING_PATH() {
|
|
465
|
+
return getEnv("ROCK_LOGGING_PATH");
|
|
466
|
+
},
|
|
467
|
+
get ROCK_LOGGING_FILE_NAME() {
|
|
468
|
+
return getEnv("ROCK_LOGGING_FILE_NAME", "rocklet.log");
|
|
469
|
+
},
|
|
470
|
+
get ROCK_LOGGING_LEVEL() {
|
|
471
|
+
return getEnv("ROCK_LOGGING_LEVEL", "INFO");
|
|
472
|
+
},
|
|
473
|
+
// Base URLs
|
|
474
|
+
get ROCK_BASE_URL() {
|
|
475
|
+
return getEnv("ROCK_BASE_URL", "http://localhost:8080");
|
|
476
|
+
},
|
|
477
|
+
get ROCK_SANDBOX_STARTUP_TIMEOUT_SECONDS() {
|
|
478
|
+
return parseInt(getEnv("ROCK_SANDBOX_STARTUP_TIMEOUT_SECONDS", "180"), 10);
|
|
479
|
+
},
|
|
480
|
+
// EnvHub
|
|
481
|
+
get ROCK_ENVHUB_BASE_URL() {
|
|
482
|
+
return getEnv("ROCK_ENVHUB_BASE_URL", "http://localhost:8081");
|
|
483
|
+
},
|
|
484
|
+
// Model Service
|
|
485
|
+
get ROCK_MODEL_SERVICE_DATA_DIR() {
|
|
486
|
+
return getEnv("ROCK_MODEL_SERVICE_DATA_DIR", "/data/logs");
|
|
487
|
+
}};
|
|
488
|
+
|
|
489
|
+
// src/envhub/schema.ts
|
|
490
|
+
var EnvHubClientConfigSchema = z.object({
|
|
491
|
+
baseUrl: z.string().default(envVars.ROCK_ENVHUB_BASE_URL)
|
|
492
|
+
});
|
|
493
|
+
var RockEnvInfoSchema = z.object({
|
|
494
|
+
envName: z.string(),
|
|
495
|
+
image: z.string(),
|
|
496
|
+
owner: z.string().default(""),
|
|
497
|
+
createAt: z.string().default(""),
|
|
498
|
+
updateAt: z.string().default(""),
|
|
499
|
+
description: z.string().default(""),
|
|
500
|
+
tags: z.array(z.string()).default([]),
|
|
501
|
+
extraSpec: z.record(z.unknown()).optional()
|
|
502
|
+
});
|
|
503
|
+
function createRockEnvInfo(data) {
|
|
504
|
+
return RockEnvInfoSchema.parse({
|
|
505
|
+
envName: data.env_name ?? data.envName,
|
|
506
|
+
image: data.image,
|
|
507
|
+
owner: data.owner ?? "",
|
|
508
|
+
createAt: data.create_at ?? data.createAt ?? "",
|
|
509
|
+
updateAt: data.update_at ?? data.updateAt ?? "",
|
|
510
|
+
description: data.description ?? "",
|
|
511
|
+
tags: data.tags ?? [],
|
|
512
|
+
extraSpec: data.extra_spec ?? data.extraSpec
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
function rockEnvInfoToDict(env) {
|
|
516
|
+
return {
|
|
517
|
+
env_name: env.envName,
|
|
518
|
+
image: env.image,
|
|
519
|
+
owner: env.owner,
|
|
520
|
+
create_at: env.createAt,
|
|
521
|
+
update_at: env.updateAt,
|
|
522
|
+
description: env.description,
|
|
523
|
+
tags: env.tags,
|
|
524
|
+
extra_spec: env.extraSpec
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// src/envhub/client.ts
|
|
529
|
+
var EnvHubError = class extends Error {
|
|
530
|
+
constructor(message) {
|
|
531
|
+
super(message);
|
|
532
|
+
this.name = "EnvHubError";
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
var EnvHubClient = class {
|
|
536
|
+
config;
|
|
537
|
+
baseUrl;
|
|
538
|
+
headers;
|
|
539
|
+
constructor(config) {
|
|
540
|
+
this.config = EnvHubClientConfigSchema.parse(config ?? {});
|
|
541
|
+
this.baseUrl = this.config.baseUrl;
|
|
542
|
+
this.headers = { "Content-Type": "application/json" };
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Register or update an environment
|
|
546
|
+
*/
|
|
547
|
+
async register(options) {
|
|
548
|
+
const url = `${this.baseUrl}/env/register`;
|
|
549
|
+
const payload = {
|
|
550
|
+
env_name: options.envName,
|
|
551
|
+
image: options.image,
|
|
552
|
+
owner: options.owner ?? "",
|
|
553
|
+
description: options.description ?? "",
|
|
554
|
+
tags: options.tags ?? [],
|
|
555
|
+
extra_spec: options.extraSpec
|
|
556
|
+
};
|
|
557
|
+
try {
|
|
558
|
+
const response = await HttpUtils.post(
|
|
559
|
+
url,
|
|
560
|
+
this.headers,
|
|
561
|
+
payload
|
|
562
|
+
);
|
|
563
|
+
return createRockEnvInfo(response);
|
|
564
|
+
} catch (e) {
|
|
565
|
+
throw new EnvHubError(`Failed to register environment: ${e}`);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Get environment by name
|
|
570
|
+
*/
|
|
571
|
+
async getEnv(envName) {
|
|
572
|
+
const url = `${this.baseUrl}/env/get`;
|
|
573
|
+
const payload = { env_name: envName };
|
|
574
|
+
try {
|
|
575
|
+
const response = await HttpUtils.post(
|
|
576
|
+
url,
|
|
577
|
+
this.headers,
|
|
578
|
+
payload
|
|
579
|
+
);
|
|
580
|
+
return createRockEnvInfo(response);
|
|
581
|
+
} catch (e) {
|
|
582
|
+
throw new EnvHubError(`Failed to get environment ${envName}: ${e}`);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* List environments
|
|
587
|
+
*/
|
|
588
|
+
async listEnvs(options) {
|
|
589
|
+
const url = `${this.baseUrl}/env/list`;
|
|
590
|
+
const payload = {
|
|
591
|
+
owner: options?.owner,
|
|
592
|
+
tags: options?.tags
|
|
593
|
+
};
|
|
594
|
+
try {
|
|
595
|
+
const response = await HttpUtils.post(
|
|
596
|
+
url,
|
|
597
|
+
this.headers,
|
|
598
|
+
payload
|
|
599
|
+
);
|
|
600
|
+
const envsData = response.envs ?? [];
|
|
601
|
+
return envsData.map((envData) => createRockEnvInfo(envData));
|
|
602
|
+
} catch (e) {
|
|
603
|
+
throw new EnvHubError(`Failed to list environments: ${e}`);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Delete environment
|
|
608
|
+
*/
|
|
609
|
+
async deleteEnv(envName) {
|
|
610
|
+
const url = `${this.baseUrl}/env/delete`;
|
|
611
|
+
const payload = { env_name: envName };
|
|
612
|
+
try {
|
|
613
|
+
await HttpUtils.post(url, this.headers, payload);
|
|
614
|
+
return true;
|
|
615
|
+
} catch (e) {
|
|
616
|
+
const errorMessage = e instanceof Error ? e.message : String(e);
|
|
617
|
+
if (errorMessage.includes("404")) {
|
|
618
|
+
return false;
|
|
619
|
+
}
|
|
620
|
+
throw new EnvHubError(`Failed to delete environment ${envName}: ${e}`);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* Health check
|
|
625
|
+
*/
|
|
626
|
+
async healthCheck() {
|
|
627
|
+
const url = `${this.baseUrl}/health`;
|
|
628
|
+
try {
|
|
629
|
+
const response = await HttpUtils.get(
|
|
630
|
+
url,
|
|
631
|
+
this.headers
|
|
632
|
+
);
|
|
633
|
+
return response;
|
|
634
|
+
} catch (e) {
|
|
635
|
+
throw new EnvHubError(`Failed to health check: ${e}`);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
};
|
|
639
|
+
var levels = {
|
|
640
|
+
error: 0,
|
|
641
|
+
warn: 1,
|
|
642
|
+
info: 2,
|
|
643
|
+
http: 3,
|
|
644
|
+
debug: 4
|
|
645
|
+
};
|
|
646
|
+
var colors = {
|
|
647
|
+
error: "red",
|
|
648
|
+
warn: "yellow",
|
|
649
|
+
info: "green",
|
|
650
|
+
http: "magenta",
|
|
651
|
+
debug: "cyan"
|
|
652
|
+
};
|
|
653
|
+
winston.addColors(colors);
|
|
654
|
+
var consoleFormat = winston.format.combine(
|
|
655
|
+
winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss.SSS" }),
|
|
656
|
+
winston.format.colorize({ all: true }),
|
|
657
|
+
winston.format.printf((info) => {
|
|
658
|
+
const { timestamp, level, message, ...meta } = info;
|
|
659
|
+
const metaStr = Object.keys(meta).length ? JSON.stringify(meta) : "";
|
|
660
|
+
return `${timestamp} ${level}: ${message} ${metaStr}`;
|
|
661
|
+
})
|
|
662
|
+
);
|
|
663
|
+
var fileFormat = winston.format.combine(
|
|
664
|
+
winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss.SSS" }),
|
|
665
|
+
winston.format.json()
|
|
666
|
+
);
|
|
667
|
+
function getLogLevel() {
|
|
668
|
+
const level = envVars.ROCK_LOGGING_LEVEL.toUpperCase();
|
|
669
|
+
if (level in levels) {
|
|
670
|
+
return level.toLowerCase();
|
|
671
|
+
}
|
|
672
|
+
return "info";
|
|
673
|
+
}
|
|
674
|
+
var loggerCache = /* @__PURE__ */ new Map();
|
|
675
|
+
function initLogger(name = "rock", fileName) {
|
|
676
|
+
if (loggerCache.has(name)) {
|
|
677
|
+
return loggerCache.get(name);
|
|
678
|
+
}
|
|
679
|
+
const transports = [];
|
|
680
|
+
const logLevel = getLogLevel();
|
|
681
|
+
const logPath = envVars.ROCK_LOGGING_PATH;
|
|
682
|
+
const logFileName = envVars.ROCK_LOGGING_FILE_NAME;
|
|
683
|
+
if (logPath) {
|
|
684
|
+
if (!existsSync(logPath)) {
|
|
685
|
+
mkdirSync(logPath, { recursive: true });
|
|
686
|
+
}
|
|
687
|
+
transports.push(
|
|
688
|
+
new winston.transports.File({
|
|
689
|
+
filename: join(logPath, logFileName),
|
|
690
|
+
format: fileFormat,
|
|
691
|
+
level: logLevel
|
|
692
|
+
})
|
|
693
|
+
);
|
|
694
|
+
} else {
|
|
695
|
+
transports.push(
|
|
696
|
+
new winston.transports.Console({
|
|
697
|
+
format: consoleFormat,
|
|
698
|
+
level: logLevel
|
|
699
|
+
})
|
|
700
|
+
);
|
|
701
|
+
}
|
|
702
|
+
const logger11 = winston.createLogger({
|
|
703
|
+
levels,
|
|
704
|
+
defaultMeta: { service: name },
|
|
705
|
+
transports
|
|
706
|
+
});
|
|
707
|
+
loggerCache.set(name, logger11);
|
|
708
|
+
return logger11;
|
|
709
|
+
}
|
|
710
|
+
initLogger("rock");
|
|
711
|
+
|
|
712
|
+
// src/envs/rock_env.ts
|
|
713
|
+
var logger = initLogger("rock.envs");
|
|
714
|
+
var RockEnv = class {
|
|
715
|
+
envId;
|
|
716
|
+
sandboxId = null;
|
|
717
|
+
isClosed = false;
|
|
718
|
+
client;
|
|
719
|
+
constructor(config) {
|
|
720
|
+
this.envId = config.envId;
|
|
721
|
+
this.client = axios3.create({
|
|
722
|
+
baseURL: envVars.ROCK_BASE_URL,
|
|
723
|
+
timeout: 3e5,
|
|
724
|
+
headers: { "Content-Type": "application/json" }
|
|
725
|
+
});
|
|
726
|
+
try {
|
|
727
|
+
this.initializeEnvironment();
|
|
728
|
+
} catch (e) {
|
|
729
|
+
throw new Error(`Failed to initialize environment: ${e}`);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
/**
|
|
733
|
+
* Initialize environment instance
|
|
734
|
+
*/
|
|
735
|
+
initializeEnvironment() {
|
|
736
|
+
logger.debug(`Initializing environment: ${this.envId}`);
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* Execute an action step
|
|
740
|
+
*
|
|
741
|
+
* @param action - Action ID to execute
|
|
742
|
+
* @returns Tuple containing observation, reward, terminated, truncated, info
|
|
743
|
+
*/
|
|
744
|
+
async step(action) {
|
|
745
|
+
this.ensureNotClosed();
|
|
746
|
+
const params = {
|
|
747
|
+
sandbox_id: this.sandboxId,
|
|
748
|
+
action
|
|
749
|
+
};
|
|
750
|
+
try {
|
|
751
|
+
const response = await this.client.post(
|
|
752
|
+
"/apis/v1/envs/gem/step",
|
|
753
|
+
params
|
|
754
|
+
);
|
|
755
|
+
return this.parseStepResult(response.data);
|
|
756
|
+
} catch (e) {
|
|
757
|
+
throw new Error(`Failed to execute step with action ${action}: ${e}`);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* Reset environment to initial state
|
|
762
|
+
*
|
|
763
|
+
* @param seed - Optional random seed
|
|
764
|
+
* @returns Tuple containing initial observation and info
|
|
765
|
+
*/
|
|
766
|
+
async reset(seed) {
|
|
767
|
+
this.ensureNotClosed();
|
|
768
|
+
const params = { sandbox_id: this.sandboxId };
|
|
769
|
+
if (seed !== void 0) {
|
|
770
|
+
params.seed = seed;
|
|
771
|
+
}
|
|
772
|
+
try {
|
|
773
|
+
const response = await this.client.post(
|
|
774
|
+
"/apis/v1/envs/gem/reset",
|
|
775
|
+
params
|
|
776
|
+
);
|
|
777
|
+
return this.parseResetResult(response.data);
|
|
778
|
+
} catch (e) {
|
|
779
|
+
throw new Error(`Failed to reset environment: ${e}`);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
/**
|
|
783
|
+
* Close environment and clean up resources
|
|
784
|
+
*/
|
|
785
|
+
async close() {
|
|
786
|
+
if (this.isClosed || !this.sandboxId) {
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
try {
|
|
790
|
+
await this.client.post("/apis/v1/envs/gem/close", {
|
|
791
|
+
sandbox_id: this.sandboxId
|
|
792
|
+
});
|
|
793
|
+
} catch (e) {
|
|
794
|
+
throw new Error(`Failed to close environment: ${e}`);
|
|
795
|
+
} finally {
|
|
796
|
+
this.isClosed = true;
|
|
797
|
+
this.sandboxId = null;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
801
|
+
* Parse step result from API response
|
|
802
|
+
*/
|
|
803
|
+
parseStepResult(data) {
|
|
804
|
+
return [
|
|
805
|
+
data.observation,
|
|
806
|
+
data.reward,
|
|
807
|
+
data.terminated,
|
|
808
|
+
data.truncated,
|
|
809
|
+
data.info
|
|
810
|
+
];
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* Parse reset result from API response
|
|
814
|
+
*/
|
|
815
|
+
parseResetResult(data) {
|
|
816
|
+
return [data.observation, data.info];
|
|
817
|
+
}
|
|
818
|
+
/**
|
|
819
|
+
* Ensure environment is not closed
|
|
820
|
+
*/
|
|
821
|
+
ensureNotClosed() {
|
|
822
|
+
if (this.isClosed) {
|
|
823
|
+
throw new Error("Environment is closed");
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
};
|
|
827
|
+
|
|
828
|
+
// src/envs/registration.ts
|
|
829
|
+
function make(envId, options) {
|
|
830
|
+
return new RockEnv({ envId, ...options });
|
|
831
|
+
}
|
|
832
|
+
var BaseConfigSchema = z.object({
|
|
833
|
+
baseUrl: z.string().default(envVars.ROCK_BASE_URL),
|
|
834
|
+
xrlAuthorization: z.string().optional(),
|
|
835
|
+
extraHeaders: z.record(z.string()).default({})
|
|
836
|
+
});
|
|
837
|
+
var SandboxConfigSchema = BaseConfigSchema.extend({
|
|
838
|
+
image: z.string().default("python:3.11"),
|
|
839
|
+
autoClearSeconds: z.number().default(300),
|
|
840
|
+
routeKey: z.string().optional(),
|
|
841
|
+
startupTimeout: z.number().default(envVars.ROCK_SANDBOX_STARTUP_TIMEOUT_SECONDS),
|
|
842
|
+
memory: z.string().default("8g"),
|
|
843
|
+
cpus: z.number().default(2),
|
|
844
|
+
userId: z.string().optional(),
|
|
845
|
+
experimentId: z.string().optional(),
|
|
846
|
+
cluster: z.string().default("zb"),
|
|
847
|
+
namespace: z.string().optional()
|
|
848
|
+
});
|
|
849
|
+
var SandboxGroupConfigSchema = SandboxConfigSchema.extend({
|
|
850
|
+
size: z.number().default(2),
|
|
851
|
+
startConcurrency: z.number().default(2),
|
|
852
|
+
startRetryTimes: z.number().default(3)
|
|
853
|
+
});
|
|
854
|
+
function createSandboxConfig(config) {
|
|
855
|
+
return SandboxConfigSchema.parse(config ?? {});
|
|
856
|
+
}
|
|
857
|
+
function createSandboxGroupConfig(config) {
|
|
858
|
+
return SandboxGroupConfigSchema.parse(config ?? {});
|
|
859
|
+
}
|
|
860
|
+
var logger2 = initLogger("rock.sandbox.deploy");
|
|
861
|
+
var Deploy = class {
|
|
862
|
+
sandbox;
|
|
863
|
+
workingDir = null;
|
|
864
|
+
constructor(sandbox) {
|
|
865
|
+
this.sandbox = sandbox;
|
|
866
|
+
}
|
|
867
|
+
/**
|
|
868
|
+
* Get the current working directory
|
|
869
|
+
*/
|
|
870
|
+
getWorkingDir() {
|
|
871
|
+
return this.workingDir;
|
|
872
|
+
}
|
|
873
|
+
/**
|
|
874
|
+
* Deploy local directory to sandbox
|
|
875
|
+
*
|
|
876
|
+
* @param localPath - Local directory path
|
|
877
|
+
* @param targetPath - Target path in sandbox (optional)
|
|
878
|
+
* @returns The target path in sandbox
|
|
879
|
+
*/
|
|
880
|
+
async deployWorkingDir(localPath, targetPath) {
|
|
881
|
+
const localAbs = resolve(localPath);
|
|
882
|
+
if (!existsSync(localAbs)) {
|
|
883
|
+
throw new Error(`local_path not found: ${localAbs}`);
|
|
884
|
+
}
|
|
885
|
+
const stats = statSync(localAbs);
|
|
886
|
+
if (!stats.isDirectory()) {
|
|
887
|
+
throw new Error(`local_path must be a directory: ${localAbs}`);
|
|
888
|
+
}
|
|
889
|
+
const target = targetPath ?? `/tmp/rock_workdir_${randomUUID().replace(/-/g, "")}`;
|
|
890
|
+
const sandboxId = this.sandbox.getSandboxId();
|
|
891
|
+
logger2.info(`[${sandboxId}] Deploying working_dir: ${localAbs} -> ${target}`);
|
|
892
|
+
const uploadResult = await this.sandbox.getFs().uploadDir(localAbs, target);
|
|
893
|
+
if (uploadResult.exitCode !== 0) {
|
|
894
|
+
throw new Error(`Failed to upload directory: ${uploadResult.failureReason}`);
|
|
895
|
+
}
|
|
896
|
+
this.workingDir = target;
|
|
897
|
+
logger2.info(`[${sandboxId}] working_dir deployed: ${target}`);
|
|
898
|
+
return target;
|
|
899
|
+
}
|
|
900
|
+
/**
|
|
901
|
+
* Format command template supporting ${} and <<>> syntax
|
|
902
|
+
*
|
|
903
|
+
* @param template - Template string with placeholders
|
|
904
|
+
* @param kwargs - Additional substitution variables
|
|
905
|
+
* @returns Formatted string
|
|
906
|
+
*/
|
|
907
|
+
format(template, kwargs = {}) {
|
|
908
|
+
const subs = {
|
|
909
|
+
...kwargs,
|
|
910
|
+
...this.workingDir ? { working_dir: this.workingDir } : {}
|
|
911
|
+
};
|
|
912
|
+
let result = template;
|
|
913
|
+
for (const key of Object.keys(subs)) {
|
|
914
|
+
result = result.replace(new RegExp(`<<${key}>>`, "g"), `\${${key}}`);
|
|
915
|
+
}
|
|
916
|
+
for (const [key, value] of Object.entries(subs)) {
|
|
917
|
+
if (value !== void 0) {
|
|
918
|
+
result = result.replace(new RegExp(`\\$\\{${key}\\}`, "g"), value);
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
return result;
|
|
922
|
+
}
|
|
923
|
+
};
|
|
924
|
+
|
|
925
|
+
// src/sandbox/file_system.ts
|
|
926
|
+
var logger3 = initLogger("rock.sandbox.fs");
|
|
927
|
+
var FileSystem = class {
|
|
928
|
+
sandbox;
|
|
929
|
+
constructor(sandbox) {
|
|
930
|
+
this.sandbox = sandbox;
|
|
931
|
+
}
|
|
932
|
+
};
|
|
933
|
+
var LinuxFileSystem = class extends FileSystem {
|
|
934
|
+
constructor(sandbox) {
|
|
935
|
+
super(sandbox);
|
|
936
|
+
}
|
|
937
|
+
async chown(request) {
|
|
938
|
+
const { paths, recursive, remoteUser } = request;
|
|
939
|
+
if (!paths || paths.length === 0) {
|
|
940
|
+
throw new Error("paths is empty");
|
|
941
|
+
}
|
|
942
|
+
const command = ["chown"];
|
|
943
|
+
if (recursive) {
|
|
944
|
+
command.push("-R");
|
|
945
|
+
}
|
|
946
|
+
command.push(`${remoteUser}:${remoteUser}`, ...paths);
|
|
947
|
+
logger3.info(`chown command: ${command.join(" ")}`);
|
|
948
|
+
const response = await this.sandbox.execute({ command, timeout: 300 });
|
|
949
|
+
if (response.exitCode !== 0) {
|
|
950
|
+
return { success: false, message: JSON.stringify(response) };
|
|
951
|
+
}
|
|
952
|
+
return { success: true, message: JSON.stringify(response) };
|
|
953
|
+
}
|
|
954
|
+
async chmod(request) {
|
|
955
|
+
const { paths, recursive, mode } = request;
|
|
956
|
+
if (!paths || paths.length === 0) {
|
|
957
|
+
throw new Error("paths is empty");
|
|
958
|
+
}
|
|
959
|
+
const command = ["chmod"];
|
|
960
|
+
if (recursive) {
|
|
961
|
+
command.push("-R");
|
|
962
|
+
}
|
|
963
|
+
command.push(mode, ...paths);
|
|
964
|
+
logger3.info(`chmod command: ${command.join(" ")}`);
|
|
965
|
+
const response = await this.sandbox.execute({ command, timeout: 300 });
|
|
966
|
+
if (response.exitCode !== 0) {
|
|
967
|
+
return { success: false, message: JSON.stringify(response) };
|
|
968
|
+
}
|
|
969
|
+
return { success: true, message: JSON.stringify(response) };
|
|
970
|
+
}
|
|
971
|
+
async uploadDir(sourceDir, targetDir, extractTimeout = 600) {
|
|
972
|
+
logger3.info(`uploadDir: ${sourceDir} -> ${targetDir}`);
|
|
973
|
+
return {
|
|
974
|
+
output: `uploaded ${sourceDir} -> ${targetDir}`,
|
|
975
|
+
exitCode: 0,
|
|
976
|
+
failureReason: "",
|
|
977
|
+
expectString: ""
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
};
|
|
981
|
+
|
|
982
|
+
// src/sandbox/types.ts
|
|
983
|
+
var SpeedupType = /* @__PURE__ */ ((SpeedupType2) => {
|
|
984
|
+
SpeedupType2["APT"] = "apt";
|
|
985
|
+
SpeedupType2["PIP"] = "pip";
|
|
986
|
+
SpeedupType2["GITHUB"] = "github";
|
|
987
|
+
return SpeedupType2;
|
|
988
|
+
})(SpeedupType || {});
|
|
989
|
+
|
|
990
|
+
// src/sandbox/network.ts
|
|
991
|
+
var logger4 = initLogger("rock.sandbox.network");
|
|
992
|
+
var Network = class {
|
|
993
|
+
sandbox;
|
|
994
|
+
constructor(sandbox) {
|
|
995
|
+
this.sandbox = sandbox;
|
|
996
|
+
}
|
|
997
|
+
/**
|
|
998
|
+
* Configure acceleration for package managers or network resources
|
|
999
|
+
*
|
|
1000
|
+
* @param speedupType - Type of speedup configuration
|
|
1001
|
+
* @param speedupValue - Speedup value (mirror URL or IP address)
|
|
1002
|
+
* @param timeout - Execution timeout in seconds
|
|
1003
|
+
* @returns Observation with execution result
|
|
1004
|
+
*/
|
|
1005
|
+
async speedup(speedupType, speedupValue, timeout = 300) {
|
|
1006
|
+
const sandboxId = this.sandbox.getSandboxId();
|
|
1007
|
+
logger4.info(
|
|
1008
|
+
`[${sandboxId}] Configuring ${speedupType} speedup: ${speedupValue}`
|
|
1009
|
+
);
|
|
1010
|
+
let command;
|
|
1011
|
+
switch (speedupType) {
|
|
1012
|
+
case "apt" /* APT */:
|
|
1013
|
+
command = this.buildAptSpeedupCommand(speedupValue);
|
|
1014
|
+
break;
|
|
1015
|
+
case "pip" /* PIP */:
|
|
1016
|
+
command = this.buildPipSpeedupCommand(speedupValue);
|
|
1017
|
+
break;
|
|
1018
|
+
case "github" /* GITHUB */:
|
|
1019
|
+
command = this.buildGithubSpeedupCommand(speedupValue);
|
|
1020
|
+
break;
|
|
1021
|
+
default:
|
|
1022
|
+
throw new Error(`Unsupported speedup type: ${speedupType}`);
|
|
1023
|
+
}
|
|
1024
|
+
const result = await this.sandbox.arun(command, {
|
|
1025
|
+
mode: "nohup",
|
|
1026
|
+
waitTimeout: timeout
|
|
1027
|
+
});
|
|
1028
|
+
return result;
|
|
1029
|
+
}
|
|
1030
|
+
buildAptSpeedupCommand(mirrorUrl) {
|
|
1031
|
+
return `cat > /etc/apt/sources.list << 'EOF'
|
|
1032
|
+
deb ${mirrorUrl} $(lsb_release -cs) main restricted universe multiverse
|
|
1033
|
+
deb ${mirrorUrl} $(lsb_release -cs)-updates main restricted universe multiverse
|
|
1034
|
+
deb ${mirrorUrl} $(lsb_release -cs)-backports main restricted universe multiverse
|
|
1035
|
+
deb ${mirrorUrl} $(lsb_release -cs)-security main restricted universe multiverse
|
|
1036
|
+
EOF
|
|
1037
|
+
apt-get update`;
|
|
1038
|
+
}
|
|
1039
|
+
buildPipSpeedupCommand(mirrorUrl) {
|
|
1040
|
+
const safeUrl = mirrorUrl.replace(/'/g, "'\\''");
|
|
1041
|
+
return `mkdir -p ~/.pip && cat > ~/.pip/pip.conf << 'EOF'
|
|
1042
|
+
[global]
|
|
1043
|
+
index-url = ${safeUrl}
|
|
1044
|
+
trusted-host = $(echo ${safeUrl} | sed 's|https\\?://||' | cut -d'/' -f1)
|
|
1045
|
+
EOF`;
|
|
1046
|
+
}
|
|
1047
|
+
buildGithubSpeedupCommand(ipAddress) {
|
|
1048
|
+
return `echo "${ipAddress} github.com" >> /etc/hosts`;
|
|
1049
|
+
}
|
|
1050
|
+
};
|
|
1051
|
+
|
|
1052
|
+
// src/sandbox/process.ts
|
|
1053
|
+
var logger5 = initLogger("rock.sandbox.process");
|
|
1054
|
+
var Process = class {
|
|
1055
|
+
sandbox;
|
|
1056
|
+
constructor(sandbox) {
|
|
1057
|
+
this.sandbox = sandbox;
|
|
1058
|
+
}
|
|
1059
|
+
/**
|
|
1060
|
+
* Execute a script in the sandbox
|
|
1061
|
+
*
|
|
1062
|
+
* @param scriptContent - The script content to execute
|
|
1063
|
+
* @param scriptName - Optional custom script name
|
|
1064
|
+
* @param waitTimeout - Maximum time to wait for script completion
|
|
1065
|
+
* @param waitInterval - Interval between process checks
|
|
1066
|
+
* @param cleanup - Whether to delete the script file after execution
|
|
1067
|
+
* @returns Observation with execution result
|
|
1068
|
+
*/
|
|
1069
|
+
async executeScript(options) {
|
|
1070
|
+
const {
|
|
1071
|
+
scriptContent,
|
|
1072
|
+
scriptName,
|
|
1073
|
+
waitTimeout = 300,
|
|
1074
|
+
cleanup = true
|
|
1075
|
+
} = options;
|
|
1076
|
+
const sandboxId = this.sandbox.getSandboxId();
|
|
1077
|
+
const timestamp = Date.now();
|
|
1078
|
+
const name = scriptName ?? `script_${timestamp}.sh`;
|
|
1079
|
+
const scriptPath = `/tmp/${name}`;
|
|
1080
|
+
try {
|
|
1081
|
+
logger5.info(`[${sandboxId}] Uploading script to ${scriptPath}`);
|
|
1082
|
+
const writeResult = await this.sandbox.write_file({
|
|
1083
|
+
content: scriptContent,
|
|
1084
|
+
path: scriptPath
|
|
1085
|
+
});
|
|
1086
|
+
if (!writeResult.success) {
|
|
1087
|
+
const errorMsg = `Failed to upload script: ${writeResult.message}`;
|
|
1088
|
+
logger5.error(errorMsg);
|
|
1089
|
+
return { output: errorMsg, exitCode: 1, failureReason: "Script upload failed", expectString: "" };
|
|
1090
|
+
}
|
|
1091
|
+
logger5.info(`[${sandboxId}] Executing script: ${scriptPath} (timeout=${waitTimeout}s)`);
|
|
1092
|
+
const result = await this.sandbox.arun(`bash ${scriptPath}`, {
|
|
1093
|
+
mode: "nohup",
|
|
1094
|
+
waitTimeout
|
|
1095
|
+
});
|
|
1096
|
+
return result;
|
|
1097
|
+
} catch (e) {
|
|
1098
|
+
const errorMsg = `Script execution failed: ${e}`;
|
|
1099
|
+
logger5.error(errorMsg);
|
|
1100
|
+
return { output: errorMsg, exitCode: 1, failureReason: errorMsg, expectString: "" };
|
|
1101
|
+
} finally {
|
|
1102
|
+
if (cleanup) {
|
|
1103
|
+
try {
|
|
1104
|
+
logger5.info(`[${sandboxId}] Cleaning up script: ${scriptPath}`);
|
|
1105
|
+
await this.sandbox.execute({ command: ["rm", "-f", scriptPath], timeout: 30 });
|
|
1106
|
+
} catch (e) {
|
|
1107
|
+
logger5.warn(`Failed to cleanup script ${scriptPath}: ${e}`);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
};
|
|
1113
|
+
|
|
1114
|
+
// src/sandbox/remote_user.ts
|
|
1115
|
+
var logger6 = initLogger("rock.sandbox.user");
|
|
1116
|
+
var RemoteUser = class {
|
|
1117
|
+
sandbox;
|
|
1118
|
+
currentUser = "root";
|
|
1119
|
+
constructor(sandbox) {
|
|
1120
|
+
this.sandbox = sandbox;
|
|
1121
|
+
}
|
|
1122
|
+
getCurrentUser() {
|
|
1123
|
+
return this.currentUser;
|
|
1124
|
+
}
|
|
1125
|
+
};
|
|
1126
|
+
var LinuxRemoteUser = class extends RemoteUser {
|
|
1127
|
+
constructor(sandbox) {
|
|
1128
|
+
super(sandbox);
|
|
1129
|
+
}
|
|
1130
|
+
async createRemoteUser(userName) {
|
|
1131
|
+
try {
|
|
1132
|
+
if (await this.isUserExist(userName)) {
|
|
1133
|
+
return true;
|
|
1134
|
+
}
|
|
1135
|
+
const response = await this.sandbox.execute({
|
|
1136
|
+
command: ["useradd", "-m", "-s", "/bin/bash", userName],
|
|
1137
|
+
timeout: 30
|
|
1138
|
+
});
|
|
1139
|
+
logger6.info(`user add execute response: ${JSON.stringify(response)}`);
|
|
1140
|
+
if (response.exitCode !== 0) {
|
|
1141
|
+
return false;
|
|
1142
|
+
}
|
|
1143
|
+
return true;
|
|
1144
|
+
} catch (e) {
|
|
1145
|
+
logger6.error("create_remote_user failed", e);
|
|
1146
|
+
throw e;
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
async isUserExist(userName) {
|
|
1150
|
+
try {
|
|
1151
|
+
const response = await this.sandbox.execute({
|
|
1152
|
+
command: ["id", userName],
|
|
1153
|
+
timeout: 30
|
|
1154
|
+
});
|
|
1155
|
+
if (response.exitCode === 0) {
|
|
1156
|
+
logger6.info(`user ${userName} already exists`);
|
|
1157
|
+
return true;
|
|
1158
|
+
}
|
|
1159
|
+
return false;
|
|
1160
|
+
} catch (e) {
|
|
1161
|
+
logger6.info(`is_user_exist exception: ${e}`);
|
|
1162
|
+
throw e;
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
};
|
|
1166
|
+
|
|
1167
|
+
// src/sandbox/utils.ts
|
|
1168
|
+
var logger7 = initLogger("rock.sandbox.utils");
|
|
1169
|
+
function getCallerLoggerName() {
|
|
1170
|
+
const stack = new Error().stack;
|
|
1171
|
+
if (!stack) return "unknown";
|
|
1172
|
+
const lines = stack.split("\n");
|
|
1173
|
+
for (let i = 2; i < lines.length; i++) {
|
|
1174
|
+
const line = lines[i];
|
|
1175
|
+
if (line && !line.includes("utils.ts")) {
|
|
1176
|
+
const match = line.match(/at\s+(?:(?:async\s+)?(?:\w+\.)?(\w+)|(\w+))/);
|
|
1177
|
+
if (match) {
|
|
1178
|
+
return match[1] ?? match[2] ?? "unknown";
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
return "unknown";
|
|
1183
|
+
}
|
|
1184
|
+
function withTimeLogging(operationName) {
|
|
1185
|
+
return function(target, propertyKey, descriptor) {
|
|
1186
|
+
const originalMethod = descriptor.value;
|
|
1187
|
+
descriptor.value = async function(...args) {
|
|
1188
|
+
const startTime = Date.now();
|
|
1189
|
+
const name = getCallerLoggerName();
|
|
1190
|
+
const log = initLogger(name);
|
|
1191
|
+
log.debug(`${operationName} started`);
|
|
1192
|
+
try {
|
|
1193
|
+
const result = await originalMethod.apply(this, args);
|
|
1194
|
+
const elapsed = Date.now() - startTime;
|
|
1195
|
+
log.info(`${operationName} completed (elapsed: ${elapsed / 1e3}s)`);
|
|
1196
|
+
return result;
|
|
1197
|
+
} catch (e) {
|
|
1198
|
+
const elapsed = Date.now() - startTime;
|
|
1199
|
+
log.error(`${operationName} failed: ${e} (elapsed: ${elapsed / 1e3}s)`);
|
|
1200
|
+
throw e;
|
|
1201
|
+
}
|
|
1202
|
+
};
|
|
1203
|
+
return descriptor;
|
|
1204
|
+
};
|
|
1205
|
+
}
|
|
1206
|
+
async function arunWithRetry(sandbox, cmd, session, mode, options = {}) {
|
|
1207
|
+
const {
|
|
1208
|
+
waitTimeout = 300,
|
|
1209
|
+
waitInterval = 10,
|
|
1210
|
+
maxAttempts = 3,
|
|
1211
|
+
errorMsg = "Command failed"
|
|
1212
|
+
} = options;
|
|
1213
|
+
let lastError = null;
|
|
1214
|
+
let currentDelay = 5e3;
|
|
1215
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
1216
|
+
try {
|
|
1217
|
+
const result = await sandbox.arun(cmd, {
|
|
1218
|
+
session,
|
|
1219
|
+
mode,
|
|
1220
|
+
waitTimeout,
|
|
1221
|
+
waitInterval
|
|
1222
|
+
});
|
|
1223
|
+
if (result.exitCode !== 0) {
|
|
1224
|
+
throw new Error(
|
|
1225
|
+
`${errorMsg} with exit code: ${result.exitCode}, output: ${result.output}`
|
|
1226
|
+
);
|
|
1227
|
+
}
|
|
1228
|
+
return result;
|
|
1229
|
+
} catch (e) {
|
|
1230
|
+
lastError = e instanceof Error ? e : new Error(String(e));
|
|
1231
|
+
logger7.warn(`Attempt ${attempt}/${maxAttempts} failed: ${lastError.message}`);
|
|
1232
|
+
if (attempt < maxAttempts) {
|
|
1233
|
+
await sleep(currentDelay);
|
|
1234
|
+
currentDelay *= 2;
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
throw lastError ?? new Error(`${errorMsg}: all attempts failed`);
|
|
1239
|
+
}
|
|
1240
|
+
function extractNohupPid(output) {
|
|
1241
|
+
const PID_PREFIX2 = "__ROCK_PID_START__";
|
|
1242
|
+
const PID_SUFFIX2 = "__ROCK_PID_END__";
|
|
1243
|
+
const pattern = new RegExp(`${PID_PREFIX2}(\\d+)${PID_SUFFIX2}`);
|
|
1244
|
+
const match = output.match(pattern);
|
|
1245
|
+
if (match?.[1]) {
|
|
1246
|
+
return parseInt(match[1], 10);
|
|
1247
|
+
}
|
|
1248
|
+
return null;
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
// src/sandbox/client.ts
|
|
1252
|
+
var logger8 = initLogger("rock.sandbox");
|
|
1253
|
+
var AbstractSandbox = class {
|
|
1254
|
+
};
|
|
1255
|
+
var Sandbox = class extends AbstractSandbox {
|
|
1256
|
+
config;
|
|
1257
|
+
url;
|
|
1258
|
+
routeKey;
|
|
1259
|
+
sandboxId = null;
|
|
1260
|
+
hostName = null;
|
|
1261
|
+
hostIp = null;
|
|
1262
|
+
cluster;
|
|
1263
|
+
// Sub-components
|
|
1264
|
+
deploy;
|
|
1265
|
+
fs;
|
|
1266
|
+
network;
|
|
1267
|
+
process;
|
|
1268
|
+
remoteUser;
|
|
1269
|
+
constructor(config = {}) {
|
|
1270
|
+
super();
|
|
1271
|
+
this.config = createSandboxConfig(config);
|
|
1272
|
+
this.url = `${this.config.baseUrl}/apis/envs/sandbox/v1`;
|
|
1273
|
+
this.routeKey = this.config.routeKey ?? randomUUID().replace(/-/g, "");
|
|
1274
|
+
this.cluster = this.config.cluster;
|
|
1275
|
+
this.deploy = new Deploy(this);
|
|
1276
|
+
this.fs = new LinuxFileSystem(this);
|
|
1277
|
+
this.network = new Network(this);
|
|
1278
|
+
this.process = new Process(this);
|
|
1279
|
+
this.remoteUser = new LinuxRemoteUser(this);
|
|
1280
|
+
}
|
|
1281
|
+
// Getters
|
|
1282
|
+
getSandboxId() {
|
|
1283
|
+
if (!this.sandboxId) {
|
|
1284
|
+
throw new Error("Sandbox not started");
|
|
1285
|
+
}
|
|
1286
|
+
return this.sandboxId;
|
|
1287
|
+
}
|
|
1288
|
+
getHostName() {
|
|
1289
|
+
return this.hostName;
|
|
1290
|
+
}
|
|
1291
|
+
getHostIp() {
|
|
1292
|
+
return this.hostIp;
|
|
1293
|
+
}
|
|
1294
|
+
getCluster() {
|
|
1295
|
+
return this.cluster;
|
|
1296
|
+
}
|
|
1297
|
+
getUrl() {
|
|
1298
|
+
return this.url;
|
|
1299
|
+
}
|
|
1300
|
+
getFs() {
|
|
1301
|
+
return this.fs;
|
|
1302
|
+
}
|
|
1303
|
+
getNetwork() {
|
|
1304
|
+
return this.network;
|
|
1305
|
+
}
|
|
1306
|
+
getProcess() {
|
|
1307
|
+
return this.process;
|
|
1308
|
+
}
|
|
1309
|
+
getRemoteUser() {
|
|
1310
|
+
return this.remoteUser;
|
|
1311
|
+
}
|
|
1312
|
+
getDeploy() {
|
|
1313
|
+
return this.deploy;
|
|
1314
|
+
}
|
|
1315
|
+
getConfig() {
|
|
1316
|
+
return this.config;
|
|
1317
|
+
}
|
|
1318
|
+
// Build headers
|
|
1319
|
+
buildHeaders() {
|
|
1320
|
+
const headers = {
|
|
1321
|
+
"ROUTE-KEY": this.routeKey,
|
|
1322
|
+
"X-Cluster": this.cluster
|
|
1323
|
+
};
|
|
1324
|
+
if (this.config.extraHeaders) {
|
|
1325
|
+
Object.assign(headers, this.config.extraHeaders);
|
|
1326
|
+
}
|
|
1327
|
+
this.addUserDefinedTags(headers);
|
|
1328
|
+
return headers;
|
|
1329
|
+
}
|
|
1330
|
+
addUserDefinedTags(headers) {
|
|
1331
|
+
if (this.config.userId) {
|
|
1332
|
+
headers["X-User-Id"] = this.config.userId;
|
|
1333
|
+
}
|
|
1334
|
+
if (this.config.experimentId) {
|
|
1335
|
+
headers["X-Experiment-Id"] = this.config.experimentId;
|
|
1336
|
+
}
|
|
1337
|
+
if (this.config.namespace) {
|
|
1338
|
+
headers["X-Namespace"] = this.config.namespace;
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
// Lifecycle methods
|
|
1342
|
+
async start() {
|
|
1343
|
+
const url = `${this.url}/start_async`;
|
|
1344
|
+
const headers = this.buildHeaders();
|
|
1345
|
+
const data = {
|
|
1346
|
+
image: this.config.image,
|
|
1347
|
+
auto_clear_time: this.config.autoClearSeconds / 60,
|
|
1348
|
+
auto_clear_time_minutes: this.config.autoClearSeconds / 60,
|
|
1349
|
+
startup_timeout: this.config.startupTimeout,
|
|
1350
|
+
memory: this.config.memory,
|
|
1351
|
+
cpus: this.config.cpus
|
|
1352
|
+
};
|
|
1353
|
+
try {
|
|
1354
|
+
const response = await HttpUtils.post(
|
|
1355
|
+
url,
|
|
1356
|
+
headers,
|
|
1357
|
+
data
|
|
1358
|
+
);
|
|
1359
|
+
logger8.debug(`Start sandbox response: ${JSON.stringify(response)}`);
|
|
1360
|
+
if (response.status !== "Success") {
|
|
1361
|
+
throw new Error(`Failed to start sandbox: ${JSON.stringify(response)}`);
|
|
1362
|
+
}
|
|
1363
|
+
this.sandboxId = response.result?.sandbox_id ?? null;
|
|
1364
|
+
this.hostName = response.result?.host_name ?? null;
|
|
1365
|
+
this.hostIp = response.result?.host_ip ?? null;
|
|
1366
|
+
const startTime = Date.now();
|
|
1367
|
+
while (Date.now() - startTime < this.config.startupTimeout * 1e3) {
|
|
1368
|
+
const status = await this.getStatus();
|
|
1369
|
+
if (status.isAlive) {
|
|
1370
|
+
return;
|
|
1371
|
+
}
|
|
1372
|
+
await sleep(3e3);
|
|
1373
|
+
}
|
|
1374
|
+
throw new Error(`Failed to start sandbox within ${this.config.startupTimeout}s`);
|
|
1375
|
+
} catch (e) {
|
|
1376
|
+
throw new Error(`Failed to start sandbox: ${e}`);
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
async stop() {
|
|
1380
|
+
if (!this.sandboxId) return;
|
|
1381
|
+
try {
|
|
1382
|
+
const url = `${this.url}/stop`;
|
|
1383
|
+
const headers = this.buildHeaders();
|
|
1384
|
+
await HttpUtils.post(url, headers, { sandbox_id: this.sandboxId });
|
|
1385
|
+
} catch (e) {
|
|
1386
|
+
logger8.warn(`Failed to stop sandbox, IGNORE: ${e}`);
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
async isAlive() {
|
|
1390
|
+
try {
|
|
1391
|
+
const status = await this.getStatus();
|
|
1392
|
+
return {
|
|
1393
|
+
isAlive: status.isAlive,
|
|
1394
|
+
message: status.hostName ?? ""
|
|
1395
|
+
};
|
|
1396
|
+
} catch (e) {
|
|
1397
|
+
throw new Error(`Failed to get is alive: ${e}`);
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
async getStatus() {
|
|
1401
|
+
const url = `${this.url}/get_status?sandbox_id=${this.sandboxId}`;
|
|
1402
|
+
const headers = this.buildHeaders();
|
|
1403
|
+
const response = await HttpUtils.get(url, headers);
|
|
1404
|
+
if (response.status !== "Success") {
|
|
1405
|
+
throw new Error(`Failed to get status: ${JSON.stringify(response)}`);
|
|
1406
|
+
}
|
|
1407
|
+
return response.result;
|
|
1408
|
+
}
|
|
1409
|
+
// Command execution
|
|
1410
|
+
async execute(command) {
|
|
1411
|
+
const url = `${this.url}/execute`;
|
|
1412
|
+
const headers = this.buildHeaders();
|
|
1413
|
+
const data = {
|
|
1414
|
+
command: command.command,
|
|
1415
|
+
sandbox_id: this.sandboxId,
|
|
1416
|
+
timeout: command.timeout,
|
|
1417
|
+
cwd: command.cwd,
|
|
1418
|
+
env: command.env
|
|
1419
|
+
};
|
|
1420
|
+
try {
|
|
1421
|
+
const response = await HttpUtils.post(
|
|
1422
|
+
url,
|
|
1423
|
+
headers,
|
|
1424
|
+
data
|
|
1425
|
+
);
|
|
1426
|
+
if (response.status !== "Success") {
|
|
1427
|
+
throw new Error(`Failed to execute command: ${JSON.stringify(response)}`);
|
|
1428
|
+
}
|
|
1429
|
+
return response.result;
|
|
1430
|
+
} catch (e) {
|
|
1431
|
+
throw new Error(`Failed to execute command: ${e}`);
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
// Session management
|
|
1435
|
+
async createSession(request) {
|
|
1436
|
+
const url = `${this.url}/create_session`;
|
|
1437
|
+
const headers = this.buildHeaders();
|
|
1438
|
+
const data = {
|
|
1439
|
+
sandbox_id: this.sandboxId,
|
|
1440
|
+
...request
|
|
1441
|
+
};
|
|
1442
|
+
try {
|
|
1443
|
+
const response = await HttpUtils.post(
|
|
1444
|
+
url,
|
|
1445
|
+
headers,
|
|
1446
|
+
data
|
|
1447
|
+
);
|
|
1448
|
+
if (response.status !== "Success") {
|
|
1449
|
+
throw new Error(`Failed to create session: ${JSON.stringify(response)}`);
|
|
1450
|
+
}
|
|
1451
|
+
return response.result;
|
|
1452
|
+
} catch (e) {
|
|
1453
|
+
throw new Error(`Failed to create session: ${e}`);
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
async closeSession(request) {
|
|
1457
|
+
const url = `${this.url}/close_session`;
|
|
1458
|
+
const headers = this.buildHeaders();
|
|
1459
|
+
const data = {
|
|
1460
|
+
sandbox_id: this.sandboxId,
|
|
1461
|
+
...request
|
|
1462
|
+
};
|
|
1463
|
+
try {
|
|
1464
|
+
const response = await HttpUtils.post(
|
|
1465
|
+
url,
|
|
1466
|
+
headers,
|
|
1467
|
+
data
|
|
1468
|
+
);
|
|
1469
|
+
if (response.status !== "Success") {
|
|
1470
|
+
throw new Error(`Failed to close session: ${JSON.stringify(response)}`);
|
|
1471
|
+
}
|
|
1472
|
+
return response.result ?? {};
|
|
1473
|
+
} catch (e) {
|
|
1474
|
+
throw new Error(`Failed to close session: ${e}`);
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
// Run command in session
|
|
1478
|
+
async arun(cmd, options = {}) {
|
|
1479
|
+
const {
|
|
1480
|
+
session,
|
|
1481
|
+
mode = "normal",
|
|
1482
|
+
waitTimeout = 300,
|
|
1483
|
+
waitInterval = 10
|
|
1484
|
+
} = options;
|
|
1485
|
+
if (mode === "normal") {
|
|
1486
|
+
return this.runInSession({ command: cmd, session: session ?? "default" });
|
|
1487
|
+
}
|
|
1488
|
+
return this.arunWithNohup(cmd, options);
|
|
1489
|
+
}
|
|
1490
|
+
async runInSession(action) {
|
|
1491
|
+
const url = `${this.url}/run_in_session`;
|
|
1492
|
+
const headers = this.buildHeaders();
|
|
1493
|
+
const data = {
|
|
1494
|
+
action_type: "bash",
|
|
1495
|
+
session: action.session,
|
|
1496
|
+
command: action.command,
|
|
1497
|
+
sandbox_id: this.sandboxId,
|
|
1498
|
+
timeout: action.timeout
|
|
1499
|
+
};
|
|
1500
|
+
try {
|
|
1501
|
+
const response = await HttpUtils.post(
|
|
1502
|
+
url,
|
|
1503
|
+
headers,
|
|
1504
|
+
data,
|
|
1505
|
+
action.timeout
|
|
1506
|
+
);
|
|
1507
|
+
if (response.status !== "Success") {
|
|
1508
|
+
throw new Error(`Failed to execute command: ${JSON.stringify(response)}`);
|
|
1509
|
+
}
|
|
1510
|
+
return response.result;
|
|
1511
|
+
} catch (e) {
|
|
1512
|
+
throw new Error(`Failed to run in session: ${e}`);
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
async arunWithNohup(cmd, options) {
|
|
1516
|
+
const {
|
|
1517
|
+
session,
|
|
1518
|
+
waitTimeout = 300,
|
|
1519
|
+
waitInterval = 10,
|
|
1520
|
+
responseLimitedBytesInNohup,
|
|
1521
|
+
ignoreOutput = false,
|
|
1522
|
+
outputFile
|
|
1523
|
+
} = options;
|
|
1524
|
+
const timestamp = Date.now();
|
|
1525
|
+
const tmpSession = session ?? `bash-${timestamp}`;
|
|
1526
|
+
if (!session) {
|
|
1527
|
+
await this.createSession({ session: tmpSession, startupSource: [], envEnable: false });
|
|
1528
|
+
}
|
|
1529
|
+
const tmpFile = outputFile ?? `/tmp/tmp_${timestamp}.out`;
|
|
1530
|
+
const nohupCommand = `nohup ${cmd} < /dev/null > ${tmpFile} 2>&1 & echo __ROCK_PID_START__$!__ROCK_PID_END__;disown`;
|
|
1531
|
+
const response = await this.runInSession({
|
|
1532
|
+
command: nohupCommand,
|
|
1533
|
+
session: tmpSession,
|
|
1534
|
+
timeout: 30
|
|
1535
|
+
});
|
|
1536
|
+
if (response.exitCode !== 0) {
|
|
1537
|
+
return response;
|
|
1538
|
+
}
|
|
1539
|
+
const pid = extractNohupPid(response.output);
|
|
1540
|
+
if (!pid) {
|
|
1541
|
+
return {
|
|
1542
|
+
output: "Failed to submit command, nohup failed to extract PID",
|
|
1543
|
+
exitCode: 1,
|
|
1544
|
+
failureReason: "PID extraction failed",
|
|
1545
|
+
expectString: ""
|
|
1546
|
+
};
|
|
1547
|
+
}
|
|
1548
|
+
const success = await this.waitForProcessCompletion(pid, tmpSession, waitTimeout, waitInterval);
|
|
1549
|
+
if (ignoreOutput) {
|
|
1550
|
+
return {
|
|
1551
|
+
output: `Command executed in nohup mode. Output file: ${tmpFile}`,
|
|
1552
|
+
exitCode: success ? 0 : 1,
|
|
1553
|
+
failureReason: success ? "" : "Process did not complete successfully",
|
|
1554
|
+
expectString: ""
|
|
1555
|
+
};
|
|
1556
|
+
}
|
|
1557
|
+
const readCmd = responseLimitedBytesInNohup ? `head -c ${responseLimitedBytesInNohup} ${tmpFile}` : `cat ${tmpFile}`;
|
|
1558
|
+
const outputResult = await this.runInSession({
|
|
1559
|
+
command: readCmd,
|
|
1560
|
+
session: tmpSession
|
|
1561
|
+
});
|
|
1562
|
+
return {
|
|
1563
|
+
output: outputResult.output,
|
|
1564
|
+
exitCode: success ? 0 : 1,
|
|
1565
|
+
failureReason: success ? "" : "Process did not complete successfully",
|
|
1566
|
+
expectString: ""
|
|
1567
|
+
};
|
|
1568
|
+
}
|
|
1569
|
+
async waitForProcessCompletion(pid, session, waitTimeout, waitInterval) {
|
|
1570
|
+
const startTime = Date.now();
|
|
1571
|
+
const checkInterval = Math.max(5, waitInterval);
|
|
1572
|
+
const effectiveInterval = Math.min(checkInterval * 2, waitTimeout);
|
|
1573
|
+
while (Date.now() - startTime < waitTimeout * 1e3) {
|
|
1574
|
+
try {
|
|
1575
|
+
await this.runInSession({
|
|
1576
|
+
command: `kill -0 ${pid}`,
|
|
1577
|
+
session,
|
|
1578
|
+
timeout: effectiveInterval
|
|
1579
|
+
});
|
|
1580
|
+
await sleep(checkInterval * 1e3);
|
|
1581
|
+
} catch {
|
|
1582
|
+
return true;
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
return false;
|
|
1586
|
+
}
|
|
1587
|
+
// File operations
|
|
1588
|
+
async write_file(request) {
|
|
1589
|
+
const url = `${this.url}/write_file`;
|
|
1590
|
+
const headers = this.buildHeaders();
|
|
1591
|
+
const data = {
|
|
1592
|
+
content: request.content,
|
|
1593
|
+
path: request.path,
|
|
1594
|
+
sandbox_id: this.sandboxId
|
|
1595
|
+
};
|
|
1596
|
+
const response = await HttpUtils.post(url, headers, data);
|
|
1597
|
+
if (response.status !== "Success") {
|
|
1598
|
+
return { success: false, message: `Failed to write file ${request.path}` };
|
|
1599
|
+
}
|
|
1600
|
+
return { success: true, message: `Successfully write content to file ${request.path}` };
|
|
1601
|
+
}
|
|
1602
|
+
async read_file(request) {
|
|
1603
|
+
const url = `${this.url}/read_file`;
|
|
1604
|
+
const headers = this.buildHeaders();
|
|
1605
|
+
const data = {
|
|
1606
|
+
path: request.path,
|
|
1607
|
+
encoding: request.encoding,
|
|
1608
|
+
errors: request.errors,
|
|
1609
|
+
sandbox_id: this.sandboxId
|
|
1610
|
+
};
|
|
1611
|
+
const response = await HttpUtils.post(
|
|
1612
|
+
url,
|
|
1613
|
+
headers,
|
|
1614
|
+
data
|
|
1615
|
+
);
|
|
1616
|
+
return { content: response.result?.content ?? "" };
|
|
1617
|
+
}
|
|
1618
|
+
// Upload
|
|
1619
|
+
async upload(request) {
|
|
1620
|
+
return this.uploadByPath(request.sourcePath, request.targetPath);
|
|
1621
|
+
}
|
|
1622
|
+
async uploadByPath(sourcePath, targetPath) {
|
|
1623
|
+
const url = `${this.url}/upload`;
|
|
1624
|
+
const headers = this.buildHeaders();
|
|
1625
|
+
try {
|
|
1626
|
+
const fs = await import('fs');
|
|
1627
|
+
if (!fs.existsSync(sourcePath)) {
|
|
1628
|
+
return { success: false, message: `File not found: ${sourcePath}` };
|
|
1629
|
+
}
|
|
1630
|
+
const fileBuffer = fs.readFileSync(sourcePath);
|
|
1631
|
+
const fileName = sourcePath.split("/").pop() ?? "file";
|
|
1632
|
+
const response = await HttpUtils.postMultipart(
|
|
1633
|
+
url,
|
|
1634
|
+
headers,
|
|
1635
|
+
{ target_path: targetPath, sandbox_id: this.sandboxId ?? "" },
|
|
1636
|
+
{ file: [fileName, fileBuffer, "application/octet-stream"] }
|
|
1637
|
+
);
|
|
1638
|
+
if (response.status !== "Success") {
|
|
1639
|
+
return { success: false, message: "Upload failed" };
|
|
1640
|
+
}
|
|
1641
|
+
return { success: true, message: `Successfully uploaded file ${fileName} to ${targetPath}` };
|
|
1642
|
+
} catch (e) {
|
|
1643
|
+
return { success: false, message: `Upload failed: ${e}` };
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
// Close
|
|
1647
|
+
async close() {
|
|
1648
|
+
await this.stop();
|
|
1649
|
+
}
|
|
1650
|
+
toString() {
|
|
1651
|
+
return `Sandbox(sandboxId=${this.sandboxId}, hostName=${this.hostName}, image=${this.config.image}, cluster=${this.cluster})`;
|
|
1652
|
+
}
|
|
1653
|
+
};
|
|
1654
|
+
var SandboxGroup = class {
|
|
1655
|
+
config;
|
|
1656
|
+
sandboxList;
|
|
1657
|
+
constructor(config = {}) {
|
|
1658
|
+
this.config = createSandboxGroupConfig(config);
|
|
1659
|
+
this.sandboxList = Array.from(
|
|
1660
|
+
{ length: this.config.size },
|
|
1661
|
+
() => new Sandbox(this.config)
|
|
1662
|
+
);
|
|
1663
|
+
}
|
|
1664
|
+
getSandboxList() {
|
|
1665
|
+
return this.sandboxList;
|
|
1666
|
+
}
|
|
1667
|
+
async start() {
|
|
1668
|
+
const concurrency = this.config.startConcurrency;
|
|
1669
|
+
const retryTimes = this.config.startRetryTimes;
|
|
1670
|
+
const startSandbox = async (index, sandbox) => {
|
|
1671
|
+
logger8.info(`Starting sandbox ${index} with ${sandbox.getConfig().image}...`);
|
|
1672
|
+
for (let attempt = 0; attempt < retryTimes; attempt++) {
|
|
1673
|
+
try {
|
|
1674
|
+
await sandbox.start();
|
|
1675
|
+
return;
|
|
1676
|
+
} catch (e) {
|
|
1677
|
+
if (attempt === retryTimes - 1) {
|
|
1678
|
+
logger8.error(`Failed to start sandbox after ${retryTimes} attempts: ${e}`);
|
|
1679
|
+
throw e;
|
|
1680
|
+
}
|
|
1681
|
+
logger8.warn(`Failed to start sandbox (attempt ${attempt + 1}/${retryTimes}): ${e}, retrying...`);
|
|
1682
|
+
await sleep(1e3);
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
};
|
|
1686
|
+
for (let i = 0; i < this.sandboxList.length; i += concurrency) {
|
|
1687
|
+
const batch = this.sandboxList.slice(i, i + concurrency);
|
|
1688
|
+
const promises = batch.map((sandbox, idx) => startSandbox(i + idx, sandbox));
|
|
1689
|
+
await Promise.all(promises);
|
|
1690
|
+
}
|
|
1691
|
+
logger8.info(`Successfully started ${this.sandboxList.length} sandboxes`);
|
|
1692
|
+
}
|
|
1693
|
+
async stop() {
|
|
1694
|
+
const promises = this.sandboxList.map((sandbox) => sandbox.stop());
|
|
1695
|
+
await Promise.allSettled(promises);
|
|
1696
|
+
logger8.info(`Stopped ${this.sandboxList.length} sandboxes`);
|
|
1697
|
+
}
|
|
1698
|
+
};
|
|
1699
|
+
var logger9 = initLogger("rock.model.client");
|
|
1700
|
+
var REQUEST_START_MARKER = "__REQUEST_START__";
|
|
1701
|
+
var REQUEST_END_MARKER = "__REQUEST_END__";
|
|
1702
|
+
var RESPONSE_START_MARKER = "__RESPONSE_START__";
|
|
1703
|
+
var RESPONSE_END_MARKER = "__RESPONSE_END__";
|
|
1704
|
+
var SESSION_END_MARKER = "__SESSION_END__";
|
|
1705
|
+
var ModelClient = class {
|
|
1706
|
+
logFile;
|
|
1707
|
+
constructor(config) {
|
|
1708
|
+
this.logFile = config?.logFileName ?? envVars.ROCK_MODEL_SERVICE_DATA_DIR + "/model.log";
|
|
1709
|
+
}
|
|
1710
|
+
/**
|
|
1711
|
+
* Anti-call LLM - input is response, output is next request
|
|
1712
|
+
*/
|
|
1713
|
+
async antiCallLlm(index, lastResponse) {
|
|
1714
|
+
if (index < 0) {
|
|
1715
|
+
throw new Error("index must be greater than 0");
|
|
1716
|
+
}
|
|
1717
|
+
if (index === 0) {
|
|
1718
|
+
if (lastResponse !== void 0) {
|
|
1719
|
+
throw new Error("lastResponse must be undefined when index is 0");
|
|
1720
|
+
}
|
|
1721
|
+
await this.waitForFirstRequest();
|
|
1722
|
+
return this.popRequest(index + 1);
|
|
1723
|
+
}
|
|
1724
|
+
if (lastResponse === void 0) {
|
|
1725
|
+
throw new Error("lastResponse must not be undefined when index is greater than 0");
|
|
1726
|
+
}
|
|
1727
|
+
await this.pushResponse(index, lastResponse);
|
|
1728
|
+
return this.popRequest(index + 1);
|
|
1729
|
+
}
|
|
1730
|
+
/**
|
|
1731
|
+
* Push response to log file
|
|
1732
|
+
*/
|
|
1733
|
+
async pushResponse(index, lastResponse) {
|
|
1734
|
+
const content = this.constructResponse(lastResponse, index);
|
|
1735
|
+
const lastResponseLine = await this.readLastResponseLine();
|
|
1736
|
+
if (lastResponseLine === null) {
|
|
1737
|
+
this.appendResponse(content);
|
|
1738
|
+
return;
|
|
1739
|
+
}
|
|
1740
|
+
const { meta } = this.parseResponseLine(lastResponseLine);
|
|
1741
|
+
const lastResponseIndex = meta.index;
|
|
1742
|
+
if (index < lastResponseIndex) {
|
|
1743
|
+
throw new Error(`index ${index} must not be smaller than last_response_index ${lastResponseIndex}`);
|
|
1744
|
+
}
|
|
1745
|
+
if (index === lastResponseIndex) {
|
|
1746
|
+
logger9.debug(`response index ${index} already exists, skip`);
|
|
1747
|
+
return;
|
|
1748
|
+
}
|
|
1749
|
+
this.appendResponse(content);
|
|
1750
|
+
}
|
|
1751
|
+
/**
|
|
1752
|
+
* Pop request from log file
|
|
1753
|
+
*/
|
|
1754
|
+
async popRequest(index) {
|
|
1755
|
+
while (true) {
|
|
1756
|
+
const lastRequestLine = await this.readLastRequestLine();
|
|
1757
|
+
const { requestJson, meta } = this.parseRequestLine(lastRequestLine);
|
|
1758
|
+
if (requestJson === SESSION_END_MARKER) {
|
|
1759
|
+
return SESSION_END_MARKER;
|
|
1760
|
+
}
|
|
1761
|
+
if (meta.index === index) {
|
|
1762
|
+
return requestJson;
|
|
1763
|
+
}
|
|
1764
|
+
logger9.debug(`Last request is not the index ${index} we want, waiting...`);
|
|
1765
|
+
await this.sleep(1e3);
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
/**
|
|
1769
|
+
* Wait for first request
|
|
1770
|
+
*/
|
|
1771
|
+
async waitForFirstRequest() {
|
|
1772
|
+
while (true) {
|
|
1773
|
+
if (!existsSync(this.logFile)) {
|
|
1774
|
+
logger9.debug(`Log file ${this.logFile} not found, waiting...`);
|
|
1775
|
+
await this.sleep(1e3);
|
|
1776
|
+
continue;
|
|
1777
|
+
}
|
|
1778
|
+
const content = readFileSync(this.logFile, "utf-8");
|
|
1779
|
+
const lines = content.split("\n").filter((l) => l.trim());
|
|
1780
|
+
if (lines.length === 0) {
|
|
1781
|
+
logger9.debug(`Log file ${this.logFile} is empty, waiting for the first request...`);
|
|
1782
|
+
await this.sleep(1e3);
|
|
1783
|
+
continue;
|
|
1784
|
+
}
|
|
1785
|
+
return;
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
parseRequestLine(lineContent) {
|
|
1789
|
+
if (lineContent.includes(SESSION_END_MARKER)) {
|
|
1790
|
+
return { requestJson: SESSION_END_MARKER, meta: {} };
|
|
1791
|
+
}
|
|
1792
|
+
const parts = lineContent.split(REQUEST_END_MARKER);
|
|
1793
|
+
const metaJson = parts[1] ?? "";
|
|
1794
|
+
const requestJson = parts[0]?.split(REQUEST_START_MARKER)[1] ?? "";
|
|
1795
|
+
const meta = JSON.parse(metaJson);
|
|
1796
|
+
return { requestJson, meta };
|
|
1797
|
+
}
|
|
1798
|
+
parseResponseLine(lineContent) {
|
|
1799
|
+
const parts = lineContent.split(RESPONSE_END_MARKER);
|
|
1800
|
+
const metaJson = parts[1] ?? "";
|
|
1801
|
+
const responseJson = parts[0]?.split(RESPONSE_START_MARKER)[1] ?? "";
|
|
1802
|
+
const meta = JSON.parse(metaJson);
|
|
1803
|
+
return { responseJson, meta };
|
|
1804
|
+
}
|
|
1805
|
+
async readLastRequestLine() {
|
|
1806
|
+
const content = readFileSync(this.logFile, "utf-8");
|
|
1807
|
+
const lines = content.split("\n").filter((l) => l.trim());
|
|
1808
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
1809
|
+
const line = lines[i];
|
|
1810
|
+
if (line && (line.includes(REQUEST_START_MARKER) || line.includes(SESSION_END_MARKER))) {
|
|
1811
|
+
return line;
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
throw new Error(`No request found in log file ${this.logFile}`);
|
|
1815
|
+
}
|
|
1816
|
+
async readLastResponseLine() {
|
|
1817
|
+
const content = readFileSync(this.logFile, "utf-8");
|
|
1818
|
+
const lines = content.split("\n").filter((l) => l.trim());
|
|
1819
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
1820
|
+
const line = lines[i];
|
|
1821
|
+
if (line && line.includes(RESPONSE_START_MARKER)) {
|
|
1822
|
+
return line;
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
return null;
|
|
1826
|
+
}
|
|
1827
|
+
appendResponse(content) {
|
|
1828
|
+
appendFileSync(this.logFile, content);
|
|
1829
|
+
}
|
|
1830
|
+
constructResponse(lastResponse, index) {
|
|
1831
|
+
const meta = {
|
|
1832
|
+
timestamp: Date.now(),
|
|
1833
|
+
index
|
|
1834
|
+
};
|
|
1835
|
+
const metaJson = JSON.stringify(meta);
|
|
1836
|
+
return `${RESPONSE_START_MARKER}${lastResponse}${RESPONSE_END_MARKER}${metaJson}
|
|
1837
|
+
`;
|
|
1838
|
+
}
|
|
1839
|
+
sleep(ms) {
|
|
1840
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
1841
|
+
}
|
|
1842
|
+
};
|
|
1843
|
+
initLogger("rock.model.service");
|
|
1844
|
+
var ModelService = class {
|
|
1845
|
+
process = null;
|
|
1846
|
+
/**
|
|
1847
|
+
* Start sandbox service
|
|
1848
|
+
*/
|
|
1849
|
+
startSandboxService(config = {}) {
|
|
1850
|
+
const {
|
|
1851
|
+
modelServiceType = "local",
|
|
1852
|
+
configFile,
|
|
1853
|
+
host,
|
|
1854
|
+
port,
|
|
1855
|
+
proxyBaseUrl,
|
|
1856
|
+
retryableStatusCodes,
|
|
1857
|
+
requestTimeout
|
|
1858
|
+
} = config;
|
|
1859
|
+
const cmd = ["node", "main.js", "--type", modelServiceType];
|
|
1860
|
+
if (configFile) {
|
|
1861
|
+
cmd.push("--config-file", configFile);
|
|
1862
|
+
}
|
|
1863
|
+
if (host) {
|
|
1864
|
+
cmd.push("--host", host);
|
|
1865
|
+
}
|
|
1866
|
+
if (port) {
|
|
1867
|
+
cmd.push("--port", String(port));
|
|
1868
|
+
}
|
|
1869
|
+
if (proxyBaseUrl) {
|
|
1870
|
+
cmd.push("--proxy-base-url", proxyBaseUrl);
|
|
1871
|
+
}
|
|
1872
|
+
if (retryableStatusCodes) {
|
|
1873
|
+
cmd.push("--retryable-status-codes", retryableStatusCodes);
|
|
1874
|
+
}
|
|
1875
|
+
if (requestTimeout) {
|
|
1876
|
+
cmd.push("--request-timeout", String(requestTimeout));
|
|
1877
|
+
}
|
|
1878
|
+
const command = cmd[0] ?? "node";
|
|
1879
|
+
this.process = spawn(command, cmd.slice(1), {
|
|
1880
|
+
cwd: resolve(__dirname, "server"),
|
|
1881
|
+
stdio: "inherit"
|
|
1882
|
+
});
|
|
1883
|
+
if (!this.process) {
|
|
1884
|
+
throw new Error("Failed to spawn model service process");
|
|
1885
|
+
}
|
|
1886
|
+
return this.process;
|
|
1887
|
+
}
|
|
1888
|
+
/**
|
|
1889
|
+
* Start and wait for service to be available
|
|
1890
|
+
*/
|
|
1891
|
+
async start(config = {}) {
|
|
1892
|
+
const { timeoutSeconds = 30, ...serviceConfig } = config;
|
|
1893
|
+
const process2 = this.startSandboxService(serviceConfig);
|
|
1894
|
+
const pid = process2.pid?.toString();
|
|
1895
|
+
if (!pid) {
|
|
1896
|
+
throw new Error("Failed to start model service");
|
|
1897
|
+
}
|
|
1898
|
+
const success = await this.waitServiceAvailable(
|
|
1899
|
+
timeoutSeconds,
|
|
1900
|
+
serviceConfig.host ?? "127.0.0.1",
|
|
1901
|
+
serviceConfig.port ?? 8080
|
|
1902
|
+
);
|
|
1903
|
+
if (!success) {
|
|
1904
|
+
await this.stop(pid);
|
|
1905
|
+
throw new Error("Model service start failed");
|
|
1906
|
+
}
|
|
1907
|
+
return pid;
|
|
1908
|
+
}
|
|
1909
|
+
/**
|
|
1910
|
+
* Start watching agent
|
|
1911
|
+
*/
|
|
1912
|
+
async startWatchAgent(agentPid, host = "127.0.0.1", port = 8080) {
|
|
1913
|
+
await axios3.post(`http://${host}:${port}/v1/agent/watch`, { pid: agentPid });
|
|
1914
|
+
}
|
|
1915
|
+
/**
|
|
1916
|
+
* Stop service
|
|
1917
|
+
*/
|
|
1918
|
+
async stop(pid) {
|
|
1919
|
+
const { execSync } = await import('child_process');
|
|
1920
|
+
try {
|
|
1921
|
+
execSync(`kill -9 ${pid}`);
|
|
1922
|
+
} catch {
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
/**
|
|
1926
|
+
* Wait for service to be available
|
|
1927
|
+
*/
|
|
1928
|
+
async waitServiceAvailable(timeoutSeconds, host, port) {
|
|
1929
|
+
const startTime = Date.now();
|
|
1930
|
+
while ((Date.now() - startTime) / 1e3 < timeoutSeconds) {
|
|
1931
|
+
try {
|
|
1932
|
+
await axios3.get(`http://${host}:${port}/health`);
|
|
1933
|
+
return true;
|
|
1934
|
+
} catch {
|
|
1935
|
+
await this.sleep(1e3);
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
return false;
|
|
1939
|
+
}
|
|
1940
|
+
sleep(ms) {
|
|
1941
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
1942
|
+
}
|
|
1943
|
+
};
|
|
1944
|
+
|
|
1945
|
+
// src/index.ts
|
|
1946
|
+
var VERSION = "1.2.1";
|
|
1947
|
+
|
|
1948
|
+
export { BadRequestRockError, BashActionSchema, ChmodRequestSchema, ChmodResponseSchema, ChownRequestSchema, ChownResponseSchema, CloseResponseSchema, CloseSessionRequestSchema, CloseSessionResponseSchema, Codes, CommandResponseSchema, CommandRockError, CommandSchema, Constants, CreateBashSessionRequestSchema, CreateSessionResponseSchema, Deploy, EnvHubClient, EnvHubClientConfigSchema, EnvHubError, ExecuteBashSessionResponseSchema, HttpUtils, InternalServerRockError, InvalidParameterRockException, IsAliveResponseSchema, LinuxFileSystem, LinuxRemoteUser, ModelClient, ModelService, Network, ObservationSchema, OssSetupResponseSchema, PID_PREFIX, PID_SUFFIX, Process, ReadFileRequestSchema, ReadFileResponseSchema, ReasonPhrases, RockEnv, RockEnvInfoSchema, RockException, RunMode, Sandbox, SandboxConfigSchema, SandboxGroup, SandboxGroupConfigSchema, SandboxResponseSchema, SandboxStatusResponseSchema, SpeedupType, UploadRequestSchema, UploadResponseSchema, VERSION, WriteFileRequestSchema, WriteFileResponseSchema, arunWithRetry, createRockEnvInfo, createSandboxConfig, createSandboxGroupConfig, deprecated, deprecatedClass, extractNohupPid as extractNohupPidFromSandbox, fromRockException, getEnv, getReasonPhrase, getRequiredEnv, isBrowser, isClientError, isCommandError, isEnvSet, isError, isNode, isServerError, isSuccess, make, raiseForCode, retryAsync, rockEnvInfoToDict, sleep, withRetry, withTimeLogging };
|
|
1949
|
+
//# sourceMappingURL=index.mjs.map
|
|
1950
|
+
//# sourceMappingURL=index.mjs.map
|