Bash 管道

/v1/bash 是基于 subprocess pipe 的命令执行服务,适合 Agent 工具调用和程序化使用。它不同于基于 PTY 的 Shell 终端:Bash 管道会分离 stdout / stderr,并支持按 offset 增量读取输出。

Shell vs Bash

/v1/shell/v1/bash
后端终端 / PTYsubprocess pipe
适合场景交互式终端、WebSocket UI、人工接管Agent 工具调用、短命令、长命令轮询
输出单一 output 字段分离的 stdoutstderr
读取方式终端快照和等待offset / stderr_offset 增量读取
stdin终端输入流写入运行中进程的 stdin pipe
命令标识session 维度每条命令有 command_id

执行短命令

大部分工具调用可以一次 exec 完成。status 表示命令生命周期,exit_code 才表示命令是否成功。

curl -X POST "http://localhost:8080/v1/bash/exec" \
  -H "Content-Type: application/json" \
  -d '{
    "command": "pwd && ls -la",
    "exec_dir": "/home/gem",
    "timeout": 30,
    "hard_timeout": 120
  }'

典型响应结构:

{
  "success": true,
  "data": {
    "session_id": "SESSION_ID",
    "command_id": "COMMAND_ID",
    "command": "pwd && ls -la",
    "status": "completed",
    "stdout": "/home/gem\n...",
    "stderr": null,
    "exit_code": 0,
    "offset": 128,
    "stderr_offset": 0
  }
}

SDK 示例

Python:

from agent_sandbox import Sandbox

client = Sandbox(base_url="http://localhost:8080")

result = client.bash.exec(
    command='printf "out\\n" && printf "err\\n" >&2',
    timeout=30,
    hard_timeout=120,
).data

print(result.stdout)
print(result.stderr)
print(result.exit_code)

TypeScript:

import { SandboxClient } from "@agent-infra/sandbox";

const client = new SandboxClient({ baseUrl: "http://localhost:8080" });

const response = await client.bash.exec({
  command: 'printf "out\\n" && printf "err\\n" >&2',
  timeout: 30,
  hard_timeout: 120,
});

if (!response.ok) {
  throw new Error("Bash exec failed");
}

console.log(response.body.data?.stdout);
console.log(response.body.data?.stderr);
console.log(response.body.data?.exit_code);

长命令与增量输出

当命令超过 timeout 仍未完成时,/v1/bash/exec 会返回 status: "running",命令继续在后台执行。随后使用 /v1/bash/output 获取增量输出。

# 1. 启动命令;如果 1 秒内没有完成,会返回 running
curl -X POST "http://localhost:8080/v1/bash/exec" \
  -H "Content-Type: application/json" \
  -d '{
    "command": "sleep 3; echo done",
    "timeout": 1,
    "hard_timeout": 30
  }'
# 2. 使用返回的 session_id、offset 和 stderr_offset 继续读取
curl -X POST "http://localhost:8080/v1/bash/output" \
  -H "Content-Type: application/json" \
  -d '{
    "session_id": "SESSION_ID",
    "offset": 0,
    "stderr_offset": 0,
    "wait": true,
    "wait_timeout": 10
  }'

wait=false 会立即返回当前已有输出;wait=true 会长轮询,直到有新输出、命令结束或 wait_timeout 到期。Agent 轮询长命令时推荐使用 wait=true,减少无意义的空轮询。

长驻进程

对不会自行退出的服务,使用 async_mode: true 立即返回,然后读取启动日志,用完后调用 /kill

curl -X POST "http://localhost:8080/v1/bash/exec" \
  -H "Content-Type: application/json" \
  -d '{
    "command": "python3 -m http.server 3000 --directory /tmp",
    "async_mode": true,
    "hard_timeout": 3600
  }'

读取日志:

curl -X POST "http://localhost:8080/v1/bash/output" \
  -H "Content-Type: application/json" \
  -d '{
    "session_id": "SESSION_ID",
    "offset": 0,
    "stderr_offset": 0,
    "wait": true,
    "wait_timeout": 5
  }'

停止进程:

