@openai/agents-core
Version:
The OpenAI Agents SDK is a lightweight yet powerful framework for building multi-agent workflows.
162 lines (147 loc) • 5.12 kB
JavaScript
import { spawn, spawnSync, } from 'node:child_process';
import { SandboxConfigurationError } from "../../errors.mjs";
const PTY_BRIDGE_UID_ENV = '__OPENAI_AGENTS_PTY_UID';
const PTY_BRIDGE_GID_ENV = '__OPENAI_AGENTS_PTY_GID';
const PTY_BRIDGE_PYTHON_CHECK_SCRIPT = String.raw `
import pty
import select
import signal
import sys
sys.exit(0 if sys.version_info[0] >= 3 else 1)
`;
const PTY_BRIDGE_SCRIPT = String.raw `
import array
import errno
import fcntl
import os
import pty
import select
import signal
import sys
import termios
PTY_CHILD_SIGNAL_DEFAULTS = (signal.SIGINT, signal.SIGQUIT)
def restore_pty_child_signal_defaults():
for signum in PTY_CHILD_SIGNAL_DEFAULTS:
signal.signal(signum, signal.SIG_DFL)
executable = sys.argv[1]
argv = sys.argv[1:]
pid, fd = pty.fork()
if pid == 0:
try:
uid = os.environ.pop('__OPENAI_AGENTS_PTY_UID', '')
gid = os.environ.pop('__OPENAI_AGENTS_PTY_GID', '')
if gid:
os.setgid(int(gid))
if uid:
os.setuid(int(uid))
restore_pty_child_signal_defaults()
os.execvpe(executable, argv, os.environ)
except BaseException as exc:
os.write(2, (str(exc) + '\n').encode())
os._exit(127)
def forward_signal(signum, _frame):
try:
os.kill(pid, signum)
except ProcessLookupError:
pass
signal.signal(signal.SIGTERM, forward_signal)
signal.signal(signal.SIGINT, forward_signal)
exit_status = 0
stdin_open = True
drain_remaining = None
while drain_remaining is None or drain_remaining > 0:
readable = [fd]
if stdin_open and drain_remaining is None:
readable.append(sys.stdin.fileno())
try:
ready, _, _ = select.select(readable, [], [], 0.1 if drain_remaining is None else 0)
except OSError:
break
if not ready and drain_remaining is not None:
break
if fd in ready:
try:
data = os.read(fd, 4096 if drain_remaining is None else min(4096, drain_remaining))
except OSError as exc:
if exc.errno == errno.EIO:
data = b''
else:
raise
if data:
os.write(sys.stdout.fileno(), data)
if drain_remaining is not None:
drain_remaining -= len(data)
else:
break
if drain_remaining is None:
waited_pid, status = os.waitpid(pid, os.WNOHANG)
if waited_pid == pid:
if os.WIFEXITED(status):
exit_status = os.WEXITSTATUS(status)
elif os.WIFSIGNALED(status):
exit_status = 128 + os.WTERMSIG(status)
available = array.array('i', [0])
fcntl.ioctl(fd, termios.FIONREAD, available)
drain_remaining = available[0]
if stdin_open and drain_remaining is None and sys.stdin.fileno() in ready:
data = os.read(sys.stdin.fileno(), 4096)
if data:
os.write(fd, data)
else:
stdin_open = False
try:
_, status = os.waitpid(pid, 0)
if os.WIFEXITED(status):
exit_status = os.WEXITSTATUS(status)
elif os.WIFSIGNALED(status):
exit_status = 128 + os.WTERMSIG(status)
except ChildProcessError:
pass
sys.exit(exit_status)
`;
const checkedPtyBridgePythonExecutables = new Set();
export function spawnInPseudoTerminal(executable, args, options = {}) {
const env = { ...(options.env ?? process.env) };
const pythonExecutable = process.env.OPENAI_AGENTS_PYTHON ?? 'python3';
assertPtyBridgePythonAvailable(pythonExecutable, {
cwd: options.cwd,
env,
});
if (typeof options.uid === 'number') {
env[PTY_BRIDGE_UID_ENV] = String(options.uid);
}
if (typeof options.gid === 'number') {
env[PTY_BRIDGE_GID_ENV] = String(options.gid);
}
return spawn(pythonExecutable, ['-c', PTY_BRIDGE_SCRIPT, executable, ...args], {
cwd: options.cwd,
env,
stdio: 'pipe',
});
}
function assertPtyBridgePythonAvailable(pythonExecutable, options) {
const cacheKey = `${pythonExecutable}\0${options.cwd ?? ''}\0${options.env.PATH ?? ''}`;
if (checkedPtyBridgePythonExecutables.has(cacheKey)) {
return;
}
const result = spawnSync(pythonExecutable, ['-c', PTY_BRIDGE_PYTHON_CHECK_SCRIPT], {
cwd: options.cwd,
env: options.env,
stdio: 'pipe',
});
if (result.error || result.status !== 0) {
throw new SandboxConfigurationError('PTY support requires Python 3. Install python3 or set OPENAI_AGENTS_PYTHON to a Python 3 executable.', {
pythonExecutable,
cwd: options.cwd,
path: options.env.PATH,
status: result.status,
signal: result.signal,
error: result.error?.message,
stderr: result.stderr?.toString('utf8').trim() ?? '',
});
}
checkedPtyBridgePythonExecutables.add(cacheKey);
}
//# sourceMappingURL=pty.mjs.map