-
Notifications
You must be signed in to change notification settings - Fork 4.4k
Expand file tree
/
Copy pathshell.py
More file actions
141 lines (122 loc) · 4.52 KB
/
Copy pathshell.py
File metadata and controls
141 lines (122 loc) · 4.52 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
import argparse
import asyncio
import os
from collections.abc import Sequence
from pathlib import Path
from agents import (
Agent,
ModelSettings,
Runner,
ShellCallOutcome,
ShellCommandOutput,
ShellCommandRequest,
ShellResult,
ShellTool,
trace,
)
from agents.items import ToolApprovalItem
from agents.run_context import RunContextWrapper
from agents.tool import ShellOnApprovalFunctionResult
SHELL_AUTO_APPROVE = os.environ.get("SHELL_AUTO_APPROVE") == "1"
class ShellExecutor:
"""Executes shell commands; approval is handled via ShellTool."""
def __init__(self, cwd: Path | None = None):
self.cwd = Path(cwd or Path.cwd())
async def __call__(self, request: ShellCommandRequest) -> ShellResult:
action = request.data.action
outputs: list[ShellCommandOutput] = []
for command in action.commands:
proc = await asyncio.create_subprocess_shell(
command,
cwd=self.cwd,
env=os.environ.copy(),
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
timed_out = False
try:
timeout = (action.timeout_ms or 0) / 1000 or None
stdout_bytes, stderr_bytes = await asyncio.wait_for(
proc.communicate(), timeout=timeout
)
except asyncio.TimeoutError:
proc.kill()
stdout_bytes, stderr_bytes = await proc.communicate()
timed_out = True
stdout = stdout_bytes.decode("utf-8", errors="ignore")
stderr = stderr_bytes.decode("utf-8", errors="ignore")
outputs.append(
ShellCommandOutput(
command=command,
stdout=stdout,
stderr=stderr,
outcome=ShellCallOutcome(
type="timeout" if timed_out else "exit",
exit_code=getattr(proc, "returncode", None),
),
)
)
if timed_out:
break
return ShellResult(
output=outputs,
provider_data={"working_directory": str(self.cwd)},
)
async def prompt_shell_approval(commands: Sequence[str]) -> bool:
"""Simple CLI prompt for shell approvals."""
if SHELL_AUTO_APPROVE:
return True
print("Shell command approval required:")
for entry in commands:
print(" ", entry)
response = input("Proceed? [y/N] ").strip().lower()
return response in {"y", "yes"}
async def main(prompt: str, model: str) -> None:
with trace("shell_example"):
print(f"[info] Using model: {model}")
async def on_shell_approval(
_context: RunContextWrapper, approval_item: ToolApprovalItem
) -> ShellOnApprovalFunctionResult:
raw = approval_item.raw_item
commands: Sequence[str] = ()
if isinstance(raw, dict):
action = raw.get("action", {})
if isinstance(action, dict):
commands = action.get("commands", [])
else:
action_obj = getattr(raw, "action", None)
if action_obj and hasattr(action_obj, "commands"):
commands = action_obj.commands
approved = await prompt_shell_approval(commands)
return {"approve": approved, "reason": "user rejected" if not approved else "approved"}
agent = Agent(
name="Shell Assistant",
model=model,
instructions=(
"You can run shell commands using the shell tool. "
"Keep responses concise and include command output when helpful."
),
tools=[
ShellTool(
executor=ShellExecutor(),
needs_approval=True,
on_approval=on_shell_approval,
)
],
model_settings=ModelSettings(tool_choice="required"),
)
result = await Runner.run(agent, prompt)
print(f"\nFinal response:\n{result.final_output}")
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument(
"--prompt",
default="Show the list of files in the current directory.",
help="Instruction to send to the agent.",
)
parser.add_argument(
"--model",
default="gpt-5.6-sol",
)
args = parser.parse_args()
asyncio.run(main(args.prompt, args.model))