curl -X POST "http://localhost:8080/v1/bash/kill" \
  -H "Content-Type: application/json" \
  -d '{
    "session_id": "SESSION_ID",
    "signal": "SIGTERM"
  }'

写入 stdin

/v1/bash/write 会写入运行中进程的 stdin pipe。适合 cat、脚本提示输入、REPL 等场景。面向行缓冲程序时,输入末尾通常需要包含 \n

# 启动一个等待 stdin 的进程
curl -X POST "http://localhost:8080/v1/bash/exec" \
  -H "Content-Type: application/json" \
  -d '{
    "command": "cat",
    "async_mode": true,
    "hard_timeout": 120
  }'
# 写入一行输入
curl -X POST "http://localhost:8080/v1/bash/write" \
  -H "Content-Type: application/json" \
  -d '{
    "session_id": "SESSION_ID",
    "input": "hello from stdin\n"
  }'
# 读取刚写入的输出
curl -X POST "http://localhost:8080/v1/bash/output" \
  -H "Content-Type: application/json" \
  -d '{
    "session_id": "SESSION_ID",
    "offset": 0,
    "wait": true,
    "wait_timeout": 5
  }'

有些程序会把提示符写到 stderr,例如部分 REPL 或交互式命令。接入时应同时读取 stdoutstderr

Session 状态

/v1/bash 是每次 exec 创建一个新进程的模型。同一 session_id 会保留 API 级状态,例如默认工作目录;但命令内部的 cdexport 不会影响后续调用。

使用 exec_dir 设置 session 默认工作目录:

curl -X POST "http://localhost:8080/v1/bash/exec" \
  -H "Content-Type: application/json" \
  -d '{
    "command": "pwd",
    "exec_dir": "/tmp"
  }'

复用同一 session:

curl -X POST "http://localhost:8080/v1/bash/exec" \
  -H "Content-Type: application/json" \
  -d '{
    "session_id": "SESSION_ID",
    "command": "pwd && echo ${MY_VAR:-unset}"
  }'

如果希望后续命令继续使用某个目录,请继续传 exec_dir 或在 API 层更新 session 默认目录,不要依赖命令内部的 cd

参数速查

POST /v1/bash/exec 常用参数:

参数类型说明
commandstring要执行的 shell 命令
session_idstring目标 session;不传会自动创建
exec_dirstring工作目录,必须是绝对路径;会更新 session 默认目录
envobject仅对本次命令生效的环境变量
async_modebooleantrue 时立即返回 running
timeoutnumber软超时;超时后返回 running,命令后台继续
hard_timeoutnumber硬超时;到期后终止进程,状态为 timed_out
max_output_lengthnumber同步响应里 stdout / stderr 的最大长度;默认 50000,设为 0 可关闭本次截断

POST /v1/bash/output 常用参数:

参数类型说明
session_idstring目标 session
command_idstring可选,指定某条异步命令
offsetnumber从哪个 stdout offset 开始读
stderr_offsetnumber从哪个 stderr offset 开始读
waitboolean是否长轮询等待新输出
wait_timeoutnumberwait=true 时的最长等待秒数

错误处理

/v1/bash 的 HTTP 成功只表示请求被服务接受,不表示命令本身成功。推荐判断顺序:

  1. 先看 HTTP 状态码。
  2. 再看响应体里的 success
  3. 读取 data.status 判断命令生命周期。
  4. status="completed" 时,再用 exit_code 判断命令是否成功。
import requests

response = requests.post(
    "http://localhost:8080/v1/bash/exec",
    json={"command": "python3 missing.py", "timeout": 30},
)
response.raise_for_status()

payload = response.json()
result = payload["data"]

if result["status"] == "running":
    print("command is still running; call /v1/bash/output")
elif result["status"] == "completed" and result["exit_code"] != 0:
    print("command completed with a non-zero exit code")
elif result["status"] in {"timed_out", "killed"}:
    print(f"command interrupted: {result['status']}")

相关接口

  • POST /v1/bash/exec
  • POST /v1/bash/output
  • POST /v1/bash/write
  • POST /v1/bash/kill
  • GET /v1/bash/sessions
  • POST /v1/bash/sessions/create
  • POST /v1/bash/sessions/{session_id}/close