chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,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))
|
||||
Reference in New Issue
Block a user