Skip to content

Commit 1b43bbf

Browse files
Port over file editor tool (#2)
Co-authored-by: openhands <openhands@all-hands.dev>
1 parent d9fe284 commit 1b43bbf

40 files changed

+4412
-832
lines changed

.github/workflows/precommit.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# .github/workflows/precommit.yml
2+
name: Pre-commit checks
3+
4+
on:
5+
push:
6+
branches: ["**"] # all branches
7+
8+
jobs:
9+
pre-commit:
10+
runs-on: ubuntu-latest
11+
12+
steps:
13+
- name: Checkout code
14+
uses: actions/checkout@v4
15+
16+
- name: Set up Python
17+
uses: actions/setup-python@v5
18+
with:
19+
python-version: "3.12"
20+
21+
- name: Install uv
22+
uses: astral-sh/setup-uv@v3
23+
24+
- name: Install dependencies
25+
run: uv sync --frozen
26+
27+
- name: Run pre-commit (all files)
28+
uses: pre-commit/action@v3.0.1
29+
with:
30+
extra_args: --all-files

.github/workflows/tests.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: Run tests
2+
on:
3+
push:
4+
branches: ["**"]
5+
6+
jobs:
7+
tests:
8+
runs-on: ubuntu-latest
9+
steps:
10+
- name: Checkout code
11+
uses: actions/checkout@v4
12+
13+
- name: Set up Python
14+
uses: actions/setup-python@v5
15+
with:
16+
python-version: "3.12"
17+
18+
- name: Install uv
19+
uses: astral-sh/setup-uv@v3
20+
21+
- name: Install dependencies
22+
run: uv sync --frozen --group dev
23+
24+
- name: Install project (editable)
25+
run: uv pip install -e .
26+
27+
- name: Run pytest
28+
run: uv run pytest -v tests/

.pre-commit-config.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,11 @@ repos:
1717
types: [python]
1818
pass_filenames: false
1919
always_run: true
20+
- id: pyright
21+
name: Type check with Pyright (strict)
22+
entry: uv
23+
args: [run, pyright]
24+
language: system
25+
types: [python]
26+
pass_filenames: false
27+
always_run: true

openhands/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""OpenHands package."""

openhands/core/__init__.py

Whitespace-only changes.

openhands/core/logger.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# simple_logger.py
2+
"""
3+
Minimal logger setup that encourages per-module loggers.
4+
5+
Usage:
6+
from openhands.core.logger import get_logger
7+
logger = get_logger(__name__)
8+
logger.info("Hello from this module!")
9+
"""
10+
11+
import logging
12+
import os
13+
from logging.handlers import TimedRotatingFileHandler
14+
15+
# ========= ENV (loaded at import) =========
16+
LEVEL_MAP = (
17+
logging.getLevelNamesMapping()
18+
if hasattr(logging, "getLevelNamesMapping")
19+
else logging._nameToLevel
20+
)
21+
22+
ENV_LOG_LEVEL_STR = os.getenv("LOG_LEVEL", "INFO").upper()
23+
ENV_LOG_LEVEL = LEVEL_MAP.get(ENV_LOG_LEVEL_STR, logging.INFO)
24+
ENV_LOG_TO_FILE = os.getenv("LOG_TO_FILE", "false").lower() in {"1", "true", "yes"}
25+
ENV_LOG_DIR = os.getenv("LOG_DIR", "logs")
26+
ENV_ROTATE_WHEN = os.getenv("LOG_ROTATE_WHEN", "midnight")
27+
ENV_BACKUP_COUNT = int(os.getenv("LOG_BACKUP_COUNT", "7"))
28+
ENV_FORMAT = os.getenv(
29+
"LOG_FORMAT",
30+
"%(asctime)s | %(levelname)s | %(name)s | %(pathname)s:%(lineno)d | %(message)s",
31+
)
32+
ENV_AUTO_CONFIG = os.getenv("LOG_AUTO_CONFIG", "true").lower() in {"1", "true", "yes"}
33+
34+
35+
# ========= SETUP =========
36+
def setup_logging(
37+
level: int | None = None,
38+
log_to_file: bool | None = None,
39+
log_dir: str | None = None,
40+
fmt: str | None = None,
41+
when: str | None = None,
42+
backup_count: int | None = None,
43+
) -> None:
44+
"""Configure the root logger. All child loggers inherit this setup."""
45+
lvl = ENV_LOG_LEVEL if level is None else level
46+
to_file = ENV_LOG_TO_FILE if log_to_file is None else log_to_file
47+
directory = ENV_LOG_DIR if log_dir is None else log_dir
48+
format_str = ENV_FORMAT if fmt is None else fmt
49+
rotate_when = ENV_ROTATE_WHEN if when is None else when
50+
keep = ENV_BACKUP_COUNT if backup_count is None else backup_count
51+
52+
root = logging.getLogger()
53+
root.setLevel(lvl)
54+
root.handlers = [] # reset
55+
56+
formatter = logging.Formatter(format_str)
57+
58+
ch = logging.StreamHandler()
59+
ch.setLevel(lvl)
60+
ch.setFormatter(formatter)
61+
root.addHandler(ch)
62+
63+
if to_file:
64+
os.makedirs(directory, exist_ok=True)
65+
fh = TimedRotatingFileHandler(
66+
os.path.join(directory, "app.log"),
67+
when=rotate_when,
68+
backupCount=keep,
69+
encoding="utf-8",
70+
)
71+
fh.setLevel(lvl)
72+
fh.setFormatter(formatter)
73+
root.addHandler(fh)
74+
75+
76+
def get_logger(name: str) -> logging.Logger:
77+
"""Return a logger for the given module name."""
78+
return logging.getLogger(name)
79+
80+
81+
# Auto-configure if desired
82+
if ENV_AUTO_CONFIG:
83+
setup_logging()

openhands/core/runtime/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""OpenHands runtime package."""
File renamed without changes.

openhands/core/runtime/security.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from typing import Literal
2+
3+
SECURITY_RISK_DESC = "The LLM's assessment of the safety risk of this action."
4+
SECURITY_RISK_LITERAL = Literal["LOW", "MEDIUM", "HIGH"]

openhands/runtime/tool.py renamed to openhands/core/runtime/tool.py

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,33 @@
11
from typing import Any, Callable
2-
from pydantic import BaseModel
2+
from pydantic import BaseModel, Field
33
from .schema import ActionBase, ObservationBase, Schema
44

55

66
class ToolAnnotations(BaseModel):
7-
title: str | None = None
8-
readOnlyHint: bool | None = None
9-
destructiveHint: bool | None = None
10-
idempotentHint: bool | None = None
11-
openWorldHint: bool | None = None
7+
"""Annotations to provide hints about the tool's behavior.
8+
9+
Based on Model Context Protocol (MCP) spec: https://github.com/modelcontextprotocol/modelcontextprotocol/blob/caf3424488b10b4a7b1f8cb634244a450a1f4400/schema/2025-06-18/schema.ts#L838
10+
"""
11+
12+
title: str | None = Field(
13+
default=None, description="A human-readable title for the tool."
14+
)
15+
readOnlyHint: bool = Field(
16+
default=False,
17+
description="If true, the tool does not modify its environment. Default: false",
18+
)
19+
destructiveHint: bool = Field(
20+
default=True,
21+
description="If true, the tool may perform destructive updates to its environment. If false, the tool performs only additive updates. (This property is meaningful only when `readOnlyHint == false`) Default: true",
22+
)
23+
idempotentHint: bool = Field(
24+
default=False,
25+
description="If true, calling the tool repeatedly with the same arguments will have no additional effect on the its environment. (This property is meaningful only when `readOnlyHint == false`) Default: false",
26+
)
27+
openWorldHint: bool = Field(
28+
default=True,
29+
description="If true, this tool may interact with an 'open world' of external entities. If false, the tool's domain of interaction is closed. For example, the world of a web search tool is open, whereas that of a memory tool is not. Default: true",
30+
)
1231

1332

1433
class Tool:

0 commit comments

Comments
 (0)