Skip to content
Merged
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: 6 additions & 0 deletions .github/workflows/pr-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,11 @@ jobs:
- name: Install dependencies
run: uv sync --group dev

- name: Run lint
run: uv run ruff check .

- name: Run typecheck
run: uv run mypy

- name: Run test suite
run: uv run pytest
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,16 @@ uv run deep-code-agent --backend-type filesystem --skills-dir .agents/skills

Skills currently require `--backend-type filesystem`; the state backend does not load local skill directories.

## Development Checks

Run the same checks used by CI before opening a pull request:

```bash
uv run ruff check .
uv run mypy
uv run pytest
```

### Working with Subagents

The Deep Code Agent includes specialized subagents that can be used independently or as part of the main workflow:
Expand Down
12 changes: 12 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,22 @@ build-backend = "uv_build"
[tool.pytest.ini_options]
addopts = "--cov=src/deep_code_agent --cov-report=term-missing --cov-fail-under=40"

[tool.ruff]
target-version = "py313"

[tool.mypy]
check_untyped_defs = true
files = ["src/deep_code_agent"]
python_version = "3.13"
show_error_codes = true
warn_unused_configs = true

[dependency-groups]
dev = [
"ipython>=9.8.0",
"langgraph-cli[inmem]>=0.4.9",
"mypy>=2.1.0",
"pytest>=9.0.2",
"pytest-cov>=7.1.0",
"ruff>=0.15.20",
]
2 changes: 1 addition & 1 deletion src/deep_code_agent/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ def _run_tui_mode(args) -> None:
dotenv_path = Path(codebase_dir) / ".env"
load_dotenv(dotenv_path=dotenv_path if dotenv_path.exists() else None)

print(f"🚀 Starting Deep Code Agent TUI...")
print("🚀 Starting Deep Code Agent TUI...")
print(f"📁 Codebase: {codebase_dir}")

