-
Notifications
You must be signed in to change notification settings - Fork 192
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
70 changed files
with
453 additions
and
642 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
include README.md | ||
include LICENSE | ||
recursive-include src/inspect_multi_tool *.py *.svg *.md | ||
recursive-exclude * __pycache__ | ||
recursive-exclude * *.py[cod] | ||
recursive-exclude * *$py.class |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
# Multi-tool Package | ||
|
||
A simplified package that provides JSON-RPC tools for inspect_ai. | ||
|
||
## Installation | ||
|
||
The package can be installed with pip: | ||
|
||
```bash | ||
# Install the package directly from the source directory | ||
pip install . | ||
|
||
# Or from the project root directory | ||
pip install -e src/multi_tool/ | ||
``` | ||
|
||
## Usage | ||
|
||
Once installed, you can use the command-line tool to invoke JSON-RPC methods: | ||
|
||
```bash | ||
# Use the command-line tool | ||
multi-tool '{"jsonrpc": "2.0", "method": "editor", "id": 1, "params": {"command": "view", "path": "/tmp"}}' | ||
|
||
# Or using Python module syntax | ||
python -m multi_tool '{"jsonrpc": "2.0", "method": "editor", "id": 1, "params": {"command": "view", "path": "/tmp"}}' | ||
``` | ||
|
||
## Features | ||
|
||
1. Simple CLI tool (`multi-tool`) for executing specified tools with given parameters | ||
2. Support for in-process JSON-RPC tools | ||
3. Validation of parameters using Pydantic models | ||
|
||
## Package Structure | ||
|
||
The package has a minimal structure: | ||
|
||
- `multi_tool/`: Main package | ||
- `_in_process_tools/`: Directory containing tool implementations | ||
- `_editor/`: Editor tool implementation | ||
- `_util/`: Utility functions for the tools | ||
- `__main__.py`: Entry point for module execution | ||
- `multi_tool.py`: Main CLI implementation | ||
|
||
## Adding New Tools | ||
|
||
To add a new tool: | ||
|
||
1. Create a new directory under `_in_process_tools/` with an underscore prefix (e.g., `_new_tool/`) | ||
2. Create files in the tool directory: | ||
- `__init__.py`: Empty file for the package | ||
- `tool_types.py`: Pydantic models for the tool parameters | ||
- `<tool>.py`: Implementation of the tool functionality | ||
- `json_rpc_methods.py`: JSON-RPC method definitions | ||
|
||
The tool will be automatically detected and loaded when the package is installed. |
File renamed without changes.
File renamed without changes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
[build-system] | ||
requires = ["setuptools>=64", "wheel"] | ||
build-backend = "setuptools.build_meta" | ||
|
||
[tool.setuptools.packages.find] | ||
where = ["src"] | ||
include = ["inspect_multi_tool*"] | ||
|
||
[tool.setuptools.package-data] | ||
"inspect_multi_tool" = ["**/*.svg", "**/*.md"] | ||
|
||
|
||
[tool.ruff] | ||
extend-exclude = ["docs"] | ||
src = ["."] | ||
|
||
[tool.ruff.lint] | ||
select = [ | ||
"E", # pycodestyle errors | ||
"W", # pycodestyle warnings | ||
"F", # flake8 | ||
"D", # pydocstyle | ||
"I", # isort | ||
"SIM101", # duplicate isinstance | ||
"UP038", # non-pep604-isinstance | ||
# "RET", # flake8-return | ||
# "RUF", # ruff rules | ||
] | ||
ignore = ["E203", "E501", "D10", "D212", "D415"] | ||
|
||
[tool.ruff.lint.pydocstyle] | ||
convention = "google" | ||
|
||
[tool.mypy] | ||
exclude = ["tests/test_package", "build", "(?:^|/)_resources/", "examples/bridge"] | ||
warn_unused_ignores = true | ||
no_implicit_reexport = true | ||
strict_equality = true | ||
warn_redundant_casts = true | ||
warn_unused_configs = true | ||
# This mypy_path config is a bit odd, it's included to get mypy to resolve | ||
# imports correctly in test files. For example, imports such as | ||
# `from test_helpers.utils import ...` fail mypy without this configuration, | ||
# despite actually working when running tests. | ||
# | ||
# Revisit this if it interferes with mypy running on `src` due to name | ||
# conflicts, but that will hopefully be unlikely. | ||
mypy_path = "tests" | ||
|
||
[[tool.mypy.overrides]] | ||
module = ["inspect_ai.*"] | ||
warn_return_any = true | ||
disallow_untyped_defs = true | ||
disallow_any_generics = true | ||
disallow_subclassing_any = true | ||
disallow_untyped_calls = true | ||
disallow_incomplete_defs = true | ||
check_untyped_defs = true | ||
disallow_untyped_decorators = true | ||
extra_checks = true | ||
disable_error_code = "unused-ignore" | ||
|
||
[tool.check-wheel-contents] | ||
ignore = ["W002", "W009"] | ||
|
||
[project] | ||
name = "inspect_multi_tool" | ||
version = "0.1.0" | ||
description = "Multi-tool sandbox container code for inspect_ai" | ||
authors = [{ name = "UK AI Security Institute" }] | ||
readme = "README.md" | ||
requires-python = ">=3.10" | ||
license = { text = "MIT License" } | ||
# dynamic = ["version", "dependencies"] | ||
classifiers = [ | ||
"Development Status :: 3 - Alpha", | ||
"Environment :: Console", | ||
"Intended Audience :: Science/Research", | ||
"Intended Audience :: Developers", | ||
"License :: OSI Approved :: MIT License", | ||
"Natural Language :: English", | ||
"Programming Language :: Python :: 3", | ||
"Topic :: Scientific/Engineering :: Artificial Intelligence", | ||
"Typing :: Typed", | ||
"Operating System :: OS Independent", | ||
] | ||
dependencies = [ | ||
"aiohttp", | ||
"httpx", | ||
"jsonrpcserver", | ||
"pydantic", | ||
"returns", | ||
"tenacity", | ||
"playwright", | ||
] | ||
|
||
[project.urls] | ||
|
||
[project.scripts] | ||
inspect-tool-exec = "inspect_multi_tool._cli.main:main" | ||
inspect-tool-install = "inspect_multi_tool._cli.post_install:main" | ||
inspect-tool-server = "inspect_multi_tool._cli.server:main" | ||
|
||
[project.optional-dependencies] | ||
|
||
dist = ["twine", "build"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
""" | ||
Multi-tool package for inspect_ai. | ||
Contains tools for web browser, bash, and editor functionality. | ||
""" | ||
|
||
__version__ = "0.1.0" | ||
|
||
from inspect_multi_tool._util._constants import SERVER_PORT | ||
from inspect_multi_tool._util._load_tools import load_tools | ||
|
||
__all__ = [ | ||
"__version__", | ||
"SERVER_PORT", | ||
"load_tools", | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
from ._cli.main import main | ||
|
||
if __name__ == "__main__": | ||
main() |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import argparse | ||
import asyncio | ||
import subprocess | ||
from typing import Literal | ||
|
||
from jsonrpcserver import async_dispatch | ||
from pydantic import BaseModel | ||
|
||
from inspect_multi_tool import SERVER_PORT | ||
from inspect_multi_tool._util._common_types import JSONRPCResponseJSON | ||
from inspect_multi_tool._util._json_rpc_helpers import json_rpc_http_call | ||
from inspect_multi_tool._util._load_tools import load_tools | ||
|
||
_SERVER_URL = f"http://localhost:{SERVER_PORT}/" | ||
|
||
|
||
class JSONRPCRequest(BaseModel): | ||
jsonrpc: Literal["2.0"] | ||
method: str | ||
id: int | float | str | ||
params: list[object] | dict[str, object] | None = None | ||
|
||
|
||
def main() -> None: | ||
asyncio.run(async_main()) | ||
|
||
|
||
# Example/testing requests | ||
# {"jsonrpc": "2.0", "method": "editor", "id": 666, "params": {"command": "view", "path": "/tmp"}} | ||
# {"jsonrpc": "2.0", "method": "bash", "id": 666, "params": {"command": "ls ~/Downloads"}} | ||
async def async_main() -> None: | ||
_ensure_daemon_is_running() | ||
|
||
in_process_tools = load_tools("inspect_multi_tool._in_process_tools") | ||
|
||
args: argparse.Namespace = parser.parse_args() | ||
|
||
validated_request = JSONRPCRequest.model_validate_json(args.request) | ||
tool_name = validated_request.method | ||
assert isinstance(tool_name, str) | ||
|
||
print( | ||
await ( | ||
_dispatch_local_method | ||
if tool_name in in_process_tools | ||
else _dispatch_remote_method | ||
)(args.request) | ||
) | ||
|
||
|
||
parser = argparse.ArgumentParser(prog="multi_tool_client") | ||
parser.add_argument( | ||
"request", type=str, help="A JSON string representing the JSON RPC 2.0 request" | ||
) | ||
|
||
|
||
async def _dispatch_local_method(request_json_str: str) -> JSONRPCResponseJSON: | ||
return JSONRPCResponseJSON(await async_dispatch(request_json_str)) | ||
|
||
|
||
async def _dispatch_remote_method(request_json_str: str) -> JSONRPCResponseJSON: | ||
return await json_rpc_http_call(_SERVER_URL, request_json_str) | ||
|
||
|
||
def _ensure_daemon_is_running() -> None: | ||
# TODO: Pipe stdout and stderr to proc 1 | ||
if b"inspect-tool-server" not in subprocess.check_output(["ps", "aux"]): | ||
subprocess.Popen( | ||
["inspect-tool-server"], | ||
stdout=subprocess.DEVNULL, | ||
stderr=subprocess.DEVNULL, | ||
) | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
39 changes: 39 additions & 0 deletions
39
src/eric_testing/src/inspect_multi_tool/_cli/post_install.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import subprocess | ||
import sys | ||
|
||
|
||
def install_playwright_dependencies(cmd=None): | ||
""" | ||
Install Playwright browsers and system dependencies. | ||
This function is called as a post-install hook and also available | ||
as a standalone command. | ||
Args: | ||
cmd: Used by setuptools for the entry point. Not used in the function. | ||
""" | ||
try: | ||
print("\n=== Installing Playwright dependencies ===") | ||
print("Installing Playwright browsers...") | ||
subprocess.run([sys.executable, "-m", "playwright", "install"], check=True) | ||
|
||
print("\nInstalling Playwright system dependencies...") | ||
subprocess.run([sys.executable, "-m", "playwright", "install-deps"], check=True) | ||
print("=== Playwright setup completed successfully ===\n") | ||
return True | ||
except Exception as e: | ||
print(f"\nError during Playwright setup: {e}", file=sys.stderr) | ||
print( | ||
"You may need to run 'playwright install' and 'playwright install-deps' manually after installation" | ||
) | ||
return False | ||
|
||
|
||
def main(): | ||
"""Main entry point for the script when run as a command.""" | ||
success = install_playwright_dependencies() | ||
return 0 if success else 1 | ||
|
||
|
||
if __name__ == "__main__": | ||
sys.exit(main()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
from aiohttp.web import Application, Request, Response, run_app | ||
from jsonrpcserver import async_dispatch | ||
|
||
from inspect_multi_tool import SERVER_PORT, load_tools | ||
|
||
|
||
def main() -> None: | ||
load_tools("inspect_multi_tool._remote_tools") | ||
|
||
async def handle_request(request: Request) -> Response: | ||
return Response( | ||
text=await async_dispatch(await request.text()), | ||
content_type="application/json", | ||
) | ||
|
||
app = Application() | ||
app.router.add_post("/", handle_request) | ||
|
||
print(f"Starting server on port {SERVER_PORT}") | ||
run_app(app, port=SERVER_PORT) | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
File renamed without changes.
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.