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