def agent_factory():
Expand Down
4 changes: 3 additions & 1 deletion src/deep_code_agent/code_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ def create_code_agent(
system_prompt = get_system_prompt().format(codebase_dir=codebase_dir)
model = model or create_chat_model()
subagents = create_subagent_configurations()
backend: Any

if backend_type == "filesystem":
from deepagents.backends.filesystem import FilesystemBackend
Expand All @@ -75,9 +76,10 @@ def create_code_agent(
elif backend_type == "state":
from deepagents.backends.state import StateBackend

def backend(rt):
def create_state_backend(rt: Any) -> Any:
return StateBackend(rt)

backend = create_state_backend
tools = []
else:
raise ValueError(f"Unsupported backend_type: {backend_type}")
Expand Down
2 changes: 1 addition & 1 deletion src/deep_code_agent/models/llms/langchain_chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def create_chat_model(
base_url: str | None = None,
):
model_name = model_name or os.getenv("MODEL_NAME")
api_key = api_key or os.getenv("OPENAI_API_KEY", "EMPTY")
api_key = api_key or os.getenv("OPENAI_API_KEY") or "EMPTY"
base_url = base_url or os.getenv("OPENAI_API_BASE")
return init_chat_model(
model=model_name,
Expand Down
4 changes: 2 additions & 2 deletions src/deep_code_agent/tui/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ class DeepCodeAgentApp(App):

# Reactive state
dark = reactive(True)
session_info = reactive({})
auto_approve_tools = reactive([])
session_info: reactive[dict[str, Any]] = reactive({})
auto_approve_tools: reactive[list[str]] = reactive(list)

def __init__(
self,
Expand Down
7 changes: 4 additions & 3 deletions src/deep_code_agent/tui/bridge/stream_handler.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""Stream handler for processing LangGraph Agent output."""

import asyncio
import json
from dataclasses import dataclass
from enum import Enum, auto
Expand Down Expand Up @@ -154,7 +153,8 @@ def add_chunk(item: dict) -> None:
for item in additional_kwargs.get("tool_calls") or []:
if not isinstance(item, dict):
continue
function = item.get("function") if isinstance(item.get("function"), dict) else {}
function_value = item.get("function")
function = function_value if isinstance(function_value, dict) else {}
add_chunk(
{
"id": item.get("id"),
Expand Down Expand Up @@ -249,7 +249,8 @@ def _normalize_tool_call(self, tc: Any, *, fallback_name: str | None = None) ->
pass

if isinstance(tc, dict):
function = tc.get("function") if isinstance(tc.get("function"), dict) else {}
function_value = tc.get("function")
function = function_value if isinstance(function_value, dict) else {}
tool_name = tc.get("name") or tc.get("tool_name") or function.get("name") or ""
raw_args = tc.get("args") if "args" in tc else function.get("arguments")
tool_args = self._coerce_tool_args(raw_args)
Expand Down
3 changes: 1 addition & 2 deletions src/deep_code_agent/tui/screens/main_screen.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from pathlib import Path
from typing import TYPE_CHECKING, cast

from textual import work
from textual.app import ComposeResult
from textual.binding import Binding
from textual.containers import Vertical
Expand Down Expand Up @@ -113,8 +114,6 @@ def update_session_info(self, session_info: dict) -> None:
except Exception:
pass

from textual import work

@work(exclusive=True, name="agent_request")
async def process_agent_request(self, content: str) -> None:
"""Process the agent request in a worker."""
Expand Down
3 changes: 2 additions & 1 deletion src/deep_code_agent/tui/widgets/input_box.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Input composer widget for user message entry."""

from pathlib import Path
from typing import Any

from rich.markup import escape
from textual.binding import Binding
Expand Down Expand Up @@ -134,7 +135,7 @@ class InputBox(Vertical):

# Reactive state
disabled = reactive(False)
session_info = reactive({})
session_info: reactive[dict[str, Any]] = reactive({})

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
Expand Down
3 changes: 2 additions & 1 deletion src/deep_code_agent/tui/widgets/session_header.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from __future__ import annotations

from pathlib import Path
from typing import Any

from deep_code_agent import __version__
from textual.reactive import reactive
Expand All @@ -22,7 +23,7 @@ class SessionHeader(Static):
}
"""

session_info = reactive({})
session_info: reactive[dict[str, Any]] = reactive({})

def __init__(self, session_info: dict | None = None, **kwargs) -> None:
super().__init__("", markup=False, **kwargs)
Expand Down
5 changes: 3 additions & 2 deletions src/deep_code_agent/tui/widgets/side_panel.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Side panel widget for session context and recent activity."""

from pathlib import Path
from typing import Any

from textual.containers import Vertical
from textual.reactive import reactive
Expand Down Expand Up @@ -72,8 +73,8 @@ class SidePanel(Vertical):
}
"""

session_info = reactive({})
tool_calls = reactive(list)
session_info: reactive[dict[str, Any]] = reactive({})
tool_calls: reactive[list[dict[str, Any]]] = reactive(list)

def __init__(self, session_info: dict | None = None, **kwargs):
super().__init__(**kwargs)
Expand Down
6 changes: 4 additions & 2 deletions src/deep_code_agent/tui/widgets/status_bar.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
"""Status bar widget for displaying application state."""

from typing import Any

from rich.markup import escape
from textual.reactive import reactive
from textual.widgets import Static
from rich.markup import escape


class StatusBar(Static):
Expand Down Expand Up @@ -42,7 +44,7 @@ class StatusBar(Static):
# Reactive state
status = reactive("ready")
message = reactive("")
session_info = reactive({})
session_info: reactive[dict[str, Any]] = reactive({})

def __init__(self, **kwargs):
super().__init__(**kwargs)
Expand Down
3 changes: 0 additions & 3 deletions tests/tui/test_app.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
"""Tests for DeepCodeAgentApp."""

import pytest
from textual.app import App


def test_app_auto_approve_tools():
"""Test that app has auto_approve_tools configuration."""
Expand Down
2 changes: 0 additions & 2 deletions tests/tui/test_tool_call_view.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
"""Tests for ToolCallView widget."""

import pytest
from textual.app import App


Expand Down
Loading
Loading