diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/__main__.py b/__main__.py new file mode 100644 index 0000000..829ff24 --- /dev/null +++ b/__main__.py @@ -0,0 +1,4 @@ +import runpy + +if __name__ == "__main__": + runpy.run_module("seclab_taskflow_agent.main", run_name="__main__") diff --git a/agent.py b/agent.py index 4ddf7fb..f254306 100644 --- a/agent.py +++ b/agent.py @@ -12,7 +12,7 @@ from agents.run import RunHooks from agents import Agent, Runner, AgentHooks, RunHooks, result, function_tool, Tool, RunContextWrapper, TContext, OpenAIChatCompletionsModel, set_default_openai_client, set_default_openai_api, set_tracing_disabled -from capi import COPILOT_INTEGRATION_ID, COPILOT_API_ENDPOINT +from .capi import COPILOT_INTEGRATION_ID, COPILOT_API_ENDPOINT # grab our secrets from .env, this must be in .gitignore load_dotenv() diff --git a/main.py b/main.py index 02bdfcf..7c09973 100644 --- a/main.py +++ b/main.py @@ -12,7 +12,7 @@ import uuid import pathlib -from agent import DEFAULT_MODEL, TaskRunHooks, TaskAgentHooks +from .agent import DEFAULT_MODEL, TaskRunHooks, TaskAgentHooks #from agents.run import DEFAULT_MAX_TURNS # XXX: this is 10, we need more than that from agents.exceptions import MaxTurnsExceeded, AgentsException from agents.agent import ModelSettings @@ -23,21 +23,27 @@ from openai.types.responses import ResponseTextDeltaEvent from typing import Any -from shell_utils import shell_tool_call -from mcp_utils import DEFAULT_MCP_CLIENT_SESSION_TIMEOUT, ReconnectingMCPServerStdio, AsyncDebugMCPServerStdio, MCPNamespaceWrap, mcp_client_params, mcp_system_prompt, StreamableMCPThread, compress_name -from render_utils import render_model_output, flush_async_output -from env_utils import TmpEnv -from yaml_parser import YamlParser -from agent import TaskAgent -from capi import list_tool_call_models -from available_tools import AvailableTools +from .shell_utils import shell_tool_call +from .mcp_utils import DEFAULT_MCP_CLIENT_SESSION_TIMEOUT, ReconnectingMCPServerStdio, AsyncDebugMCPServerStdio, MCPNamespaceWrap, mcp_client_params, mcp_system_prompt, StreamableMCPThread, compress_name +from .render_utils import render_model_output, flush_async_output +from .env_utils import TmpEnv +from .yaml_parser import YamlParser +from .agent import TaskAgent +from .capi import list_tool_call_models +from .available_tools import AvailableTools load_dotenv() # only model output or help message should go to stdout, everything else goes to log logging.getLogger('').setLevel(logging.NOTSET) + +# Create logs directory if it doesn't exist +# This is needed when running the code as a package from any directory +log_dir = os.path.join(os.getcwd(), 'logs') +os.makedirs(log_dir, exist_ok=True) + log_file_handler = RotatingFileHandler( - 'logs/task_agent.log', + os.path.join(log_dir, 'task_agent.log'), maxBytes=1024*1024*10, backupCount=10) log_file_handler.setLevel(os.getenv('TASK_AGENT_LOGLEVEL', default='DEBUG')) diff --git a/mcp_utils.py b/mcp_utils.py index c1fb40e..b656d6d 100644 --- a/mcp_utils.py +++ b/mcp_utils.py @@ -15,7 +15,7 @@ from mcp.types import CallToolResult, TextContent from agents.mcp import MCPServerStdio -from env_utils import swap_env +from .env_utils import swap_env DEFAULT_MCP_CLIENT_SESSION_TIMEOUT = 120 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..d471146 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,30 @@ +[build-system] +requires = ["hatchling >= 1.26", "hatch-requirements-txt"] +build-backend = "hatchling.build" + +[project] +name = "seclab-taskflow-agent" +version = "0.0.1" +dynamic = ["dependencies"] +authors = [ + { name="GitHub Security Lab", email="securitylab@github.com" }, +] +description = "A taskflow agent for the SecLab project, enabling secure and automated workflow execution." +readme = "seclab_taskflow_agent/README.md" +requires-python = ">=3.9" +classifiers = [ + "Programming Language :: Python :: 3", + "Operating System :: OS Independent", +] +license = "MIT" +license-files = ["seclab_taskflow_agent/LICEN[CS]E*"] + +[tool.hatch.metadata.hooks.requirements_txt] +files = ["seclab_taskflow_agent/requirements.txt"] + +[tool.hatch.build.targets.wheel] +packages = ["seclab_taskflow_agent"] + +[project.urls] +Homepage = "https://github.com/GitHubSecurityLab/seclab-taskflow-agent" +Issues = "https://github.com/GitHubSecurityLab/seclab-taskflow-agent/issues" diff --git a/release_tools/HOWTO.md b/release_tools/HOWTO.md index 2dae984..55a7ae7 100644 --- a/release_tools/HOWTO.md +++ b/release_tools/HOWTO.md @@ -33,3 +33,68 @@ server_params: command: docker args: ["run", "--entrypoint", "python" "-i", "--rm", "ghcr.io/githubsecuritylab/seclab-taskflow-agent", "toolboxes/mcp_servers/echo/echo.py"] ``` + +# How to test and release new PyPI package + +See [the packaging tutorial](https://packaging.python.org/en/latest/tutorials/packaging-projects/#namespace-packages). + +We need all the code to be in a separate directory to build it into a package, so we create a new dir and copy what is need for the build. + +For packages, only underscores are allowed as legal python identifiers and not dashes, so we need to rename the folder. +```bash +mkdir taskflow-package +cd taskflow-package +git clone https://github.com/GitHubSecurityLab/seclab-taskflow-agent.git +mv seclab-taskflow-agent seclab_taskflow_agent +``` + +Build instructions are in pyproject.toml, and we also need .gitignore to ignore the `venv`. +```bash +cp seclab_taskflow_agent/pyproject.toml pyproject.toml +cp seclab_taskflow_agent/.gitignore .gitignore + +# Create a new python virtual env, activate it, and install the tools for the build. +python -m venv venv +source venv/bin/activate + +pip install --upgrade build +pip install hatch-requirements-txt + +python -m build +pip install --upgrade twine + +``` + +This will create a `dist` directory with a tar.gz source distribution and whl built distribution. + +You can test if the package works without uploading it to PyPI by installing it with the whl. Use ` --force-reinstall` if you made a new version of the package. We use `pydantic_core` in the deps, which doesn't seem to work on some versions of macos due to Rust bindings. +```bash +pip install dist/seclab_taskflow_agent-0.0.1-py3-none-any.whl +``` + +Create an .env file with `COPILOT_TOKEN`, and run the package with: +```bash +python -m seclab_taskflow_agent -p assistant 'how do modems work' +``` + +To upload it to TestPyPI (you'll need [an account on testpypi and an API token](https://packaging.python.org/en/latest/tutorials/packaging-projects/#uploading-the-distribution-archives)). Note if you then try to download the package from TestPyPI and run it, it won't work, because TestPyPi does not have the dependencies that are required for seclab-taskflow-agent. New packages on TestPyPI are regularly cleared. Test it instead using the wheel, or by using PyPI. +```bash +python -m twine upload --repository testpypi dist/* +``` + +To upload it on PyPI (you'll need [an account on PyPI and an API token](https://packaging.python.org/en/latest/tutorials/packaging-projects/#uploading-the-distribution-archives)). Note you need to update pyproject.toml to a new (higher) version. +```bash +python -m twine upload dist/* +``` + +Create a fresh venv, and download the package: +```bash +python -m venv .venv +source .venv/bin/activate +pip install seclab-taskflow-agent +``` + +Create an .env file with `COPILOT_TOKEN`, and run the package with: +```bash +python -m seclab_taskflow_agent -p assistant 'how do modems work' +```