rl-rock 1.2.4 → 1.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,22 +1,24 @@
1
1
  'use strict';
2
2
 
3
+ var fs = require('fs');
4
+ var path = require('path');
3
5
  var zod = require('zod');
4
- var axios3 = require('axios');
6
+ var axios = require('axios');
5
7
  var https = require('https');
6
8
  var tsCaseConvert = require('ts-case-convert');
7
- require('os');
8
- var path = require('path');
9
9
  var winston = require('winston');
10
- var fs = require('fs');
10
+ var os = require('os');
11
11
  var crypto = require('crypto');
12
- var child_process = require('child_process');
12
+ var promises = require('fs/promises');
13
13
 
14
14
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
15
15
 
16
- var axios3__default = /*#__PURE__*/_interopDefault(axios3);
16
+ var axios__default = /*#__PURE__*/_interopDefault(axios);
17
17
  var https__default = /*#__PURE__*/_interopDefault(https);
18
18
  var winston__default = /*#__PURE__*/_interopDefault(winston);
19
19
 
20
+ // src/index.ts
21
+
20
22
  // src/types/codes.ts
21
23
  var Codes = /* @__PURE__ */ ((Codes2) => {
22
24
  Codes2[Codes2["OK"] = 2e3] = "OK";
@@ -177,14 +179,6 @@ var RunMode = {
177
179
  NORMAL: "normal",
178
180
  NOHUP: "nohup"
179
181
  };
180
- var Constants = class {
181
- static BASE_URL_PRODUCT = "";
182
- static BASE_URL_ALIYUN = "";
183
- static BASE_URL_INNER = "";
184
- static BASE_URL_PRE = "";
185
- static BASE_URL_LOCAL = "";
186
- static REQUEST_TIMEOUT_SECONDS = 180;
187
- };
188
182
  var PID_PREFIX = "__ROCK_PID_START__";
189
183
  var PID_SUFFIX = "__ROCK_PID_END__";
190
184
 
@@ -262,6 +256,10 @@ var objectToCamel = tsCaseConvert.objectToCamel;
262
256
  var objectToSnake = tsCaseConvert.objectToSnake;
263
257
 
264
258
  // src/utils/http.ts
259
+ var sharedHttpsAgent = new https__default.default.Agent({
260
+ rejectUnauthorized: true,
261
+ keepAlive: true
262
+ });
265
263
  var HttpUtils = class {
266
264
  static defaultTimeout = 3e5;
267
265
  // 5 minutes
@@ -270,15 +268,13 @@ var HttpUtils = class {
270
268
  * Create axios instance with default config
271
269
  */
272
270
  static createClient(config) {
273
- return axios3__default.default.create({
271
+ return axios__default.default.create({
274
272
  timeout: config?.timeout ?? this.defaultTimeout,
275
273
  headers: {
276
274
  "Content-Type": "application/json",
277
275
  ...config?.headers
278
276
  },
279
- httpsAgent: new https__default.default.Agent({
280
- rejectUnauthorized: true
281
- })
277
+ httpsAgent: sharedHttpsAgent
282
278
  });
283
279
  }
284
280
  /**
@@ -307,7 +303,7 @@ var HttpUtils = class {
307
303
  }
308
304
  return httpResponse;
309
305
  } catch (error) {
310
- if (error instanceof axios3.AxiosError) {
306
+ if (error instanceof axios.AxiosError) {
311
307
  throw new Error(`Failed to POST ${url}: ${error.message}`);
312
308
  }
313
309
  throw error;
@@ -334,7 +330,7 @@ var HttpUtils = class {
334
330
  }
335
331
  return httpResponse;
336
332
  } catch (error) {
337
- if (error instanceof axios3.AxiosError) {
333
+ if (error instanceof axios.AxiosError) {
338
334
  throw new Error(`Failed to GET ${url}: ${error.message}`);
339
335
  }
340
336
  throw error;
@@ -394,12 +390,15 @@ var HttpUtils = class {
394
390
  }
395
391
  const client = this.createClient({
396
392
  headers: {
397
- ...headers,
398
- "Content-Type": "multipart/form-data"
393
+ ...headers
399
394
  }
400
395
  });
401
396
  try {
402
- const response = await client.post(url, formData);
397
+ const response = await client.post(url, formData, {
398
+ headers: {
399
+ "Content-Type": null
400
+ }
401
+ });
403
402
  const camelData = objectToCamel(response.data);
404
403
  const httpResponse = {
405
404
  status: camelData.status ?? "Success",
@@ -413,7 +412,7 @@ var HttpUtils = class {
413
412
  }
414
413
  return httpResponse;
415
414
  } catch (error) {
416
- if (error instanceof axios3.AxiosError) {
415
+ if (error instanceof axios.AxiosError) {
417
416
  throw new Error(`Failed to POST multipart ${url}: ${error.message}`);
418
417
  }
419
418
  throw error;
@@ -444,13 +443,21 @@ var HttpUtils = class {
444
443
  return mimeTypes[ext ?? ""] ?? "application/octet-stream";
445
444
  }
446
445
  };
446
+ function extractNohupPid(output) {
447
+ const pattern = new RegExp(`${PID_PREFIX}(\\d+)${PID_SUFFIX}`);
448
+ const match = output.match(pattern);
449
+ if (match?.[1]) {
450
+ return parseInt(match[1], 10);
451
+ }
452
+ return null;
453
+ }
447
454
 
448
455
  // src/utils/retry.ts
449
456
  function retryAsync(fn, options = {}) {
450
457
  const {
451
458
  maxAttempts = 3,
452
459
  delaySeconds = 1,
453
- backoff = 1,
460
+ backoff = 2,
454
461
  jitter = false
455
462
  } = options;
456
463
  return retryAsyncImpl(fn, {
@@ -491,43 +498,12 @@ function withRetry(fn, options = {}) {
491
498
  });
492
499
  }
493
500
 
494
- // src/utils/deprecated.ts
495
- function deprecated(reason = "") {
496
- return function(target, propertyKey, descriptor) {
497
- const originalMethod = descriptor.value;
498
- descriptor.value = function(...args) {
499
- console.warn(
500
- `${String(propertyKey)} is deprecated. ${reason}`
501
- );
502
- return originalMethod.apply(this, args);
503
- };
504
- return descriptor;
505
- };
506
- }
507
- function deprecatedClass(reason = "") {
508
- return function(constructor) {
509
- return class extends constructor {
510
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
511
- constructor(...args) {
512
- console.warn(`${constructor.name} is deprecated. ${reason}`);
513
- super(...args);
514
- }
515
- };
516
- };
517
- }
518
-
519
501
  // src/utils/system.ts
520
502
  function isNode() {
521
503
  return typeof process !== "undefined" && process.versions != null && process.versions.node != null;
522
504
  }
523
- function isBrowser() {
524
- return typeof globalThis !== "undefined" && "window" in globalThis && typeof globalThis.window !== "undefined";
525
- }
526
505
  function getEnv(key, defaultValue) {
527
- if (isNode()) {
528
- return process.env[key] ?? defaultValue;
529
- }
530
- return defaultValue;
506
+ return process.env[key] ?? defaultValue;
531
507
  }
532
508
  function getRequiredEnv(key) {
533
509
  const value = getEnv(key);
@@ -537,10 +513,7 @@ function getRequiredEnv(key) {
537
513
  return value;
538
514
  }
539
515
  function isEnvSet(key) {
540
- if (isNode()) {
541
- return key in process.env;
542
- }
543
- return false;
516
+ return key in process.env;
544
517
  }
545
518
  var envVars = {
546
519
  // Logging
@@ -553,23 +526,306 @@ var envVars = {
553
526
  get ROCK_LOGGING_LEVEL() {
554
527
  return getEnv("ROCK_LOGGING_LEVEL", "INFO");
555
528
  },
529
+ // Service
530
+ get ROCK_SERVICE_STATUS_DIR() {
531
+ return getEnv("ROCK_SERVICE_STATUS_DIR", "/data/service_status");
532
+ },
533
+ get ROCK_SCHEDULER_STATUS_DIR() {
534
+ return getEnv("ROCK_SCHEDULER_STATUS_DIR", "/data/scheduler_status");
535
+ },
536
+ // Config
537
+ get ROCK_CONFIG() {
538
+ return getEnv("ROCK_CONFIG");
539
+ },
540
+ get ROCK_CONFIG_DIR_NAME() {
541
+ return getEnv("ROCK_CONFIG_DIR_NAME", "rock-conf");
542
+ },
556
543
  // Base URLs
557
544
  get ROCK_BASE_URL() {
558
545
  return getEnv("ROCK_BASE_URL", "http://localhost:8080");
559
546
  },
547
+ get ROCK_WORKER_ROCKLET_PORT() {
548
+ const val = getEnv("ROCK_WORKER_ROCKLET_PORT");
549
+ return val ? parseInt(val, 10) : void 0;
550
+ },
560
551
  get ROCK_SANDBOX_STARTUP_TIMEOUT_SECONDS() {
561
552
  return parseInt(getEnv("ROCK_SANDBOX_STARTUP_TIMEOUT_SECONDS", "180"), 10);
562
553
  },
554
+ get ROCK_CODE_SANDBOX_BASE_URL() {
555
+ return getEnv("ROCK_CODE_SANDBOX_BASE_URL", "");
556
+ },
563
557
  // EnvHub
564
558
  get ROCK_ENVHUB_BASE_URL() {
565
559
  return getEnv("ROCK_ENVHUB_BASE_URL", "http://localhost:8081");
566
560
  },
561
+ get ROCK_ENVHUB_DEFAULT_DOCKER_IMAGE() {
562
+ return getEnv("ROCK_ENVHUB_DEFAULT_DOCKER_IMAGE", "python:3.11");
563
+ },
564
+ get ROCK_ENVHUB_DB_URL() {
565
+ return getEnv(
566
+ "ROCK_ENVHUB_DB_URL",
567
+ `sqlite:///${path.join(os.homedir(), ".rock", "rock_envs.db")}`
568
+ );
569
+ },
570
+ // Auto clear
571
+ get ROCK_DEFAULT_AUTO_CLEAR_TIME_MINUTES() {
572
+ return parseInt(getEnv("ROCK_DEFAULT_AUTO_CLEAR_TIME_MINUTES", "360"), 10);
573
+ },
574
+ // Ray
575
+ get ROCK_RAY_NAMESPACE() {
576
+ return getEnv("ROCK_RAY_NAMESPACE", "xrl-sandbox");
577
+ },
578
+ get ROCK_SANDBOX_EXPIRE_TIME_KEY() {
579
+ return getEnv("ROCK_SANDBOX_EXPIRE_TIME_KEY", "expire_time");
580
+ },
581
+ get ROCK_SANDBOX_AUTO_CLEAR_TIME_KEY() {
582
+ return getEnv("ROCK_SANDBOX_AUTO_CLEAR_TIME_KEY", "auto_clear_time");
583
+ },
584
+ // Timezone
585
+ get ROCK_TIME_ZONE() {
586
+ return getEnv("ROCK_TIME_ZONE", "Asia/Shanghai");
587
+ },
588
+ // OSS
589
+ get ROCK_OSS_ENABLE() {
590
+ return getEnv("ROCK_OSS_ENABLE", "false")?.toLowerCase() === "true";
591
+ },
592
+ get ROCK_OSS_BUCKET_ENDPOINT() {
593
+ return getEnv("ROCK_OSS_BUCKET_ENDPOINT");
594
+ },
595
+ get ROCK_OSS_BUCKET_NAME() {
596
+ return getEnv("ROCK_OSS_BUCKET_NAME");
597
+ },
598
+ get ROCK_OSS_BUCKET_REGION() {
599
+ return getEnv("ROCK_OSS_BUCKET_REGION");
600
+ },
601
+ // Pip
602
+ get ROCK_PIP_INDEX_URL() {
603
+ return getEnv("ROCK_PIP_INDEX_URL", "https://pypi.org/simple/");
604
+ },
605
+ // Monitor
606
+ get ROCK_MONITOR_ENABLE() {
607
+ return getEnv("ROCK_MONITOR_ENABLE", "false")?.toLowerCase() === "true";
608
+ },
609
+ // Project
610
+ get ROCK_PROJECT_ROOT() {
611
+ return getEnv("ROCK_PROJECT_ROOT", process.cwd());
612
+ },
613
+ get ROCK_WORKER_ENV_TYPE() {
614
+ return getEnv("ROCK_WORKER_ENV_TYPE", "local");
615
+ },
616
+ get ROCK_PYTHON_ENV_PATH() {
617
+ return getEnv("ROCK_PYTHON_ENV_PATH", process.cwd());
618
+ },
619
+ // Admin
620
+ get ROCK_ADMIN_ENV() {
621
+ return getEnv("ROCK_ADMIN_ENV", "dev");
622
+ },
623
+ get ROCK_ADMIN_ROLE() {
624
+ return getEnv("ROCK_ADMIN_ROLE", "write");
625
+ },
626
+ // CLI
627
+ get ROCK_CLI_LOAD_PATHS() {
628
+ return getEnv("ROCK_CLI_LOAD_PATHS", "");
629
+ },
630
+ get ROCK_CLI_DEFAULT_CONFIG_PATH() {
631
+ return getEnv("ROCK_CLI_DEFAULT_CONFIG_PATH", path.join(os.homedir(), ".rock", "config.ini"));
632
+ },
567
633
  // Model Service
568
634
  get ROCK_MODEL_SERVICE_DATA_DIR() {
569
635
  return getEnv("ROCK_MODEL_SERVICE_DATA_DIR", "/data/logs");
570
- }};
636
+ },
637
+ get ROCK_MODEL_SERVICE_TRAJ_APPEND_MODE() {
638
+ return getEnv("ROCK_MODEL_SERVICE_TRAJ_APPEND_MODE", "false")?.toLowerCase() === "true";
639
+ },
640
+ // RuntimeEnv
641
+ get ROCK_RTENV_PYTHON_V31114_INSTALL_CMD() {
642
+ return getEnv(
643
+ "ROCK_RTENV_PYTHON_V31114_INSTALL_CMD",
644
+ "[ -f cpython31114.tar.gz ] && rm cpython31114.tar.gz; [ -d python ] && rm -rf python; wget -q -O cpython31114.tar.gz https://github.com/astral-sh/python-build-standalone/releases/download/20251120/cpython-3.11.14+20251120-x86_64-unknown-linux-gnu-install_only.tar.gz && tar -xzf cpython31114.tar.gz && mv python runtime-env"
645
+ );
646
+ },
647
+ get ROCK_RTENV_PYTHON_V31212_INSTALL_CMD() {
648
+ return getEnv(
649
+ "ROCK_RTENV_PYTHON_V31212_INSTALL_CMD",
650
+ "[ -f cpython-3.12.12.tar.gz ] && rm cpython-3.12.12.tar.gz; [ -d python ] && rm -rf python; wget -q -O cpython-3.12.12.tar.gz https://github.com/astral-sh/python-build-standalone/releases/download/20251217/cpython-3.12.12+20251217-x86_64-unknown-linux-gnu-install_only.tar.gz && tar -xzf cpython-3.12.12.tar.gz && mv python runtime-env"
651
+ );
652
+ },
653
+ get ROCK_RTENV_NODE_V22180_INSTALL_CMD() {
654
+ return getEnv(
655
+ "ROCK_RTENV_NODE_V22180_INSTALL_CMD",
656
+ "[ -f node.tar.xz ] && rm node.tar.xz; [ -d node ] && rm -rf node; wget -q -O node.tar.xz --tries=10 --waitretry=2 https://nodejs.org/dist/v22.18.0/node-v22.18.0-linux-x64.tar.xz && tar -xf node.tar.xz && mv node-v22.18.0-linux-x64 runtime-env"
657
+ );
658
+ },
659
+ // Agent
660
+ get ROCK_AGENT_PRE_INIT_BASH_CMD_LIST() {
661
+ const val = getEnv("ROCK_AGENT_PRE_INIT_BASH_CMD_LIST", "[]");
662
+ try {
663
+ return JSON.parse(val);
664
+ } catch {
665
+ return [];
666
+ }
667
+ },
668
+ get ROCK_AGENT_IFLOW_CLI_INSTALL_CMD() {
669
+ return getEnv("ROCK_AGENT_IFLOW_CLI_INSTALL_CMD", "npm i -g @iflow-ai/iflow-cli@latest");
670
+ },
671
+ get ROCK_MODEL_SERVICE_INSTALL_CMD() {
672
+ return getEnv("ROCK_MODEL_SERVICE_INSTALL_CMD", "pip install rl_rock[model-service]");
673
+ },
674
+ // Doccuum
675
+ get ROCK_DOCUUM_INSTALL_URL() {
676
+ return getEnv(
677
+ "ROCK_DOCUUM_INSTALL_URL",
678
+ "https://raw.githubusercontent.com/stepchowfun/docuum/main/install.sh"
679
+ );
680
+ },
681
+ // ========== Sandbox Defaults ==========
682
+ // Sandbox configuration defaults - allow users to override via environment variables
683
+ get ROCK_DEFAULT_IMAGE() {
684
+ return getEnv("ROCK_DEFAULT_IMAGE", "python:3.11");
685
+ },
686
+ get ROCK_DEFAULT_MEMORY() {
687
+ return getEnv("ROCK_DEFAULT_MEMORY", "8g");
688
+ },
689
+ get ROCK_DEFAULT_CPUS() {
690
+ return parseFloat(getEnv("ROCK_DEFAULT_CPUS", "2"));
691
+ },
692
+ get ROCK_DEFAULT_CLUSTER() {
693
+ return getEnv("ROCK_DEFAULT_CLUSTER", "zb");
694
+ },
695
+ get ROCK_DEFAULT_AUTO_CLEAR_SECONDS() {
696
+ return parseInt(getEnv("ROCK_DEFAULT_AUTO_CLEAR_SECONDS", "300"), 10);
697
+ },
698
+ // ========== SandboxGroup Defaults ==========
699
+ get ROCK_DEFAULT_GROUP_SIZE() {
700
+ return parseInt(getEnv("ROCK_DEFAULT_GROUP_SIZE", "2"), 10);
701
+ },
702
+ get ROCK_DEFAULT_START_CONCURRENCY() {
703
+ return parseInt(getEnv("ROCK_DEFAULT_START_CONCURRENCY", "2"), 10);
704
+ },
705
+ get ROCK_DEFAULT_START_RETRY_TIMES() {
706
+ return parseInt(getEnv("ROCK_DEFAULT_START_RETRY_TIMES", "3"), 10);
707
+ },
708
+ // ========== Client Timeouts (in seconds) ==========
709
+ get ROCK_DEFAULT_ARUN_TIMEOUT() {
710
+ return parseInt(getEnv("ROCK_DEFAULT_ARUN_TIMEOUT", "300"), 10);
711
+ },
712
+ get ROCK_DEFAULT_NOHUP_WAIT_TIMEOUT() {
713
+ return parseInt(getEnv("ROCK_DEFAULT_NOHUP_WAIT_TIMEOUT", "300"), 10);
714
+ },
715
+ get ROCK_DEFAULT_NOHUP_WAIT_INTERVAL() {
716
+ return parseInt(getEnv("ROCK_DEFAULT_NOHUP_WAIT_INTERVAL", "10"), 10);
717
+ },
718
+ get ROCK_DEFAULT_STATUS_CHECK_INTERVAL() {
719
+ return parseInt(getEnv("ROCK_DEFAULT_STATUS_CHECK_INTERVAL", "3"), 10);
720
+ }
721
+ };
722
+ var levels = {
723
+ error: 0,
724
+ warn: 1,
725
+ info: 2,
726
+ http: 3,
727
+ debug: 4
728
+ };
729
+ var colors = {
730
+ error: "red",
731
+ warn: "yellow",
732
+ info: "green",
733
+ http: "magenta",
734
+ debug: "cyan"
735
+ };
736
+ winston__default.default.addColors(colors);
737
+ var consoleFormat = winston__default.default.format.combine(
738
+ winston__default.default.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss.SSS" }),
739
+ winston__default.default.format.colorize({ all: true }),
740
+ winston__default.default.format.printf((info) => {
741
+ const { timestamp, level, message, ...meta } = info;
742
+ const metaStr = Object.keys(meta).length ? JSON.stringify(meta) : "";
743
+ return `${timestamp} ${level}: ${message} ${metaStr}`;
744
+ })
745
+ );
746
+ var fileFormat = winston__default.default.format.combine(
747
+ winston__default.default.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss.SSS" }),
748
+ winston__default.default.format.json()
749
+ );
750
+ function getLogLevel() {
751
+ const level = envVars.ROCK_LOGGING_LEVEL.toLowerCase();
752
+ if (level in levels) {
753
+ return level;
754
+ }
755
+ return "info";
756
+ }
757
+ var loggerContainer = new winston__default.default.Container();
758
+ function initLogger(name = "rock", fileName) {
759
+ if (loggerContainer.has(name)) {
760
+ return loggerContainer.get(name);
761
+ }
762
+ const transports = [];
763
+ const logLevel = getLogLevel();
764
+ const logPath = envVars.ROCK_LOGGING_PATH;
765
+ const logFileName = envVars.ROCK_LOGGING_FILE_NAME;
766
+ if (logPath) {
767
+ if (!fs.existsSync(logPath)) {
768
+ fs.mkdirSync(logPath, { recursive: true });
769
+ }
770
+ transports.push(
771
+ new winston__default.default.transports.File({
772
+ filename: path.join(logPath, logFileName),
773
+ format: fileFormat,
774
+ level: logLevel
775
+ })
776
+ );
777
+ } else {
778
+ transports.push(
779
+ new winston__default.default.transports.Console({
780
+ format: consoleFormat,
781
+ level: logLevel
782
+ })
783
+ );
784
+ }
785
+ const logger13 = loggerContainer.add(name, {
786
+ levels,
787
+ defaultMeta: { service: name },
788
+ transports
789
+ });
790
+ return logger13;
791
+ }
792
+ initLogger("rock");
571
793
 
572
- // src/envhub/schema.ts
794
+ // src/utils/deprecated.ts
795
+ var warnedKeys = /* @__PURE__ */ new Set();
796
+ function getLogger() {
797
+ return initLogger("rock.deprecated");
798
+ }
799
+ function warnOnce(key, message) {
800
+ if (warnedKeys.has(key)) {
801
+ return;
802
+ }
803
+ getLogger().warn(message);
804
+ warnedKeys.add(key);
805
+ }
806
+ function deprecated(reason = "") {
807
+ return function(target, propertyKey, descriptor) {
808
+ const originalMethod = descriptor.value;
809
+ const key = String(propertyKey);
810
+ descriptor.value = function(...args) {
811
+ warnOnce(key, `${key} is deprecated. ${reason}`);
812
+ return originalMethod.apply(this, args);
813
+ };
814
+ return descriptor;
815
+ };
816
+ }
817
+ function deprecatedClass(reason = "") {
818
+ return function(constructor) {
819
+ const key = constructor.name;
820
+ return class extends constructor {
821
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
822
+ constructor(...args) {
823
+ warnOnce(key, `${key} is deprecated. ${reason}`);
824
+ super(...args);
825
+ }
826
+ };
827
+ };
828
+ }
573
829
  var EnvHubClientConfigSchema = zod.z.object({
574
830
  baseUrl: zod.z.string().default(envVars.ROCK_ENVHUB_BASE_URL)
575
831
  });
@@ -698,104 +954,51 @@ var EnvHubClient = class {
698
954
  }
699
955
  }
700
956
  };
701
- var levels = {
702
- error: 0,
703
- warn: 1,
704
- info: 2,
705
- http: 3,
706
- debug: 4
707
- };
708
- var colors = {
709
- error: "red",
710
- warn: "yellow",
711
- info: "green",
712
- http: "magenta",
713
- debug: "cyan"
714
- };
715
- winston__default.default.addColors(colors);
716
- var consoleFormat = winston__default.default.format.combine(
717
- winston__default.default.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss.SSS" }),
718
- winston__default.default.format.colorize({ all: true }),
719
- winston__default.default.format.printf((info) => {
720
- const { timestamp, level, message, ...meta } = info;
721
- const metaStr = Object.keys(meta).length ? JSON.stringify(meta) : "";
722
- return `${timestamp} ${level}: ${message} ${metaStr}`;
723
- })
724
- );
725
- var fileFormat = winston__default.default.format.combine(
726
- winston__default.default.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss.SSS" }),
727
- winston__default.default.format.json()
728
- );
729
- function getLogLevel() {
730
- const level = envVars.ROCK_LOGGING_LEVEL.toLowerCase();
731
- if (level in levels) {
732
- return level;
733
- }
734
- return "info";
735
- }
736
- var loggerCache = /* @__PURE__ */ new Map();
737
- function initLogger(name = "rock", fileName) {
738
- if (loggerCache.has(name)) {
739
- return loggerCache.get(name);
740
- }
741
- const transports = [];
742
- const logLevel = getLogLevel();
743
- const logPath = envVars.ROCK_LOGGING_PATH;
744
- const logFileName = envVars.ROCK_LOGGING_FILE_NAME;
745
- if (logPath) {
746
- if (!fs.existsSync(logPath)) {
747
- fs.mkdirSync(logPath, { recursive: true });
748
- }
749
- transports.push(
750
- new winston__default.default.transports.File({
751
- filename: path.join(logPath, logFileName),
752
- format: fileFormat,
753
- level: logLevel
754
- })
755
- );
756
- } else {
757
- transports.push(
758
- new winston__default.default.transports.Console({
759
- format: consoleFormat,
760
- level: logLevel
761
- })
762
- );
763
- }
764
- const logger10 = winston__default.default.createLogger({
765
- levels,
766
- defaultMeta: { service: name },
767
- transports
768
- });
769
- loggerCache.set(name, logger10);
770
- return logger10;
771
- }
772
- initLogger("rock");
773
957
 
774
958
  // src/envs/rock_env.ts
775
959
  var logger = initLogger("rock.envs");
776
- var RockEnv = class {
960
+ var RockEnv = class _RockEnv {
777
961
  envId;
778
962
  sandboxId = null;
779
963
  isClosed = false;
780
- client;
781
964
  constructor(config) {
782
965
  this.envId = config.envId;
783
- this.client = axios3__default.default.create({
784
- baseURL: envVars.ROCK_BASE_URL,
785
- timeout: 3e5,
786
- headers: { "Content-Type": "application/json" }
787
- });
966
+ }
967
+ /**
968
+ * Create and initialize a RockEnv instance
969
+ *
970
+ * @param config - Environment configuration
971
+ * @returns Initialized RockEnv instance
972
+ */
973
+ static async create(config) {
974
+ const env = new _RockEnv(config);
788
975
  try {
789
- this.initializeEnvironment();
976
+ await env.initializeEnvironment();
790
977
  } catch (e) {
791
978
  throw new Error(`Failed to initialize environment: ${e}`);
792
979
  }
980
+ return env;
981
+ }
982
+ /**
983
+ * Get the sandbox ID
984
+ */
985
+ getSandboxId() {
986
+ return this.sandboxId;
793
987
  }
794
988
  /**
795
989
  * Initialize environment instance
796
990
  */
797
- initializeEnvironment() {
991
+ async initializeEnvironment() {
798
992
  logger.debug(`Initializing environment: ${this.envId}`);
993
+ const response = await HttpUtils.post(
994
+ `${envVars.ROCK_BASE_URL}/apis/v1/envs/gem/make`,
995
+ { "Content-Type": "application/json" },
996
+ { envId: this.envId }
997
+ );
998
+ this.sandboxId = response.result?.sandboxId ?? null;
999
+ if (!this.sandboxId) {
1000
+ throw new Error("Failed to get environment instance ID");
1001
+ }
799
1002
  }
800
1003
  /**
801
1004
  * Execute an action step
@@ -805,19 +1008,12 @@ var RockEnv = class {
805
1008
  */
806
1009
  async step(action) {
807
1010
  this.ensureNotClosed();
808
- const params = {
809
- sandbox_id: this.sandboxId,
810
- action
811
- };
812
- try {
813
- const response = await this.client.post(
814
- "/apis/v1/envs/gem/step",
815
- params
816
- );
817
- return this.parseStepResult(response.data);
818
- } catch (e) {
819
- throw new Error(`Failed to execute step with action ${action}: ${e}`);
820
- }
1011
+ const response = await HttpUtils.post(
1012
+ `${envVars.ROCK_BASE_URL}/apis/v1/envs/gem/step`,
1013
+ { "Content-Type": "application/json" },
1014
+ { sandboxId: this.sandboxId, action }
1015
+ );
1016
+ return this.parseStepResult(response.result);
821
1017
  }
822
1018
  /**
823
1019
  * Reset environment to initial state
@@ -827,19 +1023,16 @@ var RockEnv = class {
827
1023
  */
828
1024
  async reset(seed) {
829
1025
  this.ensureNotClosed();
830
- const params = { sandbox_id: this.sandboxId };
1026
+ const params = { sandboxId: this.sandboxId };
831
1027
  if (seed !== void 0) {
832
1028
  params.seed = seed;
833
1029
  }
834
- try {
835
- const response = await this.client.post(
836
- "/apis/v1/envs/gem/reset",
837
- params
838
- );
839
- return this.parseResetResult(response.data);
840
- } catch (e) {
841
- throw new Error(`Failed to reset environment: ${e}`);
842
- }
1030
+ const response = await HttpUtils.post(
1031
+ `${envVars.ROCK_BASE_URL}/apis/v1/envs/gem/reset`,
1032
+ { "Content-Type": "application/json" },
1033
+ params
1034
+ );
1035
+ return this.parseResetResult(response.result);
843
1036
  }
844
1037
  /**
845
1038
  * Close environment and clean up resources
@@ -849,9 +1042,11 @@ var RockEnv = class {
849
1042
  return;
850
1043
  }
851
1044
  try {
852
- await this.client.post("/apis/v1/envs/gem/close", {
853
- sandbox_id: this.sandboxId
854
- });
1045
+ await HttpUtils.post(
1046
+ `${envVars.ROCK_BASE_URL}/apis/v1/envs/gem/close`,
1047
+ { "Content-Type": "application/json" },
1048
+ { sandboxId: this.sandboxId }
1049
+ );
855
1050
  } catch (e) {
856
1051
  throw new Error(`Failed to close environment: ${e}`);
857
1052
  } finally {
@@ -863,6 +1058,9 @@ var RockEnv = class {
863
1058
  * Parse step result from API response
864
1059
  */
865
1060
  parseStepResult(data) {
1061
+ if (!data) {
1062
+ throw new Error("Invalid step result: no data");
1063
+ }
866
1064
  return [
867
1065
  data.observation,
868
1066
  data.reward,
@@ -875,6 +1073,9 @@ var RockEnv = class {
875
1073
  * Parse reset result from API response
876
1074
  */
877
1075
  parseResetResult(data) {
1076
+ if (!data) {
1077
+ throw new Error("Invalid reset result: no data");
1078
+ }
878
1079
  return [data.observation, data.info];
879
1080
  }
880
1081
  /**
@@ -888,30 +1089,30 @@ var RockEnv = class {
888
1089
  };
889
1090
 
890
1091
  // src/envs/registration.ts
891
- function make(envId, options) {
892
- return new RockEnv({ envId, ...options });
1092
+ async function make(envId, options) {
1093
+ return RockEnv.create({ envId, ...options });
893
1094
  }
894
1095
  var BaseConfigSchema = zod.z.object({
895
- baseUrl: zod.z.string().default(envVars.ROCK_BASE_URL),
1096
+ baseUrl: zod.z.string().default(() => envVars.ROCK_BASE_URL),
896
1097
  xrlAuthorization: zod.z.string().optional(),
897
1098
  extraHeaders: zod.z.record(zod.z.string()).default({})
898
1099
  });
899
1100
  var SandboxConfigSchema = BaseConfigSchema.extend({
900
- image: zod.z.string().default("python:3.11"),
901
- autoClearSeconds: zod.z.number().default(300),
1101
+ image: zod.z.string().default(() => envVars.ROCK_DEFAULT_IMAGE),
1102
+ autoClearSeconds: zod.z.number().default(() => envVars.ROCK_DEFAULT_AUTO_CLEAR_SECONDS),
902
1103
  routeKey: zod.z.string().optional(),
903
- startupTimeout: zod.z.number().default(envVars.ROCK_SANDBOX_STARTUP_TIMEOUT_SECONDS),
904
- memory: zod.z.string().default("8g"),
905
- cpus: zod.z.number().default(2),
1104
+ startupTimeout: zod.z.number().default(() => envVars.ROCK_SANDBOX_STARTUP_TIMEOUT_SECONDS),
1105
+ memory: zod.z.string().default(() => envVars.ROCK_DEFAULT_MEMORY),
1106
+ cpus: zod.z.number().default(() => envVars.ROCK_DEFAULT_CPUS),
906
1107
  userId: zod.z.string().optional(),
907
1108
  experimentId: zod.z.string().optional(),
908
- cluster: zod.z.string().default("zb"),
1109
+ cluster: zod.z.string().default(() => envVars.ROCK_DEFAULT_CLUSTER),
909
1110
  namespace: zod.z.string().optional()
910
1111
  });
911
1112
  var SandboxGroupConfigSchema = SandboxConfigSchema.extend({
912
- size: zod.z.number().default(2),
913
- startConcurrency: zod.z.number().default(2),
914
- startRetryTimes: zod.z.number().default(3)
1113
+ size: zod.z.number().default(() => envVars.ROCK_DEFAULT_GROUP_SIZE),
1114
+ startConcurrency: zod.z.number().default(() => envVars.ROCK_DEFAULT_START_CONCURRENCY),
1115
+ startRetryTimes: zod.z.number().default(() => envVars.ROCK_DEFAULT_START_RETRY_TIMES)
915
1116
  });
916
1117
  function createSandboxConfig(config) {
917
1118
  return SandboxConfigSchema.parse(config ?? {});
@@ -984,8 +1185,100 @@ var Deploy = class {
984
1185
  }
985
1186
  };
986
1187
 
1188
+ // src/utils/shell.ts
1189
+ function shellQuote(str) {
1190
+ if (str === "") {
1191
+ return "''";
1192
+ }
1193
+ return `'${str.replace(/'/g, "'\\''")}'`;
1194
+ }
1195
+ function validateUrl(url, allowedProtocols = ["http:", "https:"]) {
1196
+ try {
1197
+ const parsed = new URL(url);
1198
+ if (!allowedProtocols.includes(parsed.protocol)) {
1199
+ throw new Error(
1200
+ `Protocol ${parsed.protocol} is not allowed. Allowed: ${allowedProtocols.join(", ")}`
1201
+ );
1202
+ }
1203
+ return url.replace(/\/$/, "");
1204
+ } catch (e) {
1205
+ if (e instanceof Error && e.message.includes("Protocol")) {
1206
+ throw e;
1207
+ }
1208
+ throw new Error(`Invalid URL: ${url}. ${e instanceof Error ? e.message : String(e)}`);
1209
+ }
1210
+ }
1211
+ function validateIpAddress(ip) {
1212
+ const pattern = /^(\d{1,3}\.){3}\d{1,3}$/;
1213
+ if (!pattern.test(ip)) {
1214
+ throw new Error(`Invalid IP address format: ${ip}. Expected format: x.x.x.x`);
1215
+ }
1216
+ const octets = ip.split(".");
1217
+ for (const octet of octets) {
1218
+ const value = parseInt(octet, 10);
1219
+ if (value < 0 || value > 255) {
1220
+ throw new Error(`Invalid IP address: ${ip}. Each octet must be 0-255.`);
1221
+ }
1222
+ }
1223
+ return ip;
1224
+ }
1225
+ function validatePath(path) {
1226
+ if (!path.startsWith("/")) {
1227
+ throw new Error(`Path must be absolute: ${path}`);
1228
+ }
1229
+ if (path.includes("..")) {
1230
+ throw new Error(`Path cannot contain ..: ${path}`);
1231
+ }
1232
+ if (/[`$(){};|&<>()]/.test(path)) {
1233
+ throw new Error(`Path contains forbidden characters: ${path}`);
1234
+ }
1235
+ }
1236
+ function validateUsername(username) {
1237
+ if (username.length === 0) {
1238
+ throw new Error("Username cannot be empty");
1239
+ }
1240
+ if (username.startsWith("-")) {
1241
+ throw new Error(`Username starting with dash is not allowed: ${username}`);
1242
+ }
1243
+ if (!/^[a-zA-Z_][a-zA-Z0-9_-]*$/.test(username)) {
1244
+ throw new Error(`Invalid username format: ${username}. Only alphanumeric, underscore, and hyphen allowed.`);
1245
+ }
1246
+ }
1247
+ function validateChmodMode(mode) {
1248
+ if (mode.length === 0) {
1249
+ throw new Error("Mode cannot be empty");
1250
+ }
1251
+ if (/^[0-7]{3,4}$/.test(mode)) {
1252
+ return;
1253
+ }
1254
+ if (/^[ugoa]*[+-=][rwxXstugo]*([,][ugoa]*[+-=][rwxXstugo]*)*$/.test(mode)) {
1255
+ return;
1256
+ }
1257
+ throw new Error(`Invalid chmod mode: ${mode}. Expected octal (e.g., 755) or symbolic (e.g., u+x)`);
1258
+ }
1259
+
987
1260
  // src/sandbox/file_system.ts
988
1261
  var logger3 = initLogger("rock.sandbox.fs");
1262
+ async function createTarGz(sourceDir, outputPath) {
1263
+ const { spawn } = await import('child_process');
1264
+ return new Promise((resolve3, reject) => {
1265
+ const tar = spawn("tar", ["-czf", outputPath, "-C", sourceDir, "."]);
1266
+ let stderr = "";
1267
+ tar.stderr.on("data", (data) => {
1268
+ stderr += data.toString();
1269
+ });
1270
+ tar.on("close", (code) => {
1271
+ if (code === 0) {
1272
+ resolve3();
1273
+ } else {
1274
+ reject(new Error(`tar command failed with code ${code}: ${stderr}`));
1275
+ }
1276
+ });
1277
+ tar.on("error", (err) => {
1278
+ reject(err);
1279
+ });
1280
+ });
1281
+ }
989
1282
  var FileSystem = class {
990
1283
  sandbox;
991
1284
  constructor(sandbox) {
@@ -1001,6 +1294,10 @@ var LinuxFileSystem = class extends FileSystem {
1001
1294
  if (!paths || paths.length === 0) {
1002
1295
  throw new Error("paths is empty");
1003
1296
  }
1297
+ validateUsername(remoteUser);
1298
+ for (const p of paths) {
1299
+ validatePath(p);
1300
+ }
1004
1301
  const command = ["chown"];
1005
1302
  if (recursive) {
1006
1303
  command.push("-R");
@@ -1018,6 +1315,10 @@ var LinuxFileSystem = class extends FileSystem {
1018
1315
  if (!paths || paths.length === 0) {
1019
1316
  throw new Error("paths is empty");
1020
1317
  }
1318
+ validateChmodMode(mode);
1319
+ for (const p of paths) {
1320
+ validatePath(p);
1321
+ }
1021
1322
  const command = ["chmod"];
1022
1323
  if (recursive) {
1023
1324
  command.push("-R");
@@ -1030,27 +1331,128 @@ var LinuxFileSystem = class extends FileSystem {
1030
1331
  }
1031
1332
  return { success: true, message: JSON.stringify(response) };
1032
1333
  }
1033
- async uploadDir(sourceDir, targetDir, _extractTimeout = 600) {
1034
- logger3.info(`uploadDir: ${sourceDir} -> ${targetDir}`);
1035
- return {
1036
- output: `uploaded ${sourceDir} -> ${targetDir}`,
1037
- exitCode: 0,
1038
- failureReason: "",
1039
- expectString: ""
1040
- };
1334
+ async uploadDir(sourceDir, targetDir, extractTimeout = 600) {
1335
+ let localTarPath = null;
1336
+ let remoteTarPath = null;
1337
+ let session = null;
1338
+ try {
1339
+ const src = path.resolve(sourceDir);
1340
+ if (!fs.existsSync(src)) {
1341
+ return {
1342
+ output: "",
1343
+ exitCode: 1,
1344
+ failureReason: `source_dir not found: ${src}`,
1345
+ expectString: ""
1346
+ };
1347
+ }
1348
+ const stats = fs.statSync(src);
1349
+ if (!stats.isDirectory()) {
1350
+ return {
1351
+ output: "",
1352
+ exitCode: 1,
1353
+ failureReason: `source_dir must be a directory: ${src}`,
1354
+ expectString: ""
1355
+ };
1356
+ }
1357
+ try {
1358
+ validatePath(targetDir);
1359
+ } catch (e) {
1360
+ return {
1361
+ output: "",
1362
+ exitCode: 1,
1363
+ failureReason: e instanceof Error ? e.message : "Invalid target directory",
1364
+ expectString: ""
1365
+ };
1366
+ }
1367
+ const ts = Date.now().toString();
1368
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "rock-upload-"));
1369
+ localTarPath = path.join(tmpDir, `rock_upload_${ts}.tar.gz`);
1370
+ remoteTarPath = `/tmp/rock_upload_${ts}.tar.gz`;
1371
+ session = `bash-${ts}`;
1372
+ logger3.info(`uploadDir: ${src} -> ${targetDir}`);
1373
+ await this.sandbox.createSession({ session, startupSource: [], envEnable: false });
1374
+ const checkResult = await this.sandbox.arun("command -v tar >/dev/null 2>&1", {
1375
+ session,
1376
+ mode: RunMode.NORMAL
1377
+ });
1378
+ if (checkResult.exitCode !== 0) {
1379
+ return {
1380
+ output: "",
1381
+ exitCode: 1,
1382
+ failureReason: "sandbox has no tar command; cannot extract tarball",
1383
+ expectString: ""
1384
+ };
1385
+ }
1386
+ try {
1387
+ await createTarGz(src, localTarPath);
1388
+ } catch (e) {
1389
+ throw new Error(`tar pack failed: ${e}`);
1390
+ }
1391
+ const uploadResponse = await this.sandbox.upload({
1392
+ sourcePath: localTarPath,
1393
+ targetPath: remoteTarPath
1394
+ });
1395
+ if (!uploadResponse.success) {
1396
+ return {
1397
+ output: "",
1398
+ exitCode: 1,
1399
+ failureReason: `tar upload failed: ${uploadResponse.message}`,
1400
+ expectString: ""
1401
+ };
1402
+ }
1403
+ const escapedTargetDir = shellQuote(targetDir);
1404
+ const escapedRemoteTar = shellQuote(remoteTarPath);
1405
+ const extractCmd = `rm -rf ${escapedTargetDir} && mkdir -p ${escapedTargetDir} && tar -xzf ${escapedRemoteTar} -C ${escapedTargetDir}`;
1406
+ const extractResult = await this.sandbox.arun(`bash -c ${shellQuote(extractCmd)}`, {
1407
+ session,
1408
+ mode: RunMode.NOHUP,
1409
+ waitTimeout: extractTimeout
1410
+ });
1411
+ if (extractResult.exitCode !== 0) {
1412
+ return {
1413
+ output: "",
1414
+ exitCode: 1,
1415
+ failureReason: `tar extract failed: ${extractResult.output}`,
1416
+ expectString: ""
1417
+ };
1418
+ }
1419
+ try {
1420
+ await this.sandbox.execute({ command: ["rm", "-f", remoteTarPath], timeout: 30 });
1421
+ } catch {
1422
+ }
1423
+ return {
1424
+ output: `uploaded ${src} -> ${targetDir} via tar`,
1425
+ exitCode: 0,
1426
+ failureReason: "",
1427
+ expectString: ""
1428
+ };
1429
+ } catch (e) {
1430
+ return {
1431
+ output: "",
1432
+ exitCode: 1,
1433
+ failureReason: `upload_dir unexpected error: ${e}`,
1434
+ expectString: ""
1435
+ };
1436
+ } finally {
1437
+ try {
1438
+ if (localTarPath) {
1439
+ const tmpDir = path.basename(localTarPath).replace(/\.tar\.gz$/, "");
1440
+ fs.rmSync(path.join(os.tmpdir(), `rock-upload-${tmpDir.split("_").pop()}`), { recursive: true, force: true });
1441
+ }
1442
+ } catch {
1443
+ }
1444
+ }
1041
1445
  }
1042
1446
  };
1043
1447
 
1044
- // src/sandbox/types.ts
1448
+ // src/sandbox/network.ts
1449
+ var logger4 = initLogger("rock.sandbox.network");
1045
1450
  var SpeedupType = /* @__PURE__ */ ((SpeedupType2) => {
1046
1451
  SpeedupType2["APT"] = "apt";
1047
1452
  SpeedupType2["PIP"] = "pip";
1048
1453
  SpeedupType2["GITHUB"] = "github";
1049
1454
  return SpeedupType2;
1050
1455
  })(SpeedupType || {});
1051
-
1052
- // src/sandbox/network.ts
1053
- var logger4 = initLogger("rock.sandbox.network");
1054
1456
  var Network = class {
1055
1457
  sandbox;
1056
1458
  constructor(sandbox) {
@@ -1069,45 +1471,179 @@ var Network = class {
1069
1471
  logger4.info(
1070
1472
  `[${sandboxId}] Configuring ${speedupType} speedup: ${speedupValue}`
1071
1473
  );
1072
- let command;
1474
+ let validatedValue;
1073
1475
  switch (speedupType) {
1074
1476
  case "apt" /* APT */:
1075
- command = this.buildAptSpeedupCommand(speedupValue);
1076
- break;
1077
1477
  case "pip" /* PIP */:
1078
- command = this.buildPipSpeedupCommand(speedupValue);
1478
+ validatedValue = validateUrl(speedupValue);
1079
1479
  break;
1080
1480
  case "github" /* GITHUB */:
1081
- command = this.buildGithubSpeedupCommand(speedupValue);
1481
+ validatedValue = validateIpAddress(speedupValue);
1082
1482
  break;
1083
1483
  default:
1084
1484
  throw new Error(`Unsupported speedup type: ${speedupType}`);
1085
1485
  }
1086
- const result = await this.sandbox.arun(command, {
1087
- mode: "nohup",
1486
+ const scriptContent = this.generateSpeedupScript(speedupType, validatedValue);
1487
+ const result = await this.sandbox.getProcess().executeScript({
1488
+ scriptContent,
1088
1489
  waitTimeout: timeout
1089
1490
  });
1090
1491
  return result;
1091
1492
  }
1092
- buildAptSpeedupCommand(mirrorUrl) {
1093
- return `cat > /etc/apt/sources.list << 'EOF'
1094
- deb ${mirrorUrl} $(lsb_release -cs) main restricted universe multiverse
1095
- deb ${mirrorUrl} $(lsb_release -cs)-updates main restricted universe multiverse
1096
- deb ${mirrorUrl} $(lsb_release -cs)-backports main restricted universe multiverse
1097
- deb ${mirrorUrl} $(lsb_release -cs)-security main restricted universe multiverse
1493
+ /**
1494
+ * Generate speedup script content based on type
1495
+ */
1496
+ generateSpeedupScript(speedupType, value) {
1497
+ switch (speedupType) {
1498
+ case "apt" /* APT */:
1499
+ return this.buildAptSpeedupScript(value);
1500
+ case "pip" /* PIP */:
1501
+ return this.buildPipSpeedupScript(value);
1502
+ case "github" /* GITHUB */:
1503
+ return this.buildGithubSpeedupScript(value);
1504
+ default:
1505
+ throw new Error(`Unsupported speedup type: ${speedupType}`);
1506
+ }
1507
+ }
1508
+ /**
1509
+ * Build APT speedup script
1510
+ * Uses script file approach for safety
1511
+ */
1512
+ buildAptSpeedupScript(mirrorUrl) {
1513
+ return `#!/bin/bash
1514
+ detect_system_and_version() {
1515
+ if [ -f /etc/debian_version ]; then
1516
+ . /etc/os-release
1517
+ if [ "$ID" = "ubuntu" ]; then
1518
+ echo "ubuntu:$VERSION_CODENAME"
1519
+ elif [ "$ID" = "debian" ]; then
1520
+ echo "debian:$VERSION_CODENAME"
1521
+ else
1522
+ echo "unknown:"
1523
+ fi
1524
+ else
1525
+ echo "unknown:"
1526
+ fi
1527
+ }
1528
+
1529
+ SYSTEM_INFO=$(detect_system_and_version)
1530
+ SYSTEM=$(echo "$SYSTEM_INFO" | cut -d: -f1)
1531
+ CODENAME=$(echo "$SYSTEM_INFO" | cut -d: -f2)
1532
+ echo "System type: $SYSTEM, Version codename: $CODENAME"
1533
+
1534
+ # Backup original sources file
1535
+ if [ ! -f /etc/apt/sources.list.backup ]; then
1536
+ cp /etc/apt/sources.list /etc/apt/sources.list.backup
1537
+ fi
1538
+
1539
+ if [ "$SYSTEM" = "debian" ]; then
1540
+ if [ -z "$CODENAME" ]; then
1541
+ CODENAME="bookworm"
1542
+ fi
1543
+ cat > /etc/apt/sources.list <<EOF
1544
+ deb ${mirrorUrl}/debian/ \${CODENAME} main non-free non-free-firmware contrib
1545
+ deb ${mirrorUrl}/debian-security/ \${CODENAME}-security main
1546
+ deb ${mirrorUrl}/debian/ \${CODENAME}-updates main non-free non-free-firmware contrib
1098
1547
  EOF
1099
- apt-get update`;
1548
+ elif [ "$SYSTEM" = "ubuntu" ]; then
1549
+ if [ -z "$CODENAME" ]; then
1550
+ if [ -f /etc/os-release ]; then
1551
+ VERSION_ID=$(grep VERSION_ID /etc/os-release | cut -d'"' -f2)
1552
+ case "$VERSION_ID" in
1553
+ "24.04") CODENAME="noble" ;;
1554
+ "22.04") CODENAME="jammy" ;;
1555
+ "20.04") CODENAME="focal" ;;
1556
+ *) CODENAME="noble" ;;
1557
+ esac
1558
+ else
1559
+ CODENAME="noble"
1560
+ fi
1561
+ fi
1562
+ cat > /etc/apt/sources.list <<EOF
1563
+ deb ${mirrorUrl}/ubuntu/ $CODENAME main restricted universe multiverse
1564
+ deb ${mirrorUrl}/ubuntu/ $CODENAME-security main restricted universe multiverse
1565
+ deb ${mirrorUrl}/ubuntu/ $CODENAME-updates main restricted universe multiverse
1566
+ deb ${mirrorUrl}/ubuntu/ $CODENAME-backports main restricted universe multiverse
1567
+ EOF
1568
+ fi
1569
+
1570
+ # Clean up other source files
1571
+ rm -rf /etc/apt/sources.list.d
1572
+
1573
+ # Clean APT cache and update
1574
+ apt-get clean
1575
+ rm -rf /var/lib/apt/lists/*
1576
+ echo ">>> APT source configuration completed"
1577
+ `;
1100
1578
  }
1101
- buildPipSpeedupCommand(mirrorUrl) {
1102
- const safeUrl = mirrorUrl.replace(/'/g, "'\\''");
1103
- return `mkdir -p ~/.pip && cat > ~/.pip/pip.conf << 'EOF'
1579
+ /**
1580
+ * Build PIP speedup script
1581
+ * Uses script file approach for safety
1582
+ */
1583
+ buildPipSpeedupScript(mirrorUrl) {
1584
+ const parsed = new URL(mirrorUrl);
1585
+ const trustedHost = parsed.host;
1586
+ const indexUrl = `${mirrorUrl}/pypi/simple/`;
1587
+ return `#!/bin/bash
1588
+ echo ">>> Configuring pip source..."
1589
+
1590
+ # Configure for root user
1591
+ mkdir -p /root/.pip
1592
+ cat > /root/.pip/pip.conf <<EOF
1104
1593
  [global]
1105
- index-url = ${safeUrl}
1106
- trusted-host = $(echo ${safeUrl} | sed 's|https\\?://||' | cut -d'/' -f1)
1107
- EOF`;
1594
+ index-url = ${indexUrl}
1595
+ trusted-host = ${trustedHost}
1596
+ timeout = 120
1597
+
1598
+ [install]
1599
+ trusted-host = ${trustedHost}
1600
+ EOF
1601
+
1602
+ # Configure for other existing users
1603
+ for home_dir in /home/*; do
1604
+ if [ -d "$home_dir" ]; then
1605
+ username=$(basename "$home_dir")
1606
+ mkdir -p "$home_dir/.pip"
1607
+ cat > "$home_dir/.pip/pip.conf" <<EOF
1608
+ [global]
1609
+ index-url = ${indexUrl}
1610
+ trusted-host = ${trustedHost}
1611
+ timeout = 120
1612
+
1613
+ [install]
1614
+ trusted-host = ${trustedHost}
1615
+ EOF
1616
+ chown -R "$username:$username" "$home_dir/.pip" 2>/dev/null || true
1617
+ fi
1618
+ done
1619
+
1620
+ echo ">>> pip source configuration completed"
1621
+ `;
1108
1622
  }
1109
- buildGithubSpeedupCommand(ipAddress) {
1110
- return `echo "${ipAddress} github.com" >> /etc/hosts`;
1623
+ /**
1624
+ * Build GitHub speedup script
1625
+ * Uses script file approach for safety
1626
+ */
1627
+ buildGithubSpeedupScript(ipAddress) {
1628
+ return `#!/bin/bash
1629
+ echo ">>> Configuring GitHub hosts for github.com acceleration..."
1630
+
1631
+ # Backup original hosts file if not already backed up
1632
+ if [ ! -f /etc/hosts.backup ]; then
1633
+ cp /etc/hosts /etc/hosts.backup
1634
+ echo "Hosts file backed up to /etc/hosts.backup"
1635
+ fi
1636
+
1637
+ # Remove existing github.com entry if any
1638
+ sed -i '/github.com$/d' /etc/hosts
1639
+
1640
+ # Add new github.com hosts entry
1641
+ echo "${ipAddress} github.com" | tee -a /etc/hosts
1642
+
1643
+ echo ">>> GitHub hosts configuration completed"
1644
+ echo "Current github.com entry in /etc/hosts:"
1645
+ grep 'github.com$' /etc/hosts || echo "No github.com entry found"
1646
+ `;
1111
1647
  }
1112
1648
  };
1113
1649
 
@@ -1299,16 +1835,6 @@ async function arunWithRetry(sandbox, cmd, session, mode, options = {}) {
1299
1835
  }
1300
1836
  throw lastError ?? new Error(`${errorMsg}: all attempts failed`);
1301
1837
  }
1302
- function extractNohupPid(output) {
1303
- const PID_PREFIX2 = "__ROCK_PID_START__";
1304
- const PID_SUFFIX2 = "__ROCK_PID_END__";
1305
- const pattern = new RegExp(`${PID_PREFIX2}(\\d+)${PID_SUFFIX2}`);
1306
- const match = output.match(pattern);
1307
- if (match?.[1]) {
1308
- return parseInt(match[1], 10);
1309
- }
1310
- return null;
1311
- }
1312
1838
 
1313
1839
  // src/sandbox/client.ts
1314
1840
  var logger8 = initLogger("rock.sandbox");
@@ -1415,45 +1941,45 @@ var Sandbox = class extends AbstractSandbox {
1415
1941
  };
1416
1942
  logger8.debug(`Calling start_async API: ${url}`);
1417
1943
  logger8.debug(`Request data: ${JSON.stringify(data)}`);
1418
- try {
1419
- const response = await HttpUtils.post(
1420
- url,
1421
- headers,
1422
- data
1423
- );
1424
- logger8.debug(`Start sandbox response: ${JSON.stringify(response)}`);
1425
- if (response.status !== "Success") {
1426
- throw new Error(`Failed to start sandbox: ${JSON.stringify(response)}`);
1427
- }
1428
- this.sandboxId = response.result?.sandboxId ?? null;
1429
- this.hostName = response.result?.hostName ?? null;
1430
- this.hostIp = response.result?.hostIp ?? null;
1431
- logger8.info(`Sandbox ID: ${this.sandboxId}`);
1432
- await sleep(2e3);
1433
- const startTime = Date.now();
1434
- const checkTimeout = 1e4;
1435
- const checkInterval = 3e3;
1436
- while (Date.now() - startTime < this.config.startupTimeout * 1e3) {
1437
- try {
1438
- logger8.info(`Checking status... (elapsed: ${Math.round((Date.now() - startTime) / 1e3)}s)`);
1439
- const statusPromise = this.getStatus();
1440
- const timeoutPromise = new Promise(
1441
- (_, reject) => setTimeout(() => reject(new Error("Status check timeout")), checkTimeout)
1442
- );
1443
- const status = await Promise.race([statusPromise, timeoutPromise]);
1444
- if (status && status.isAlive) {
1445
- logger8.info("Sandbox is alive");
1446
- return;
1447
- }
1448
- } catch (e) {
1449
- logger8.debug(`Status check failed (will retry): ${e}`);
1944
+ const response = await HttpUtils.post(
1945
+ url,
1946
+ headers,
1947
+ data
1948
+ );
1949
+ logger8.debug(`Start sandbox response: ${JSON.stringify(response)}`);
1950
+ if (response.status !== "Success") {
1951
+ const code = response.result?.code;
1952
+ raiseForCode(code, `Failed to start sandbox: ${JSON.stringify(response)}`);
1953
+ throw new Error(`Failed to start sandbox: ${JSON.stringify(response)}`);
1954
+ }
1955
+ this.sandboxId = response.result?.sandboxId ?? null;
1956
+ this.hostName = response.result?.hostName ?? null;
1957
+ this.hostIp = response.result?.hostIp ?? null;
1958
+ logger8.info(`Sandbox ID: ${this.sandboxId}`);
1959
+ await sleep(2e3);
1960
+ const startTime = Date.now();
1961
+ const checkTimeout = 1e4;
1962
+ const checkInterval = 3e3;
1963
+ while (Date.now() - startTime < this.config.startupTimeout * 1e3) {
1964
+ try {
1965
+ logger8.info(`Checking status... (elapsed: ${Math.round((Date.now() - startTime) / 1e3)}s)`);
1966
+ const statusPromise = this.getStatus();
1967
+ const timeoutPromise = new Promise(
1968
+ (_, reject) => setTimeout(() => reject(new Error("Status check timeout")), checkTimeout)
1969
+ );
1970
+ const status = await Promise.race([statusPromise, timeoutPromise]);
1971
+ if (status && status.isAlive) {
1972
+ logger8.info("Sandbox is alive");
1973
+ return;
1450
1974
  }
1451
- await sleep(checkInterval);
1975
+ } catch (e) {
1976
+ logger8.debug(`Status check failed (will retry): ${e}`);
1452
1977
  }
1453
- throw new Error(`Failed to start sandbox within ${this.config.startupTimeout}s`);
1454
- } catch (e) {
1455
- throw new Error(`Failed to start sandbox: ${e}`);
1978
+ await sleep(checkInterval);
1456
1979
  }
1980
+ throw new InternalServerRockError(
1981
+ `Failed to start sandbox within ${this.config.startupTimeout}s, sandbox: ${this.toString()}`
1982
+ );
1457
1983
  }
1458
1984
  async stop() {
1459
1985
  if (!this.sandboxId) return;
@@ -1468,10 +1994,10 @@ var Sandbox = class extends AbstractSandbox {
1468
1994
  async isAlive() {
1469
1995
  try {
1470
1996
  const status = await this.getStatus();
1471
- return {
1997
+ return IsAliveResponseSchema.parse({
1472
1998
  isAlive: status.isAlive,
1473
1999
  message: status.hostName ?? ""
1474
- };
2000
+ });
1475
2001
  } catch (e) {
1476
2002
  throw new Error(`Failed to get is alive: ${e}`);
1477
2003
  }
@@ -1482,9 +2008,11 @@ var Sandbox = class extends AbstractSandbox {
1482
2008
  const response = await HttpUtils.get(url, headers);
1483
2009
  if (response.status !== "Success") {
1484
2010
  const errorDetail = response.error ? `, error=${response.error}` : "";
2011
+ const code = response.result?.code;
2012
+ raiseForCode(code, `Failed to get status: status=${response.status}${errorDetail}, result=${JSON.stringify(response.result)}`);
1485
2013
  throw new Error(`Failed to get status: status=${response.status}${errorDetail}, result=${JSON.stringify(response.result)}`);
1486
2014
  }
1487
- const result = response.result;
2015
+ const result = SandboxStatusResponseSchema.parse(response.result);
1488
2016
  result.cluster = response.headers["x-rock-gateway-target-cluster"] || this.config.cluster || "N/A";
1489
2017
  result.requestId = response.headers["x-request-id"] || response.headers["request-id"] || "N/A";
1490
2018
  result.eagleeyeTraceid = response.headers["eagleeye-traceid"] || "N/A";
@@ -1501,20 +2029,18 @@ var Sandbox = class extends AbstractSandbox {
1501
2029
  cwd: command.cwd,
1502
2030
  env: command.env
1503
2031
  };
1504
- try {
1505
- const response = await HttpUtils.post(
1506
- url,
1507
- headers,
1508
- data
1509
- );
1510
- if (response.status !== "Success") {
1511
- const errorDetail = response.error ? `, error=${response.error}` : "";
1512
- throw new Error(`Failed to execute command: status=${response.status}${errorDetail}, result=${JSON.stringify(response.result)}`);
1513
- }
1514
- return response.result;
1515
- } catch (e) {
1516
- throw new Error(`Failed to execute command: ${e}`);
2032
+ const response = await HttpUtils.post(
2033
+ url,
2034
+ headers,
2035
+ data
2036
+ );
2037
+ if (response.status !== "Success") {
2038
+ const errorDetail = response.error ? `, error=${response.error}` : "";
2039
+ const code = response.result?.code;
2040
+ raiseForCode(code, `Failed to execute command: status=${response.status}${errorDetail}, result=${JSON.stringify(response.result)}`);
2041
+ throw new Error(`Failed to execute command: status=${response.status}${errorDetail}, result=${JSON.stringify(response.result)}`);
1517
2042
  }
2043
+ return CommandResponseSchema.parse(response.result);
1518
2044
  }
1519
2045
  // Session management
1520
2046
  async createSession(request) {
@@ -1524,20 +2050,18 @@ var Sandbox = class extends AbstractSandbox {
1524
2050
  sandboxId: this.sandboxId,
1525
2051
  ...request
1526
2052
  };
1527
- try {
1528
- const response = await HttpUtils.post(
1529
- url,
1530
- headers,
1531
- data
1532
- );
1533
- if (response.status !== "Success") {
1534
- const errorDetail = response.error ? `, error=${response.error}` : "";
1535
- throw new Error(`Failed to create session: status=${response.status}${errorDetail}, result=${JSON.stringify(response.result)}`);
1536
- }
1537
- return response.result;
1538
- } catch (e) {
1539
- throw new Error(`Failed to create session: ${e}`);
2053
+ const response = await HttpUtils.post(
2054
+ url,
2055
+ headers,
2056
+ data
2057
+ );
2058
+ if (response.status !== "Success") {
2059
+ const errorDetail = response.error ? `, error=${response.error}` : "";
2060
+ const code = response.result?.code;
2061
+ raiseForCode(code, `Failed to create session: status=${response.status}${errorDetail}, result=${JSON.stringify(response.result)}`);
2062
+ throw new Error(`Failed to create session: status=${response.status}${errorDetail}, result=${JSON.stringify(response.result)}`);
1540
2063
  }
2064
+ return CreateSessionResponseSchema.parse(response.result);
1541
2065
  }
1542
2066
  async closeSession(request) {
1543
2067
  const url = `${this.url}/close_session`;
@@ -1546,20 +2070,18 @@ var Sandbox = class extends AbstractSandbox {
1546
2070
  sandboxId: this.sandboxId,
1547
2071
  ...request
1548
2072
  };
1549
- try {
1550
- const response = await HttpUtils.post(
1551
- url,
1552
- headers,
1553
- data
1554
- );
1555
- if (response.status !== "Success") {
1556
- const errorDetail = response.error ? `, error=${response.error}` : "";
1557
- throw new Error(`Failed to close session: status=${response.status}${errorDetail}, result=${JSON.stringify(response.result)}`);
1558
- }
1559
- return response.result ?? { sessionType: "bash" };
1560
- } catch (e) {
1561
- throw new Error(`Failed to close session: ${e}`);
2073
+ const response = await HttpUtils.post(
2074
+ url,
2075
+ headers,
2076
+ data
2077
+ );
2078
+ if (response.status !== "Success") {
2079
+ const errorDetail = response.error ? `, error=${response.error}` : "";
2080
+ const code = response.result?.code;
2081
+ raiseForCode(code, `Failed to close session: status=${response.status}${errorDetail}, result=${JSON.stringify(response.result)}`);
2082
+ throw new Error(`Failed to close session: status=${response.status}${errorDetail}, result=${JSON.stringify(response.result)}`);
1562
2083
  }
2084
+ return CloseSessionResponseSchema.parse(response.result ?? {});
1563
2085
  }
1564
2086
  // Run command in session
1565
2087
  async arun(cmd, options = {}) {
@@ -1570,13 +2092,6 @@ var Sandbox = class extends AbstractSandbox {
1570
2092
  } = options;
1571
2093
  const sessionName = session ?? "default";
1572
2094
  if (mode === "normal") {
1573
- try {
1574
- await this.createSession({ session: sessionName, startupSource: [], envEnable: false });
1575
- } catch (e) {
1576
- if (String(e).includes("already exists")) ; else {
1577
- throw e;
1578
- }
1579
- }
1580
2095
  return this.runInSession({ command: cmd, session: sessionName, timeout });
1581
2096
  }
1582
2097
  return this.arunWithNohup(cmd, options);
@@ -1591,22 +2106,20 @@ var Sandbox = class extends AbstractSandbox {
1591
2106
  sandboxId: this.sandboxId,
1592
2107
  timeout: action.timeout
1593
2108
  };
1594
- try {
1595
- const timeoutMs = action.timeout ? action.timeout * 1e3 : void 0;
1596
- const response = await HttpUtils.post(
1597
- url,
1598
- headers,
1599
- data,
1600
- timeoutMs
1601
- );
1602
- if (response.status !== "Success") {
1603
- const errorDetail = response.error ? `, error=${response.error}` : "";
1604
- throw new Error(`Failed to run in session: status=${response.status}${errorDetail}, result=${JSON.stringify(response.result)}`);
1605
- }
1606
- return response.result;
1607
- } catch (e) {
1608
- throw new Error(`Failed to run in session: ${e}`);
2109
+ const timeoutMs = action.timeout ? action.timeout * 1e3 : void 0;
2110
+ const response = await HttpUtils.post(
2111
+ url,
2112
+ headers,
2113
+ data,
2114
+ timeoutMs
2115
+ );
2116
+ if (response.status !== "Success") {
2117
+ const errorDetail = response.error ? `, error=${response.error}` : "";
2118
+ const code = response.result?.code;
2119
+ raiseForCode(code, `Failed to run in session: status=${response.status}${errorDetail}, result=${JSON.stringify(response.result)}`);
2120
+ throw new Error(`Failed to run in session: status=${response.status}${errorDetail}, result=${JSON.stringify(response.result)}`);
1609
2121
  }
2122
+ return ObservationSchema.parse(response.result);
1610
2123
  }
1611
2124
  async arunWithNohup(cmd, options) {
1612
2125
  const {
@@ -1618,13 +2131,12 @@ var Sandbox = class extends AbstractSandbox {
1618
2131
  outputFile
1619
2132
  } = options;
1620
2133
  const timestamp = Date.now();
1621
- const tmpSession = session ?? `bash-${timestamp}`;
1622
- try {
2134
+ let tmpSession;
2135
+ if (session === void 0 || session === null) {
2136
+ tmpSession = `bash-${timestamp}`;
1623
2137
  await this.createSession({ session: tmpSession, startupSource: [], envEnable: false });
1624
- } catch (e) {
1625
- if (!String(e).includes("already exists")) {
1626
- throw e;
1627
- }
2138
+ } else {
2139
+ tmpSession = session;
1628
2140
  }
1629
2141
  const tmpFile = outputFile ?? `/tmp/tmp_${timestamp}.out`;
1630
2142
  const nohupCommand = `nohup ${cmd} < /dev/null > ${tmpFile} 2>&1 & echo __ROCK_PID_START__$!__ROCK_PID_END__;disown`;
@@ -1717,7 +2229,7 @@ var Sandbox = class extends AbstractSandbox {
1717
2229
  headers,
1718
2230
  data
1719
2231
  );
1720
- return { content: response.result?.content ?? "" };
2232
+ return ReadFileResponseSchema.parse(response.result ?? {});
1721
2233
  }
1722
2234
  // Upload
1723
2235
  async upload(request) {
@@ -1727,11 +2239,13 @@ var Sandbox = class extends AbstractSandbox {
1727
2239
  const url = `${this.url}/upload`;
1728
2240
  const headers = this.buildHeaders();
1729
2241
  try {
1730
- const fs = await import('fs');
1731
- if (!fs.existsSync(sourcePath)) {
2242
+ const fs = await import('fs/promises');
2243
+ try {
2244
+ await fs.access(sourcePath);
2245
+ } catch {
1732
2246
  return { success: false, message: `File not found: ${sourcePath}` };
1733
2247
  }
1734
- const fileBuffer = fs.readFileSync(sourcePath);
2248
+ const fileBuffer = await fs.readFile(sourcePath);
1735
2249
  const fileName = sourcePath.split("/").pop() ?? "file";
1736
2250
  const response = await HttpUtils.postMultipart(
1737
2251
  url,
@@ -1801,6 +2315,7 @@ var SandboxGroup = class {
1801
2315
  }
1802
2316
  };
1803
2317
  var logger9 = initLogger("rock.model.client");
2318
+ var DEFAULT_POLL_TIMEOUT = 60;
1804
2319
  var REQUEST_START_MARKER = "__REQUEST_START__";
1805
2320
  var REQUEST_END_MARKER = "__REQUEST_END__";
1806
2321
  var RESPONSE_START_MARKER = "__RESPONSE_START__";
@@ -1838,7 +2353,7 @@ var ModelClient = class {
1838
2353
  const content = this.constructResponse(lastResponse, index);
1839
2354
  const lastResponseLine = await this.readLastResponseLine();
1840
2355
  if (lastResponseLine === null) {
1841
- this.appendResponse(content);
2356
+ await this.appendResponse(content);
1842
2357
  return;
1843
2358
  }
1844
2359
  const { meta } = this.parseResponseLine(lastResponseLine);
@@ -1850,42 +2365,77 @@ var ModelClient = class {
1850
2365
  logger9.debug(`response index ${index} already exists, skip`);
1851
2366
  return;
1852
2367
  }
1853
- this.appendResponse(content);
2368
+ await this.appendResponse(content);
1854
2369
  }
1855
2370
  /**
1856
2371
  * Pop request from log file
2372
+ *
2373
+ * @param index - The index of the request to pop
2374
+ * @param options - Optional configuration for timeout and cancellation
2375
+ * @returns The request JSON string or SESSION_END_MARKER
2376
+ * @throws Error if timeout expires or operation is aborted
1857
2377
  */
1858
- // eslint-disable-next-line no-constant-condition
1859
- async popRequest(index) {
2378
+ async popRequest(index, options) {
2379
+ const timeout = options?.timeout ?? DEFAULT_POLL_TIMEOUT;
2380
+ const startTime = Date.now();
1860
2381
  while (true) {
1861
- const lastRequestLine = await this.readLastRequestLine();
1862
- const { requestJson, meta } = this.parseRequestLine(lastRequestLine);
1863
- if (requestJson === SESSION_END_MARKER) {
1864
- return SESSION_END_MARKER;
2382
+ if (options?.signal?.aborted) {
2383
+ throw new Error(`popRequest(index=${index}) aborted`);
1865
2384
  }
1866
- if (meta.index === index) {
1867
- return requestJson;
2385
+ if ((Date.now() - startTime) / 1e3 > timeout) {
2386
+ throw new Error(`popRequest timed out after ${timeout} seconds`);
2387
+ }
2388
+ try {
2389
+ const lastRequestLine = await this.readLastRequestLine();
2390
+ const { requestJson, meta } = this.parseRequestLine(lastRequestLine);
2391
+ if (requestJson === SESSION_END_MARKER) {
2392
+ return SESSION_END_MARKER;
2393
+ }
2394
+ if (meta.index === index) {
2395
+ return requestJson;
2396
+ }
2397
+ logger9.debug(`Last request is not the index ${index} we want, waiting...`);
2398
+ await sleep(1e3);
2399
+ } catch (e) {
2400
+ if (e instanceof Error) {
2401
+ if (e.message.includes("aborted")) {
2402
+ throw e;
2403
+ }
2404
+ if (e.message.includes("Invalid request line format")) {
2405
+ throw e;
2406
+ }
2407
+ }
2408
+ logger9.debug(`Error reading request: ${e}, waiting...`);
2409
+ await sleep(1e3);
1868
2410
  }
1869
- logger9.debug(`Last request is not the index ${index} we want, waiting...`);
1870
- await this.sleep(1e3);
1871
2411
  }
1872
2412
  }
1873
2413
  /**
1874
2414
  * Wait for first request
2415
+ *
2416
+ * @param options - Optional configuration for timeout and cancellation
2417
+ * @throws Error if timeout expires or operation is aborted
1875
2418
  */
1876
- // eslint-disable-next-line no-constant-condition
1877
- async waitForFirstRequest() {
2419
+ async waitForFirstRequest(options) {
2420
+ const timeout = options?.timeout ?? DEFAULT_POLL_TIMEOUT;
2421
+ const startTime = Date.now();
1878
2422
  while (true) {
2423
+ if (options?.signal?.aborted) {
2424
+ throw new Error("waitForFirstRequest aborted");
2425
+ }
2426
+ if ((Date.now() - startTime) / 1e3 > timeout) {
2427
+ throw new Error(`waitForFirstRequest timed out after ${timeout} seconds`);
2428
+ }
1879
2429
  if (!fs.existsSync(this.logFile)) {
1880
2430
  logger9.debug(`Log file ${this.logFile} not found, waiting...`);
1881
- await this.sleep(1e3);
2431
+ await sleep(1e3);
1882
2432
  continue;
1883
2433
  }
1884
- const content = fs.readFileSync(this.logFile, "utf-8");
2434
+ const content = await promises.readFile(this.logFile, "utf-8");
1885
2435
  const lines = content.split("\n").filter((l) => l.trim());
1886
2436
  if (lines.length === 0) {
1887
2437
  logger9.debug(`Log file ${this.logFile} is empty, waiting for the first request...`);
1888
- await this.sleep(1e3);
2438
+ await sleep(1e3);
1889
2439
  continue;
1890
2440
  }
1891
2441
  return;
@@ -1895,21 +2445,31 @@ var ModelClient = class {
1895
2445
  if (lineContent.includes(SESSION_END_MARKER)) {
1896
2446
  return { requestJson: SESSION_END_MARKER, meta: {} };
1897
2447
  }
1898
- const parts = lineContent.split(REQUEST_END_MARKER);
1899
- const metaJson = parts[1] ?? "";
1900
- const requestJson = parts[0]?.split(REQUEST_START_MARKER)[1] ?? "";
1901
- const meta = JSON.parse(metaJson);
1902
- return { requestJson, meta };
2448
+ try {
2449
+ const parts = lineContent.split(REQUEST_END_MARKER);
2450
+ const metaJson = parts[1] ?? "";
2451
+ const requestJson = parts[0]?.split(REQUEST_START_MARKER)[1] ?? "";
2452
+ const meta = JSON.parse(metaJson);
2453
+ return { requestJson, meta };
2454
+ } catch (e) {
2455
+ logger9.error(`Failed to parse request line: ${lineContent}, error: ${e}`);
2456
+ throw new Error(`Invalid request line format: ${e}`);
2457
+ }
1903
2458
  }
1904
2459
  parseResponseLine(lineContent) {
1905
- const parts = lineContent.split(RESPONSE_END_MARKER);
1906
- const metaJson = parts[1] ?? "";
1907
- const responseJson = parts[0]?.split(RESPONSE_START_MARKER)[1] ?? "";
1908
- const meta = JSON.parse(metaJson);
1909
- return { responseJson, meta };
2460
+ try {
2461
+ const parts = lineContent.split(RESPONSE_END_MARKER);
2462
+ const metaJson = parts[1] ?? "";
2463
+ const responseJson = parts[0]?.split(RESPONSE_START_MARKER)[1] ?? "";
2464
+ const meta = JSON.parse(metaJson);
2465
+ return { responseJson, meta };
2466
+ } catch (e) {
2467
+ logger9.error(`Failed to parse response line: ${lineContent}, error: ${e}`);
2468
+ throw new Error(`Invalid response line format: ${e}`);
2469
+ }
1910
2470
  }
1911
2471
  async readLastRequestLine() {
1912
- const content = fs.readFileSync(this.logFile, "utf-8");
2472
+ const content = await promises.readFile(this.logFile, "utf-8");
1913
2473
  const lines = content.split("\n").filter((l) => l.trim());
1914
2474
  for (let i = lines.length - 1; i >= 0; i--) {
1915
2475
  const line = lines[i];
@@ -1920,7 +2480,7 @@ var ModelClient = class {
1920
2480
  throw new Error(`No request found in log file ${this.logFile}`);
1921
2481
  }
1922
2482
  async readLastResponseLine() {
1923
- const content = fs.readFileSync(this.logFile, "utf-8");
2483
+ const content = await promises.readFile(this.logFile, "utf-8");
1924
2484
  const lines = content.split("\n").filter((l) => l.trim());
1925
2485
  for (let i = lines.length - 1; i >= 0; i--) {
1926
2486
  const line = lines[i];
@@ -1930,8 +2490,8 @@ var ModelClient = class {
1930
2490
  }
1931
2491
  return null;
1932
2492
  }
1933
- appendResponse(content) {
1934
- fs.appendFileSync(this.logFile, content);
2493
+ async appendResponse(content) {
2494
+ await promises.appendFile(this.logFile, content);
1935
2495
  }
1936
2496
  constructResponse(lastResponse, index) {
1937
2497
  const meta = {
@@ -1942,114 +2502,711 @@ var ModelClient = class {
1942
2502
  return `${RESPONSE_START_MARKER}${lastResponse}${RESPONSE_END_MARKER}${metaJson}
1943
2503
  `;
1944
2504
  }
1945
- sleep(ms) {
1946
- return new Promise((resolve3) => setTimeout(resolve3, ms));
1947
- }
1948
2505
  };
1949
- var ModelService = class {
1950
- process = null;
2506
+ var RuntimeEnvConfigSchema = zod.z.object({
2507
+ /** Runtime type discriminator. */
2508
+ type: zod.z.string(),
2509
+ /** Runtime version. Use 'default' for the default version of each runtime. */
2510
+ version: zod.z.string().default("default"),
2511
+ /** Environment variables for the runtime session. */
2512
+ env: zod.z.record(zod.z.string()).default({}),
2513
+ /** Timeout in seconds for installation commands. */
2514
+ installTimeout: zod.z.number().int().positive().default(600),
2515
+ /** Custom install command to run after init. */
2516
+ customInstallCmd: zod.z.string().nullable().default(null),
2517
+ /** Directory to create symlinks of executables. If null, no symlinks are created. */
2518
+ extraSymlinkDir: zod.z.string().nullable().default(null),
2519
+ /** List of executable names to symlink. Empty list means no symlinks. */
2520
+ extraSymlinkExecutables: zod.z.array(zod.z.string()).default([])
2521
+ });
2522
+ var logger10 = initLogger("rock.sandbox.runtime_env");
2523
+ function createRuntimeEnvId() {
2524
+ return crypto.randomUUID().slice(0, 8);
2525
+ }
2526
+ var RUNTIME_ENV_REGISTRY = {};
2527
+ var RuntimeEnv = class {
2528
+ /** Registry for subclasses */
2529
+ static registry = RUNTIME_ENV_REGISTRY;
2530
+ /** Sandbox instance */
2531
+ _sandbox;
2532
+ /** Configuration */
2533
+ _config;
2534
+ /** Version */
2535
+ _version;
2536
+ /** Environment variables */
2537
+ _env;
2538
+ /** Install timeout in seconds */
2539
+ _installTimeout;
2540
+ /** Custom install command */
2541
+ _customInstallCmd;
2542
+ /** Extra symlink directory */
2543
+ _extraSymlinkDir;
2544
+ /** Extra symlink executables */
2545
+ _extraSymlinkExecutables;
2546
+ /** Unique ID for this runtime env instance */
2547
+ _runtimeEnvId;
2548
+ /** Working directory */
2549
+ _workdir;
2550
+ /** Session name */
2551
+ _session;
2552
+ /** Whether the runtime has been initialized */
2553
+ _initialized = false;
2554
+ /** Whether the session is ready */
2555
+ _sessionReady = false;
2556
+ constructor(sandbox, config) {
2557
+ this._sandbox = sandbox;
2558
+ this._config = config;
2559
+ this._version = config.version;
2560
+ this._env = config.env;
2561
+ this._installTimeout = config.installTimeout;
2562
+ this._customInstallCmd = config.customInstallCmd;
2563
+ this._extraSymlinkDir = config.extraSymlinkDir;
2564
+ this._extraSymlinkExecutables = config.extraSymlinkExecutables;
2565
+ this._runtimeEnvId = createRuntimeEnvId();
2566
+ const versionStr = this._version || "default";
2567
+ this._workdir = `/tmp/rock-runtime-envs/${config.type}/${versionStr}/${this._runtimeEnvId}`;
2568
+ this._session = `runtime-env-${config.type}-${versionStr}-${this._runtimeEnvId}`;
2569
+ }
2570
+ /** Whether the runtime has been initialized */
2571
+ get initialized() {
2572
+ return this._initialized;
2573
+ }
2574
+ /** Unique ID for this runtime env instance */
2575
+ get runtimeEnvId() {
2576
+ return this._runtimeEnvId;
2577
+ }
2578
+ /** Working directory for this runtime env instance */
2579
+ get workdir() {
2580
+ return this._workdir;
2581
+ }
2582
+ /** Binary directory for this runtime env instance */
2583
+ get binDir() {
2584
+ return `${this._workdir}/runtime-env/bin`;
2585
+ }
1951
2586
  /**
1952
- * Start sandbox service
2587
+ * Initialize the runtime environment.
2588
+ * This method performs installation and validation.
2589
+ * It is idempotent: calling multiple times only initializes once.
1953
2590
  */
1954
- startSandboxService(config = {}) {
1955
- const {
1956
- modelServiceType = "local",
1957
- configFile,
1958
- host,
1959
- port,
1960
- proxyBaseUrl,
1961
- retryableStatusCodes,
1962
- requestTimeout
1963
- } = config;
1964
- const cmd = ["node", "main.js", "--type", modelServiceType];
1965
- if (configFile) {
1966
- cmd.push("--config-file", configFile);
1967
- }
1968
- if (host) {
1969
- cmd.push("--host", host);
1970
- }
1971
- if (port) {
1972
- cmd.push("--port", String(port));
1973
- }
1974
- if (proxyBaseUrl) {
1975
- cmd.push("--proxy-base-url", proxyBaseUrl);
1976
- }
1977
- if (retryableStatusCodes) {
1978
- cmd.push("--retryable-status-codes", retryableStatusCodes);
1979
- }
1980
- if (requestTimeout) {
1981
- cmd.push("--request-timeout", String(requestTimeout));
1982
- }
1983
- const command = cmd[0] ?? "node";
1984
- this.process = child_process.spawn(command, cmd.slice(1), {
1985
- cwd: path.resolve(__dirname, "server"),
1986
- stdio: "inherit"
2591
+ async init() {
2592
+ if (this._initialized) {
2593
+ return;
2594
+ }
2595
+ await this._ensureSession();
2596
+ await this._ensureWorkdir();
2597
+ await this._installRuntime();
2598
+ await this._postInit();
2599
+ if (this._customInstallCmd) {
2600
+ await this._doCustomInstall();
2601
+ }
2602
+ await this._createSysPathLinks();
2603
+ this._initialized = true;
2604
+ }
2605
+ /**
2606
+ * Run a command under this runtime
2607
+ */
2608
+ async run(cmd, options = {}) {
2609
+ const { mode = "nohup", waitTimeout = 600, errorMsg = "runtime env command failed" } = options;
2610
+ await this._ensureSession();
2611
+ const wrapped = this.wrappedCmd(cmd, true);
2612
+ logger10.debug(`[${this._sandbox.sandboxId}] RuntimeEnv run cmd: ${wrapped}`);
2613
+ const result = await this._sandbox.arun(wrapped, {
2614
+ session: this._session,
2615
+ mode,
2616
+ waitTimeout
1987
2617
  });
1988
- if (!this.process) {
1989
- throw new Error("Failed to spawn model service process");
2618
+ if (result.exitCode !== void 0 && result.exitCode !== 0) {
2619
+ throw new Error(`${errorMsg} with exit code: ${result.exitCode}, output: ${result.output}`);
1990
2620
  }
1991
- return this.process;
2621
+ return result;
1992
2622
  }
1993
2623
  /**
1994
- * Start and wait for service to be available
2624
+ * Wrap command with PATH export.
2625
+ * Always wrap with bash -c to ensure it only affects current cmd.
2626
+ * Default prepend=true to give current runtime_env highest priority.
1995
2627
  */
1996
- async start(config = {}) {
1997
- const { timeoutSeconds = 30, ...serviceConfig } = config;
1998
- const process2 = this.startSandboxService(serviceConfig);
1999
- const pid = process2.pid?.toString();
2000
- if (!pid) {
2001
- throw new Error("Failed to start model service");
2628
+ wrappedCmd(cmd, prepend = true) {
2629
+ const binDir = this.binDir;
2630
+ let wrapped;
2631
+ if (prepend) {
2632
+ wrapped = `export PATH='${binDir}':$PATH && ${cmd}`;
2633
+ } else {
2634
+ wrapped = `export PATH=$PATH:'${binDir}' && ${cmd}`;
2635
+ }
2636
+ return `bash -c '${wrapped.replace(/'/g, `'"'"'`)}'`;
2637
+ }
2638
+ /**
2639
+ * Ensure runtime env session exists. Safe to call multiple times.
2640
+ */
2641
+ async _ensureSession() {
2642
+ if (this._sessionReady) {
2643
+ return;
2644
+ }
2645
+ await this._sandbox.createSession({
2646
+ session: this._session,
2647
+ envEnable: true,
2648
+ env: this._env
2649
+ });
2650
+ this._sessionReady = true;
2651
+ }
2652
+ /**
2653
+ * Create workdir for runtime environment.
2654
+ */
2655
+ async _ensureWorkdir() {
2656
+ const result = await this._sandbox.arun(`mkdir -p ${this._workdir}`, {
2657
+ session: this._session
2658
+ });
2659
+ if (result.exitCode !== void 0 && result.exitCode !== 0) {
2660
+ throw new Error(`Failed to create workdir: ${this._workdir}, exit_code: ${result.exitCode}`);
2002
2661
  }
2003
- const success = await this.waitServiceAvailable(
2004
- timeoutSeconds,
2005
- serviceConfig.host ?? "127.0.0.1",
2006
- serviceConfig.port ?? 8080
2662
+ }
2663
+ /**
2664
+ * Install the runtime environment.
2665
+ */
2666
+ async _installRuntime() {
2667
+ const installCmd = `cd '${this._workdir}' && ${this._getInstallCmd()}`;
2668
+ const wrappedCmd = `bash -c '${installCmd.replace(/'/g, `'"'"'`)}'`;
2669
+ const result = await this._sandbox.arun(wrappedCmd, {
2670
+ session: this._session,
2671
+ mode: "nohup",
2672
+ waitTimeout: this._installTimeout
2673
+ });
2674
+ if (result.exitCode !== void 0 && result.exitCode !== 0) {
2675
+ throw new Error(
2676
+ `${this.runtimeEnvType} runtime installation failed with exit code: ${result.exitCode}, output: ${result.output}`
2677
+ );
2678
+ }
2679
+ }
2680
+ /**
2681
+ * Additional initialization after runtime installation.
2682
+ * Override in subclasses.
2683
+ */
2684
+ async _postInit() {
2685
+ }
2686
+ /**
2687
+ * Execute custom install command after _postInit.
2688
+ */
2689
+ async _doCustomInstall() {
2690
+ if (!this._customInstallCmd) {
2691
+ return;
2692
+ }
2693
+ await this.run(this._customInstallCmd, {
2694
+ waitTimeout: this._installTimeout,
2695
+ errorMsg: "custom_install_cmd failed"
2696
+ });
2697
+ }
2698
+ /**
2699
+ * Create symlinks in target directory for executables.
2700
+ */
2701
+ async _createSysPathLinks() {
2702
+ if (this._extraSymlinkDir === null) {
2703
+ return;
2704
+ }
2705
+ if (this._extraSymlinkExecutables.length === 0) {
2706
+ return;
2707
+ }
2708
+ const links = this._extraSymlinkExecutables.map((exe) => `ln -sf '${this.binDir}/${exe}' '${this._extraSymlinkDir}/${exe}'`).join(" && ");
2709
+ await this.run(links);
2710
+ }
2711
+ };
2712
+
2713
+ // src/sandbox/runtime_env/python_runtime_env.ts
2714
+ var PythonRuntimeEnvConfigSchema = RuntimeEnvConfigSchema.extend({
2715
+ /** Runtime type discriminator. Must be 'python'. */
2716
+ type: zod.z.literal("python").default("python"),
2717
+ /** Python version. Use "default" for 3.11. */
2718
+ version: zod.z.enum(["3.11", "3.12", "default"]).default("default"),
2719
+ /**
2720
+ * Pip packages to install.
2721
+ * Can be:
2722
+ * - string[]: List of package names to install
2723
+ * - string: Path to requirements.txt file
2724
+ * - null: No packages to install
2725
+ */
2726
+ pip: zod.z.union([zod.z.array(zod.z.string()), zod.z.string()]).nullable().default(null),
2727
+ /** Pip index URL for package installation. If set, will use this mirror. */
2728
+ pipIndexUrl: zod.z.string().nullable().default(null),
2729
+ /** List of Python executables to symlink. */
2730
+ extraSymlinkExecutables: zod.z.array(zod.z.string()).default(["python", "python3", "pip", "pip3"])
2731
+ });
2732
+ function getDefaultPipIndexUrl() {
2733
+ return envVars.ROCK_PIP_INDEX_URL;
2734
+ }
2735
+ var PythonRuntimeEnv = class extends RuntimeEnv {
2736
+ runtimeEnvType = "python";
2737
+ _pip;
2738
+ _pipIndexUrl;
2739
+ constructor(sandbox, config) {
2740
+ if (config.version !== "3.11" && config.version !== "3.12" && config.version !== "default") {
2741
+ throw new Error(
2742
+ `Unsupported Python version: ${config.version}. Supported versions: 3.11, 3.12, default`
2743
+ );
2744
+ }
2745
+ super(sandbox, config);
2746
+ this._pip = config.pip;
2747
+ this._pipIndexUrl = config.pipIndexUrl;
2748
+ }
2749
+ _getInstallCmd() {
2750
+ const version = this._version;
2751
+ if (version === "3.11" || version === "default") {
2752
+ return envVars.ROCK_RTENV_PYTHON_V31114_INSTALL_CMD;
2753
+ }
2754
+ return envVars.ROCK_RTENV_PYTHON_V31212_INSTALL_CMD;
2755
+ }
2756
+ async _postInit() {
2757
+ await this._validatePython();
2758
+ if (this._pipIndexUrl) {
2759
+ await this._configurePip();
2760
+ }
2761
+ if (this._pip) {
2762
+ await this._installPip();
2763
+ }
2764
+ }
2765
+ async _validatePython() {
2766
+ await this.run("test -x python");
2767
+ }
2768
+ async _configurePip() {
2769
+ const escapedUrl = this._pipIndexUrl.replace(/'/g, "'\\''");
2770
+ await this.run(`pip config set global.index-url '${escapedUrl}'`);
2771
+ }
2772
+ async _installPip() {
2773
+ if (!this._pip) {
2774
+ return;
2775
+ }
2776
+ if (typeof this._pip === "string") {
2777
+ await this.run(`pip install -r '${this._pip.replace(/'/g, "'\\''")}'`);
2778
+ } else {
2779
+ const packages = this._pip.map((pkg) => `'${pkg.replace(/'/g, "'\\''")}'`).join(" ");
2780
+ await this.run(`pip install ${packages}`);
2781
+ }
2782
+ }
2783
+ };
2784
+ var NODE_DEFAULT_VERSION = "22.18.0";
2785
+ var NodeRuntimeEnvConfigSchema = RuntimeEnvConfigSchema.extend({
2786
+ /** Runtime type discriminator. Must be 'node'. */
2787
+ type: zod.z.literal("node").default("node"),
2788
+ /** Node.js version. Use "default" for 22.18.0. */
2789
+ version: zod.z.enum(["22.18.0", "default"]).default("default"),
2790
+ /** NPM registry URL. If set, will run 'npm config set registry <url>' during init. */
2791
+ npmRegistry: zod.z.string().nullable().default(null),
2792
+ /** List of Node.js executables to symlink. */
2793
+ extraSymlinkExecutables: zod.z.array(zod.z.string()).default(["node", "npm", "npx"])
2794
+ });
2795
+ var NodeRuntimeEnv = class extends RuntimeEnv {
2796
+ runtimeEnvType = "node";
2797
+ _npmRegistry;
2798
+ constructor(sandbox, config) {
2799
+ if (config.version !== "default" && config.version !== NODE_DEFAULT_VERSION) {
2800
+ throw new Error(
2801
+ `Unsupported Node version: ${config.version}. Only ${NODE_DEFAULT_VERSION} is supported right now.`
2802
+ );
2803
+ }
2804
+ super(sandbox, config);
2805
+ this._npmRegistry = config.npmRegistry;
2806
+ }
2807
+ _getInstallCmd() {
2808
+ return envVars.ROCK_RTENV_NODE_V22180_INSTALL_CMD;
2809
+ }
2810
+ async _postInit() {
2811
+ await this._validateNode();
2812
+ if (this._npmRegistry) {
2813
+ await this._configureNpmRegistry();
2814
+ }
2815
+ }
2816
+ async _validateNode() {
2817
+ await this.run("test -x node");
2818
+ }
2819
+ async _configureNpmRegistry() {
2820
+ const escapedRegistry = this._npmRegistry.replace(/'/g, "'\\''");
2821
+ await this.run(`npm config set registry '${escapedRegistry}'`);
2822
+ }
2823
+ };
2824
+ var logger11 = initLogger("rock.model_service");
2825
+ var ModelServiceConfigSchema = zod.z.object({
2826
+ /** Whether to enable model service */
2827
+ enabled: zod.z.boolean().default(false),
2828
+ /** Type of model service to start */
2829
+ type: zod.z.string().default("local"),
2830
+ /** Command to install model service package */
2831
+ installCmd: zod.z.string().default(envVars.ROCK_MODEL_SERVICE_INSTALL_CMD),
2832
+ /** Timeout for model service installation in seconds */
2833
+ installTimeout: zod.z.number().positive().default(300),
2834
+ /** Runtime environment configuration for the model service */
2835
+ runtimeEnvConfig: PythonRuntimeEnvConfigSchema.default({ type: "python", version: "default" }),
2836
+ /** Command to start model service with type placeholder */
2837
+ startCmd: zod.z.string().default("rock model-service start --type ${type}"),
2838
+ /** Command to stop model service */
2839
+ stopCmd: zod.z.string().default("rock model-service stop"),
2840
+ /** Command to create Rock config file */
2841
+ configIniCmd: zod.z.string().default("mkdir -p ~/.rock && touch ~/.rock/config.ini"),
2842
+ /** Command to watch agent with pid placeholder */
2843
+ watchAgentCmd: zod.z.string().default("rock model-service watch-agent --pid ${pid}"),
2844
+ /** Command to anti-call LLM with index and response_payload placeholders */
2845
+ antiCallLlmCmd: zod.z.string().default(
2846
+ "rock model-service anti-call-llm --index ${index} --response ${response_payload}"
2847
+ ),
2848
+ /** Command to anti-call LLM with only index placeholder */
2849
+ antiCallLlmCmdNoResponse: zod.z.string().default("rock model-service anti-call-llm --index ${index}"),
2850
+ /** Path for logging directory */
2851
+ loggingPath: zod.z.string().default("/data/logs"),
2852
+ /** Name of the log file */
2853
+ loggingFileName: zod.z.string().default("model_service.log")
2854
+ });
2855
+ var ModelService = class {
2856
+ _sandbox;
2857
+ _config;
2858
+ _runtimeEnv = null;
2859
+ _isInstalled = false;
2860
+ _isStarted = false;
2861
+ constructor(sandbox, config) {
2862
+ this._sandbox = sandbox;
2863
+ this._config = config;
2864
+ }
2865
+ /** Whether the model service has been installed */
2866
+ get isInstalled() {
2867
+ return this._isInstalled;
2868
+ }
2869
+ /** Whether the model service has been started */
2870
+ get isStarted() {
2871
+ return this._isStarted;
2872
+ }
2873
+ /**
2874
+ * Install model service in the sandbox.
2875
+ *
2876
+ * Performs the following installation steps:
2877
+ * 1. Create and initialize Python runtime environment (via RuntimeEnv).
2878
+ * 2. Install model service package.
2879
+ */
2880
+ async install() {
2881
+ if (this._config.runtimeEnvConfig.type !== "python") {
2882
+ throw new Error("ModelService requires a Python runtime environment");
2883
+ }
2884
+ const runtimeConfigResult = PythonRuntimeEnvConfigSchema.safeParse(this._config.runtimeEnvConfig);
2885
+ if (!runtimeConfigResult.success) {
2886
+ throw new Error(`Invalid runtime config: ${runtimeConfigResult.error.message}`);
2887
+ }
2888
+ this._runtimeEnv = new PythonRuntimeEnv(
2889
+ this._sandbox,
2890
+ runtimeConfigResult.data
2007
2891
  );
2008
- if (!success) {
2009
- await this.stop(pid);
2010
- throw new Error("Model service start failed");
2892
+ await this._runtimeEnv.init();
2893
+ await this._createRockConfig();
2894
+ await this._installModelService();
2895
+ this._isInstalled = true;
2896
+ }
2897
+ async _createRockConfig() {
2898
+ if (!this._runtimeEnv) {
2899
+ throw new Error("Runtime environment not initialized");
2900
+ }
2901
+ await this._runtimeEnv.run(this._config.configIniCmd);
2902
+ }
2903
+ async _installModelService() {
2904
+ if (!this._runtimeEnv) {
2905
+ throw new Error("Runtime environment not initialized");
2011
2906
  }
2012
- return pid;
2907
+ const installCmd = `cd ${this._runtimeEnv.workdir} && ${this._config.installCmd}`;
2908
+ await this._runtimeEnv.run(installCmd, {
2909
+ waitTimeout: this._config.installTimeout,
2910
+ errorMsg: "Model service installation failed"
2911
+ });
2013
2912
  }
2014
2913
  /**
2015
- * Start watching agent
2914
+ * Start the model service in the sandbox.
2915
+ *
2916
+ * Starts the service with configured logging settings.
2016
2917
  */
2017
- async startWatchAgent(agentPid, host = "127.0.0.1", port = 8080) {
2018
- await axios3__default.default.post(`http://${host}:${port}/v1/agent/watch`, { pid: agentPid });
2918
+ async start() {
2919
+ if (!this._isInstalled) {
2920
+ throw new Error(
2921
+ `[${this._sandbox.sandboxId}] Cannot start model service: ModelService has not been installed yet. Please call install() first.`
2922
+ );
2923
+ }
2924
+ if (!this._runtimeEnv) {
2925
+ throw new Error("Runtime environment not initialized");
2926
+ }
2927
+ const startCmd = this._config.startCmd.replace(/\$\{type\}/g, this._config.type);
2928
+ const bashStartCmd = `export ROCK_LOGGING_PATH=${this._config.loggingPath} && export ROCK_LOGGING_FILE_NAME=${this._config.loggingFileName} && ${this._config.stopCmd} && ` + startCmd;
2929
+ logger11.debug(`[${this._sandbox.sandboxId}] Model service Start command: ${bashStartCmd}`);
2930
+ await this._runtimeEnv.run(bashStartCmd);
2931
+ this._isStarted = true;
2019
2932
  }
2020
2933
  /**
2021
- * Stop service
2934
+ * Stop the model service.
2022
2935
  */
2023
- async stop(pid) {
2024
- const { execSync } = await import('child_process');
2025
- try {
2026
- execSync(`kill -9 ${pid}`);
2027
- } catch {
2936
+ async stop() {
2937
+ if (!this._isStarted) {
2938
+ logger11.warn(
2939
+ `[${this._sandbox.sandboxId}] Model service is not running, skipping stop operation. is_started=${this._isStarted}`
2940
+ );
2941
+ return;
2028
2942
  }
2943
+ if (!this._runtimeEnv) {
2944
+ throw new Error("Runtime environment not initialized");
2945
+ }
2946
+ await this._runtimeEnv.run(this._config.stopCmd);
2947
+ this._isStarted = false;
2029
2948
  }
2030
2949
  /**
2031
- * Wait for service to be available
2950
+ * Watch agent process with the specified PID.
2032
2951
  */
2033
- async waitServiceAvailable(timeoutSeconds, host, port) {
2034
- const startTime = Date.now();
2035
- while ((Date.now() - startTime) / 1e3 < timeoutSeconds) {
2036
- try {
2037
- await axios3__default.default.get(`http://${host}:${port}/health`);
2038
- return true;
2039
- } catch {
2040
- await this.sleep(1e3);
2952
+ async watchAgent(pid) {
2953
+ if (!this._isStarted) {
2954
+ throw new Error(
2955
+ `[${this._sandbox.sandboxId}] Cannot watch agent: ModelService is not started. Please call start() first.`
2956
+ );
2957
+ }
2958
+ if (!this._runtimeEnv) {
2959
+ throw new Error("Runtime environment not initialized");
2960
+ }
2961
+ const watchCmd = this._config.watchAgentCmd.replace(/\$\{pid\}/g, pid);
2962
+ logger11.debug(
2963
+ `[${this._sandbox.sandboxId}] Model service watch agent with pid=${pid}, cmd: ${watchCmd}`
2964
+ );
2965
+ await this._runtimeEnv.run(watchCmd);
2966
+ }
2967
+ /**
2968
+ * Execute anti-call LLM command.
2969
+ */
2970
+ async antiCallLlm(index, responsePayload, callTimeout = 600) {
2971
+ if (!this._isStarted) {
2972
+ throw new Error(
2973
+ `[${this._sandbox.sandboxId}] Cannot execute anti-call LLM: ModelService is not started. Please call start() first.`
2974
+ );
2975
+ }
2976
+ if (!this._runtimeEnv) {
2977
+ throw new Error("Runtime environment not initialized");
2978
+ }
2979
+ logger11.info(
2980
+ `[${this._sandbox.sandboxId}] Executing anti-call LLM: index=${index}, has_response=${responsePayload !== void 0}, timeout=${callTimeout}s`
2981
+ );
2982
+ let cmd;
2983
+ if (responsePayload !== void 0) {
2984
+ const escapedPayload = `'${responsePayload.replace(/'/g, "'\\''")}'`;
2985
+ cmd = this._config.antiCallLlmCmd.replace(/\$\{index\}/g, String(index)).replace(/\$\{response_payload\}/g, escapedPayload);
2986
+ } else {
2987
+ cmd = this._config.antiCallLlmCmdNoResponse.replace(/\$\{index\}/g, String(index));
2988
+ }
2989
+ const bashCmd = this._runtimeEnv.wrappedCmd(cmd);
2990
+ logger11.debug(`[${this._sandbox.sandboxId}] Executing command: ${bashCmd}`);
2991
+ const result = await this._sandbox.arun(bashCmd, {
2992
+ mode: "nohup",
2993
+ waitTimeout: callTimeout,
2994
+ waitInterval: 3
2995
+ });
2996
+ if (result.exitCode !== 0) {
2997
+ throw new Error(`Anti-call LLM command failed: ${result.output}`);
2998
+ }
2999
+ return result.output;
3000
+ }
3001
+ };
3002
+
3003
+ // src/sandbox/agent/base.ts
3004
+ var logger12 = initLogger("rock.agent");
3005
+ var Agent = class {
3006
+ _sandbox;
3007
+ _modelService = null;
3008
+ constructor(sandbox) {
3009
+ this._sandbox = sandbox;
3010
+ }
3011
+ get sandbox() {
3012
+ return this._sandbox;
3013
+ }
3014
+ get modelService() {
3015
+ return this._modelService;
3016
+ }
3017
+ };
3018
+ var DefaultAgent = class extends Agent {
3019
+ _config = null;
3020
+ _agentSession = null;
3021
+ get config() {
3022
+ return this._config;
3023
+ }
3024
+ get agentSession() {
3025
+ return this._agentSession;
3026
+ }
3027
+ get modelService() {
3028
+ return this._modelService;
3029
+ }
3030
+ async install(config) {
3031
+ this._config = config;
3032
+ this._agentSession = config.agentSession;
3033
+ const sandboxId = this._sandbox.sandboxId;
3034
+ logger12.info(`[${sandboxId}] Starting agent initialization`);
3035
+ try {
3036
+ await this._setupSession();
3037
+ await this._executeInitCommands(config.preInitCmds, "pre-init");
3038
+ await this._executeInitCommands(config.postInitCmds, "post-init");
3039
+ logger12.info(`[${sandboxId}] Agent initialization completed`);
3040
+ } catch (e) {
3041
+ const error = e;
3042
+ logger12.error(`[${sandboxId}] Agent initialization failed: ${error.message}`);
3043
+ throw error;
3044
+ }
3045
+ }
3046
+ async run(prompt) {
3047
+ if (!this._config) {
3048
+ throw new Error("Agent is not installed. Please call install() first.");
3049
+ }
3050
+ if (!this._config.runCmd) {
3051
+ throw new Error("runCmd is not configured");
3052
+ }
3053
+ const cmd = await this._createAgentRunCmd(prompt);
3054
+ return this._agentRun(cmd);
3055
+ }
3056
+ async _setupSession() {
3057
+ if (!this._config) return;
3058
+ const sandboxId = this._sandbox.sandboxId;
3059
+ logger12.info(`[${sandboxId}] Creating bash session: ${this._agentSession}`);
3060
+ await this._sandbox.createSession({
3061
+ session: this._agentSession,
3062
+ envEnable: true,
3063
+ env: this._config.env
3064
+ });
3065
+ logger12.info(`[${sandboxId}] Bash session '${this._agentSession}' created successfully`);
3066
+ }
3067
+ async _executeInitCommands(cmdList, stepName) {
3068
+ if (!cmdList || cmdList.length === 0) return;
3069
+ const sandboxId = this._sandbox.sandboxId;
3070
+ logger12.info(`[${sandboxId}] ${stepName} started: Executing ${cmdList.length} commands`);
3071
+ for (let idx = 0; idx < cmdList.length; idx++) {
3072
+ const cmdConfig = cmdList[idx];
3073
+ if (!cmdConfig) continue;
3074
+ const command = cmdConfig.command;
3075
+ const timeout = cmdConfig.timeoutSeconds;
3076
+ logger12.debug(
3077
+ `[${sandboxId}] Executing ${stepName} command ${idx + 1}/${cmdList.length}: ${command.substring(0, 100)}...`
3078
+ );
3079
+ const result = await this._sandbox.arun(`bash -c ${JSON.stringify(command)}`, {
3080
+ waitTimeout: timeout,
3081
+ mode: "NOHUP"
3082
+ });
3083
+ if (result.exitCode !== 0) {
3084
+ throw new Error(
3085
+ `[${sandboxId}] ${stepName} command ${idx + 1} failed with exit code ${result.exitCode}: ${result.output.substring(0, 200)}`
3086
+ );
2041
3087
  }
3088
+ logger12.debug(`[${sandboxId}] ${stepName} command ${idx + 1} completed successfully`);
2042
3089
  }
2043
- return false;
3090
+ logger12.info(`[${sandboxId}] ${stepName} completed: Completed ${cmdList.length} commands`);
2044
3091
  }
2045
- sleep(ms) {
2046
- return new Promise((resolve3) => setTimeout(resolve3, ms));
3092
+ async _createAgentRunCmd(prompt) {
3093
+ if (!this._config || !this._config.runCmd) {
3094
+ throw new Error("runCmd is not configured");
3095
+ }
3096
+ let runCmd = this._config.runCmd.replace(/{prompt}/g, JSON.stringify(prompt));
3097
+ if (this._config.skipWrapRunCmd) {
3098
+ return `bash -c ${JSON.stringify(runCmd)}`;
3099
+ }
3100
+ return `bash -c ${JSON.stringify(runCmd)}`;
3101
+ }
3102
+ async _agentRun(cmd) {
3103
+ const sandboxId = this._sandbox.sandboxId;
3104
+ try {
3105
+ const timestamp = Date.now().toString();
3106
+ const tmpFile = `/tmp/tmp_${timestamp}.out`;
3107
+ if (!this._sandbox.startNohupProcess) {
3108
+ const msg = "startNohupProcess method is not available on sandbox";
3109
+ return { output: msg, exitCode: 1, failureReason: msg, expectString: "" };
3110
+ }
3111
+ const { pid, errorResponse } = await this._sandbox.startNohupProcess(cmd, tmpFile, this._agentSession);
3112
+ if (errorResponse) {
3113
+ return errorResponse;
3114
+ }
3115
+ if (!pid) {
3116
+ const msg = "Failed to submit command, nohup failed to extract PID";
3117
+ return { output: msg, exitCode: 1, failureReason: msg, expectString: "" };
3118
+ }
3119
+ logger12.info(`[${sandboxId}] Agent process started with PID: ${pid}`);
3120
+ if (!this._sandbox.waitForProcessCompletion) {
3121
+ const msg = "waitForProcessCompletion method is not available on sandbox";
3122
+ return { output: msg, exitCode: 1, failureReason: msg, expectString: "" };
3123
+ }
3124
+ const { success, message } = await this._sandbox.waitForProcessCompletion(
3125
+ pid,
3126
+ this._agentSession,
3127
+ this._config.agentRunTimeout,
3128
+ this._config.agentRunCheckInterval
3129
+ );
3130
+ if (!this._sandbox.handleNohupOutput) {
3131
+ const msg = "handleNohupOutput method is not available on sandbox";
3132
+ return { output: msg, exitCode: 1, failureReason: msg, expectString: "" };
3133
+ }
3134
+ const result = await this._sandbox.handleNohupOutput(tmpFile, this._agentSession, success, message, false, null);
3135
+ return result;
3136
+ } catch (e) {
3137
+ const error = e;
3138
+ const errorMsg = `Failed to execute nohup command: ${error.message}`;
3139
+ logger12.error(`[${sandboxId}] ${errorMsg}`);
3140
+ return { output: errorMsg, exitCode: 1, failureReason: errorMsg, expectString: "" };
3141
+ }
2047
3142
  }
2048
3143
  };
3144
+ var AgentConfigSchema = zod.z.object({
3145
+ agentType: zod.z.string(),
3146
+ version: zod.z.string().default("default")
3147
+ });
3148
+ var AgentBashCommandSchema = zod.z.object({
3149
+ command: zod.z.string(),
3150
+ timeoutSeconds: zod.z.number().int().positive().default(300)
3151
+ });
3152
+ var DefaultAgentConfigSchema = zod.z.object({
3153
+ agentType: zod.z.string(),
3154
+ version: zod.z.string().default("default"),
3155
+ // Session management
3156
+ agentSession: zod.z.string().default("default-agent-session"),
3157
+ // Startup/shutdown commands
3158
+ preInitBashCmdList: zod.z.array(AgentBashCommandSchema).default(
3159
+ envVars.ROCK_AGENT_PRE_INIT_BASH_CMD_LIST.map((cmd) => ({
3160
+ command: cmd.command,
3161
+ timeoutSeconds: cmd.timeoutSeconds || 300
3162
+ }))
3163
+ ),
3164
+ postInitBashCmdList: zod.z.array(AgentBashCommandSchema).default([]),
3165
+ // Environment variables for the session
3166
+ sessionEnvs: zod.z.record(zod.z.string()).default({}),
3167
+ // Optional ModelService configuration
3168
+ modelServiceConfig: zod.z.custom().nullable().default(null)
3169
+ });
3170
+ var RockAgentConfigSchema = zod.z.object({
3171
+ agentType: zod.z.string().default("default"),
3172
+ agentName: zod.z.string().default(() => crypto.randomUUID().replace(/-/g, "")),
3173
+ version: zod.z.string().default("default"),
3174
+ agentInstalledDir: zod.z.string().default("/tmp/installed_agent"),
3175
+ instanceId: zod.z.string().default(() => `instance-id-${crypto.randomUUID().replace(/-/g, "")}`),
3176
+ projectPath: zod.z.string().nullable().default(null),
3177
+ useDeployWorkingDirAsFallback: zod.z.boolean().default(true),
3178
+ agentSession: zod.z.string().default(() => `agent-session-${crypto.randomUUID().replace(/-/g, "")}`),
3179
+ env: zod.z.record(zod.z.string()).default({}),
3180
+ preInitCmds: zod.z.array(AgentBashCommandSchema).default(
3181
+ envVars.ROCK_AGENT_PRE_INIT_BASH_CMD_LIST.map((cmd) => ({
3182
+ command: cmd.command,
3183
+ timeoutSeconds: cmd.timeoutSeconds || 300
3184
+ }))
3185
+ ),
3186
+ postInitCmds: zod.z.array(AgentBashCommandSchema).default([]),
3187
+ agentInstallTimeout: zod.z.number().int().positive().default(600),
3188
+ agentRunTimeout: zod.z.number().int().positive().default(1800),
3189
+ agentRunCheckInterval: zod.z.number().int().positive().default(30),
3190
+ workingDir: zod.z.string().nullable().default(null),
3191
+ runCmd: zod.z.string().nullable().default(null),
3192
+ skipWrapRunCmd: zod.z.boolean().default(false),
3193
+ runtimeEnvConfig: zod.z.any().nullable().default(null),
3194
+ modelServiceConfig: zod.z.custom().nullable().default(null)
3195
+ }).refine((data) => data.agentRunCheckInterval < data.agentRunTimeout, {
3196
+ message: "agentRunCheckInterval must be less than agentRunTimeout"
3197
+ });
2049
3198
 
2050
3199
  // src/index.ts
2051
- var VERSION = "1.2.1";
3200
+ function getVersion() {
3201
+ const packageJsonPath = path.join(__dirname, "..", "package.json");
3202
+ const content = fs.readFileSync(packageJsonPath, "utf-8");
3203
+ return JSON.parse(content).version;
3204
+ }
3205
+ var VERSION = getVersion();
2052
3206
 
3207
+ exports.Agent = Agent;
3208
+ exports.AgentBashCommandSchema = AgentBashCommandSchema;
3209
+ exports.AgentConfigSchema = AgentConfigSchema;
2053
3210
  exports.BadRequestRockError = BadRequestRockError;
2054
3211
  exports.BashActionSchema = BashActionSchema;
2055
3212
  exports.ChmodRequestSchema = ChmodRequestSchema;
@@ -2063,9 +3220,10 @@ exports.Codes = Codes;
2063
3220
  exports.CommandResponseSchema = CommandResponseSchema;
2064
3221
  exports.CommandRockError = CommandRockError;
2065
3222
  exports.CommandSchema = CommandSchema;
2066
- exports.Constants = Constants;
2067
3223
  exports.CreateBashSessionRequestSchema = CreateBashSessionRequestSchema;
2068
3224
  exports.CreateSessionResponseSchema = CreateSessionResponseSchema;
3225
+ exports.DefaultAgent = DefaultAgent;
3226
+ exports.DefaultAgentConfigSchema = DefaultAgentConfigSchema;
2069
3227
  exports.Deploy = Deploy;
2070
3228
  exports.EnvHubClient = EnvHubClient;
2071
3229
  exports.EnvHubClientConfigSchema = EnvHubClientConfigSchema;
@@ -2079,19 +3237,28 @@ exports.LinuxFileSystem = LinuxFileSystem;
2079
3237
  exports.LinuxRemoteUser = LinuxRemoteUser;
2080
3238
  exports.ModelClient = ModelClient;
2081
3239
  exports.ModelService = ModelService;
3240
+ exports.ModelServiceConfigSchema = ModelServiceConfigSchema;
3241
+ exports.NODE_DEFAULT_VERSION = NODE_DEFAULT_VERSION;
2082
3242
  exports.Network = Network;
3243
+ exports.NodeRuntimeEnv = NodeRuntimeEnv;
3244
+ exports.NodeRuntimeEnvConfigSchema = NodeRuntimeEnvConfigSchema;
2083
3245
  exports.ObservationSchema = ObservationSchema;
2084
3246
  exports.OssSetupResponseSchema = OssSetupResponseSchema;
2085
3247
  exports.PID_PREFIX = PID_PREFIX;
2086
3248
  exports.PID_SUFFIX = PID_SUFFIX;
2087
3249
  exports.Process = Process;
3250
+ exports.PythonRuntimeEnv = PythonRuntimeEnv;
3251
+ exports.PythonRuntimeEnvConfigSchema = PythonRuntimeEnvConfigSchema;
2088
3252
  exports.ReadFileRequestSchema = ReadFileRequestSchema;
2089
3253
  exports.ReadFileResponseSchema = ReadFileResponseSchema;
2090
3254
  exports.ReasonPhrases = ReasonPhrases;
3255
+ exports.RockAgentConfigSchema = RockAgentConfigSchema;
2091
3256
  exports.RockEnv = RockEnv;
2092
3257
  exports.RockEnvInfoSchema = RockEnvInfoSchema;
2093
3258
  exports.RockException = RockException;
2094
3259
  exports.RunMode = RunMode;
3260
+ exports.RuntimeEnv = RuntimeEnv;
3261
+ exports.RuntimeEnvConfigSchema = RuntimeEnvConfigSchema;
2095
3262
  exports.Sandbox = Sandbox;
2096
3263
  exports.SandboxConfigSchema = SandboxConfigSchema;
2097
3264
  exports.SandboxGroup = SandboxGroup;
@@ -2106,16 +3273,17 @@ exports.WriteFileRequestSchema = WriteFileRequestSchema;
2106
3273
  exports.WriteFileResponseSchema = WriteFileResponseSchema;
2107
3274
  exports.arunWithRetry = arunWithRetry;
2108
3275
  exports.createRockEnvInfo = createRockEnvInfo;
3276
+ exports.createRuntimeEnvId = createRuntimeEnvId;
2109
3277
  exports.createSandboxConfig = createSandboxConfig;
2110
3278
  exports.createSandboxGroupConfig = createSandboxGroupConfig;
2111
3279
  exports.deprecated = deprecated;
2112
3280
  exports.deprecatedClass = deprecatedClass;
2113
3281
  exports.extractNohupPidFromSandbox = extractNohupPid;
2114
3282
  exports.fromRockException = fromRockException;
3283
+ exports.getDefaultPipIndexUrl = getDefaultPipIndexUrl;
2115
3284
  exports.getEnv = getEnv;
2116
3285
  exports.getReasonPhrase = getReasonPhrase;
2117
3286
  exports.getRequiredEnv = getRequiredEnv;
2118
- exports.isBrowser = isBrowser;
2119
3287
  exports.isClientError = isClientError;
2120
3288
  exports.isCommandError = isCommandError;
2121
3289
  exports.isEnvSet = isEnvSet;