diff --git a/docs/_specs/response/interface_response_body_spec.md b/docs/_specs/response/interface_response_body_spec.md
new file mode 100644
index 000000000..e4c7bfbf4
--- /dev/null
+++ b/docs/_specs/response/interface_response_body_spec.md
@@ -0,0 +1,80 @@
+# 接口响应体说明
+
+## 基础结构
+
+| 字段 | 类型 | 描述 |
+| --- | --- | --- |
+| status | String | 枚举值:Sucess/Failed。
代表请求是否正常由沙箱执行完成。 |
+| message | String | 响应信息。 |
+| error | String | 表示当status=Failed时的错误描述信息。 |
+| result | Object | 请求执行结果。根据具体功能,结构有所差异。 |
+| └code | Integer | 响应状态码。参考下方状态码定义。 |
+| └failure\_reason | String | 当code=5xxx时,包含一些额外的错误信息。 |
+| └... | | 根据不同接口,扩展信息有所不同。 |
+
+### 响应状态码定义
+
+| **值** | **描述** |
+| --- | --- |
+| 2xxx | 命令执行成功 |
+| 5xxx | 服务端异常。该场景建议用户进行重试。 |
+| 6xxx | 命令执行错误 |
+
+## result扩展信息定义
+
+### execute/run\_in\_session
+
+| 字段 | 类型 | 描述 |
+| --- | --- | --- |
+| exit\_code | Integer | 命令执行退出码。
特殊退出码:-1(超时/网络/会话失效) |
+| stdout | String | 命令执行标准输出 |
+| stderr | String | 命令执行标准错误 |
+
+### create\_session
+
+| 字段 | 类型 | 描述 |
+| --- | --- | --- |
+| output | String | session创建结果。 |
+| session\_type | String | session类型。
枚举值:
* bash |
+
+### close\_session
+
+| 字段 | 类型 | 描述 |
+| --- | --- | --- |
+| session\_type | String | session类型。
枚举值:
* bash |
+
+### upload
+
+| **字段** | **类型** | **描述** |
+| --- | --- | --- |
+| success | Boolean | 文件上传是否成功。 |
+| message | String | 上传结果信息。 |
+| file\_name | String | 文件名 |
+
+现有响应结构固定返回默认值,需要调整。
+
+```shell
+{
+ "status": "Success",
+ "message": null,
+ "error": null,
+ "result": {
+ "success": false,
+ "message": "",
+ "file_name": ""
+ }
+}
+```
+
+### write\_file
+
+| **字段** | **类型** | **描述** |
+| --- | --- | --- |
+| success | Boolean | 文件写入是否成功。 |
+| message | String | 写入结果信息。 |
+
+### read\_file
+
+| **字段** | **类型** | **描述** |
+| --- | --- | --- |
+| content | String | 文件内容。 |
\ No newline at end of file
diff --git a/rock/actions/__init__.py b/rock/actions/__init__.py
index d900c2f36..11f67809d 100644
--- a/rock/actions/__init__.py
+++ b/rock/actions/__init__.py
@@ -31,7 +31,6 @@
OssSetupResponse,
ReadFileResponse,
SandboxResponse,
- SandboxStatusResponse,
UploadResponse,
WriteFileResponse,
)
@@ -65,7 +64,6 @@
"ReadFileRequest",
"UploadRequest",
"IsAliveResponse",
- "SandboxStatusResponse",
"CommandResponse",
"WriteFileResponse",
"OssSetupResponse",
diff --git a/rock/actions/response.py b/rock/actions/response.py
index 3fef5afd2..65577ecf7 100644
--- a/rock/actions/response.py
+++ b/rock/actions/response.py
@@ -20,3 +20,13 @@ class BaseResponse(BaseModel):
class RockResponse(BaseResponse, Generic[T]):
result: T | None = None
+
+
+class ChownResponse(BaseModel):
+ success: bool = False
+ message: str = ""
+
+
+class ChmodResponse(BaseModel):
+ success: bool = False
+ message: str = ""
diff --git a/rock/actions/sandbox/response.py b/rock/actions/sandbox/response.py
index e218b566a..9f59a6752 100644
--- a/rock/actions/sandbox/response.py
+++ b/rock/actions/sandbox/response.py
@@ -8,7 +8,6 @@
class SandboxResponse(BaseModel):
code: codes | None = None
- exit_code: int | None = None
failure_reason: str | None = None
@@ -32,30 +31,13 @@ def __bool__(self) -> bool:
return self.is_alive
-class SandboxStatusResponse(BaseModel):
- sandbox_id: str = None
- status: dict = None
- port_mapping: dict = None
- host_name: str | None = None
- host_ip: str | None = None
- is_alive: bool = True
- image: str | None = None
- gateway_version: str | None = None
- swe_rex_version: str | None = None
- user_id: str | None = None
- experiment_id: str | None = None
- namespace: str | None = None
- cpus: float | None = None
- memory: str | None = None
-
-
-class CommandResponse(BaseModel):
+class CommandResponse(SandboxResponse):
+ exit_code: int | None = None
stdout: str = ""
stderr: str = ""
- exit_code: int | None = None
-class WriteFileResponse(BaseModel):
+class WriteFileResponse(SandboxResponse):
success: bool = False
message: str = ""
@@ -70,7 +52,7 @@ class ExecuteBashSessionResponse(BaseModel):
message: str = ""
-class CreateBashSessionResponse(BaseModel):
+class CreateBashSessionResponse(SandboxResponse):
output: str = ""
session_type: Literal["bash"] = "bash"
@@ -80,18 +62,17 @@ class CreateBashSessionResponse(BaseModel):
"""Union type for all create session responses. Do not use this directly."""
-class BashObservation(BaseModel):
+class BashObservation(SandboxResponse):
+ exit_code: int | None = None
session_type: Literal["bash"] = "bash"
output: str = ""
- exit_code: int | None = None
- failure_reason: str = ""
expect_string: str = ""
Observation = BashObservation
-class CloseBashSessionResponse(BaseModel):
+class CloseBashSessionResponse(SandboxResponse):
session_type: Literal["bash"] = "bash"
@@ -99,12 +80,12 @@ class CloseBashSessionResponse(BaseModel):
"""Union type for all close session responses. Do not use this directly."""
-class ReadFileResponse(BaseModel):
+class ReadFileResponse(SandboxResponse):
content: str = ""
"""Content of the file as a string."""
-class UploadResponse(BaseModel):
+class UploadResponse(SandboxResponse):
success: bool = False
message: str = ""
file_name: str = ""
@@ -113,17 +94,7 @@ class UploadResponse(BaseModel):
FileUploadResponse = UploadResponse
-class CloseResponse(BaseModel):
+class CloseResponse(SandboxResponse):
"""Response for close operations."""
pass
-
-
-class ChownResponse(BaseModel):
- success: bool = False
- message: str = ""
-
-
-class ChmodResponse(BaseModel):
- success: bool = False
- message: str = ""
diff --git a/rock/sdk/sandbox/client.py b/rock/sdk/sandbox/client.py
index ed85f7b52..b3c1760e0 100644
--- a/rock/sdk/sandbox/client.py
+++ b/rock/sdk/sandbox/client.py
@@ -32,12 +32,12 @@
ReadFileRequest,
ReadFileResponse,
SandboxResponse,
- SandboxStatusResponse,
UploadRequest,
UploadResponse,
WriteFileRequest,
WriteFileResponse,
)
+from rock.admin.proto.response import SandboxStatusResponse
from rock.common.constants import PID_PREFIX, PID_SUFFIX
from rock.sdk.common.constants import RunModeType
from rock.sdk.common.exceptions import (
diff --git a/rock/sdk/sandbox/file_system.py b/rock/sdk/sandbox/file_system.py
index 4f219eb9b..65454cedc 100644
--- a/rock/sdk/sandbox/file_system.py
+++ b/rock/sdk/sandbox/file_system.py
@@ -7,9 +7,10 @@
from pathlib import Path
from rock.actions import CreateBashSessionRequest, Observation
+from rock.actions.response import ChmodResponse, ChownResponse
from rock.actions.sandbox.base import AbstractSandbox
from rock.actions.sandbox.request import ChmodRequest, ChownRequest, Command
-from rock.actions.sandbox.response import ChmodResponse, ChownResponse, CommandResponse
+from rock.actions.sandbox.response import CommandResponse
logger = logging.getLogger(__name__)
diff --git a/tests/integration/sdk/sandbox/test_file_system.py b/tests/integration/sdk/sandbox/test_file_system.py
index 271389b79..ccd75c23a 100644
--- a/tests/integration/sdk/sandbox/test_file_system.py
+++ b/tests/integration/sdk/sandbox/test_file_system.py
@@ -1,7 +1,8 @@
import logging
+from rock.actions.response import ChmodResponse, ChownResponse
from rock.actions.sandbox.request import ChmodRequest, ChownRequest, Command, CreateBashSessionRequest
-from rock.actions.sandbox.response import ChmodResponse, ChownResponse, CommandResponse, Observation
+from rock.actions.sandbox.response import CommandResponse, Observation
from rock.sdk.sandbox.client import Sandbox
logger = logging.getLogger(__name__)
diff --git a/tests/integration/sdk/sandbox/test_sdk_client.py b/tests/integration/sdk/sandbox/test_sdk_client.py
index 70bb34e33..f8009601c 100644
--- a/tests/integration/sdk/sandbox/test_sdk_client.py
+++ b/tests/integration/sdk/sandbox/test_sdk_client.py
@@ -4,7 +4,7 @@
import pytest
from rock.actions.sandbox.request import ReadFileRequest
-from rock.actions.sandbox.response import SandboxStatusResponse
+from rock.admin.proto.response import SandboxStatusResponse
from rock.sdk.sandbox.client import Sandbox
from rock.sdk.sandbox.config import SandboxConfig
from rock.utils.docker import DockerUtil
@@ -126,8 +126,6 @@ async def test_execute(sandbox_instance: Sandbox):
@SKIP_IF_NO_DOCKER
@pytest.mark.asyncio
async def test_start_sandbox_upper_limit(sandbox_instance: Sandbox):
- from rock.actions import SandboxStatusResponse
-
status: SandboxStatusResponse = await sandbox_instance.get_status()
assert status.cpus == 4
diff --git a/tests/unit/sandbox/test_sandbox_manager.py b/tests/unit/sandbox/test_sandbox_manager.py
index fb69a486f..9ca271388 100644
--- a/tests/unit/sandbox/test_sandbox_manager.py
+++ b/tests/unit/sandbox/test_sandbox_manager.py
@@ -6,8 +6,8 @@
import pytest
import ray
-from rock.actions import SandboxStatusResponse
from rock.actions.sandbox.response import State
+from rock.admin.proto.response import SandboxStatusResponse
from rock.deployments.config import DockerDeploymentConfig, RayDeploymentConfig
from rock.deployments.constants import Port
from rock.deployments.status import ServiceStatus
diff --git a/tests/unit/sdk/test_arun_nohup.py b/tests/unit/sdk/test_arun_nohup.py
index 6c301e475..f519d6c2e 100644
--- a/tests/unit/sdk/test_arun_nohup.py
+++ b/tests/unit/sdk/test_arun_nohup.py
@@ -4,8 +4,7 @@
from httpx import ReadTimeout
from rock.actions.sandbox.response import Observation
-from rock.common.constants import PID_PREFIX
-from rock.common.constants import PID_SUFFIX
+from rock.common.constants import PID_PREFIX, PID_SUFFIX
from rock.sdk.sandbox.client import Sandbox
from rock.sdk.sandbox.config import SandboxConfig
@@ -42,7 +41,7 @@ async def fake_wait(self, pid, session, wait_timeout, wait_interval):
)
assert result.exit_code == 0
- assert result.failure_reason == ""
+ assert result.failure_reason is None
assert "/tmp/tmp_1701.out" in result.output
assert "without streaming the log content" in result.output
assert "File size: 2.00 KB" in result.output
diff --git a/tests/unit/test_sandbox_response.py b/tests/unit/test_sandbox_response.py
new file mode 100644
index 000000000..ef5cd4cc3
--- /dev/null
+++ b/tests/unit/test_sandbox_response.py
@@ -0,0 +1,33 @@
+from rock.actions.sandbox.response import (
+ BashObservation,
+ CloseBashSessionResponse,
+ CommandResponse,
+ CreateBashSessionResponse,
+ ReadFileResponse,
+ UploadResponse,
+ WriteFileResponse,
+)
+
+
+class TestResponseContainsCodeAndFailureReason:
+ """各 result Response 序列化后应包含 code 和 failure_reason 字段"""
+
+ RESPONSE_CLASSES = [
+ CommandResponse,
+ BashObservation,
+ CreateBashSessionResponse,
+ CloseBashSessionResponse,
+ ReadFileResponse,
+ WriteFileResponse,
+ UploadResponse,
+ ]
+
+ def test_all_responses_have_code_field(self):
+ for cls in self.RESPONSE_CLASSES:
+ data = cls().model_dump()
+ assert "code" in data, f"{cls.__name__} missing 'code'"
+
+ def test_all_responses_have_failure_reason_field(self):
+ for cls in self.RESPONSE_CLASSES:
+ data = cls().model_dump()
+ assert "failure_reason" in data, f"{cls.__name__} missing 'failure_reason'"