Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ default_stages: [ commit ]
# 2. pre-commit install
# 3. pre-commit run --all-files # make sure all files are clean
repos:
- repo: https://github.com/pycqa/isort
- repo: git@github.com:pycqa/isort.git
rev: 5.11.5
hooks:
- id: isort
Expand All @@ -15,14 +15,14 @@ repos:
.*__init__\.py$
)
- repo: https://github.com/astral-sh/ruff-pre-commit
- repo: git@github.com:astral-sh/ruff-pre-commit.git
# Ruff version.
rev: v0.0.284
hooks:
- id: ruff
args: [ --fix ]

- repo: https://github.com/psf/black
- repo: git@github.com:psf/black.git
rev: 23.3.0
hooks:
- id: black
Expand Down
47 changes: 30 additions & 17 deletions metagpt/tools/libs/terminal.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import asyncio
import os
import re
import sys
from asyncio import Queue
from asyncio.subprocess import PIPE, STDOUT
from typing import Optional
Expand All @@ -22,8 +23,17 @@ class Terminal:
"""

def __init__(self):
self.shell_command = ["bash"] # FIXME: should consider windows support later
self.command_terminator = "\n"
if sys.platform.startswith("win"):
self.shell_command = ["cmd.exe"] # Windows
self.executable = None
self.command_terminator = "\r\n"
self.pwd_command = "cd"
else:
self.shell_command = ["bash"] # Linux / macOS
self.executable = "bash"
self.command_terminator = "\n"
self.pwd_command = "pwd"

self.stdout_queue = Queue(maxsize=1000)
self.observer = TerminalReporter()
self.process: Optional[asyncio.subprocess.Process] = None
Expand All @@ -41,17 +51,17 @@ async def _start_process(self):
stdin=PIPE,
stdout=PIPE,
stderr=STDOUT,
executable="bash",
executable=self.executable,
env=os.environ.copy(),
cwd=DEFAULT_WORKSPACE_ROOT.absolute(),
cwd=str(DEFAULT_WORKSPACE_ROOT) if sys.platform.startswith("win") else DEFAULT_WORKSPACE_ROOT, # Windows
)
await self._check_state()

async def _check_state(self):
"""
Check the state of the terminal, e.g. the current directory of the terminal process. Useful for agent to understand.
Check the state of the terminal, e.g. the current directory.
"""
output = await self.run_command("pwd")
output = await self.run_command(self.pwd_command)
logger.info("The terminal is at:", output)

async def run_command(self, cmd: str, daemon=False) -> str:
Expand All @@ -74,20 +84,21 @@ async def run_command(self, cmd: str, daemon=False) -> str:
output = ""
# Remove forbidden commands
commands = re.split(r"\s*&&\s*", cmd)
skip_cmd = "echo Skipped" if sys.platform.startswith("win") else "true"
for cmd_name, reason in self.forbidden_commands.items():
# "true" is a pass command in linux terminal.
for index, command in enumerate(commands):
if cmd_name in command:
output += f"Failed to execut {command}. {reason}\n"
commands[index] = "true"
output += f"Failed to execute {command}. {reason}\n"
commands[index] = skip_cmd
cmd = " && ".join(commands)

# Send the command
self.process.stdin.write((cmd + self.command_terminator).encode())
self.process.stdin.write(
f'echo "{END_MARKER_VALUE}"{self.command_terminator}'.encode() # write EOF
) # Unique marker to signal command end

marker_cmd = f"echo {END_MARKER_VALUE}"
self.process.stdin.write((marker_cmd + self.command_terminator).encode()) # Unique marker to signal command end
await self.process.stdin.drain()

if daemon:
asyncio.create_task(self._read_and_process_output(cmd))
else:
Expand Down Expand Up @@ -116,7 +127,8 @@ async def execute_in_conda_env(self, cmd: str, env, daemon=False) -> str:
This function wraps `run_command`, prepending the necessary Conda activation commands
to ensure the specified environment is active for the command's execution.
"""
cmd = f"conda run -n {env} {cmd}"
# windows & linux conda run
cmd = f"conda activate {env} && {cmd}" if sys.platform.startswith("win") else f"conda run -n {env} {cmd}"
return await self.run_command(cmd, daemon=daemon)

async def get_stdout_output(self) -> str:
Expand Down Expand Up @@ -147,10 +159,10 @@ async def _read_and_process_output(self, cmd, daemon=False) -> str:
continue
*lines, tmp = output.splitlines(True)
for line in lines:
line = line.decode()
line = line.decode(errors="ignore")
ix = line.rfind(END_MARKER_VALUE)
if ix >= 0:
line = line[0:ix]
line = line[:ix]
if line:
await observer.async_report(line, "output")
# report stdout in real-time
Expand All @@ -164,8 +176,9 @@ async def _read_and_process_output(self, cmd, daemon=False) -> str:

async def close(self):
"""Close the persistent shell process."""
self.process.stdin.close()
await self.process.wait()
if self.process:
self.process.stdin.close()
await self.process.wait()


@register_tool(include_functions=["run"])
Expand Down
4 changes: 2 additions & 2 deletions metagpt/utils/token_counter.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,10 @@
"doubao-pro-128k-240515": {"prompt": 0.0007, "completion": 0.0013},
"llama3-70b-llama3-70b-instruct": {"prompt": 0.0, "completion": 0.0},
"llama3-8b-llama3-8b-instruct": {"prompt": 0.0, "completion": 0.0},
"llama-4-Scout-17B-16E-Instruct-FP8" : {"prompt": 0.0, "completion": 0.0}, # start, for Llama API
"llama-4-Scout-17B-16E-Instruct-FP8": {"prompt": 0.0, "completion": 0.0}, # start, for Llama API
"llama-4-Maverick-17B-128E-Instruct-FP8": {"prompt": 0.0, "completion": 0.0},
"llama-3.3-8B-Instruct": {"prompt": 0.0, "completion": 0.0},
"llama-3.3-70B-Instruct": {"prompt": 0.0, "completion": 0.0}, # end, for Llama API
"llama-3.3-70B-Instruct": {"prompt": 0.0, "completion": 0.0}, # end, for Llama API
}


Expand Down
Loading