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
14 changes: 7 additions & 7 deletions codetide/agents/tide/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def pass_custom_logger_fn(self)->Self:
def approve(self):
self._has_patch = False
if os.path.exists(self.patch_path):
changed_paths = process_patch(self.patch_path, open_file, write_file, remove_file, file_exists)
changed_paths = process_patch(self.patch_path, open_file, write_file, remove_file, file_exists, root_path=self.tide.rootpath)
self.changed_paths.extend(changed_paths)

previous_response = self.history[-1]
Expand All @@ -81,10 +81,10 @@ def reject(self, feedback :str):

@property
def patch_path(self)->Path:
if not os.path.exists(DEFAULT_STORAGE_PATH):
os.makedirs(DEFAULT_STORAGE_PATH, exist_ok=True)
if not os.path.exists(self.tide.rootpath / DEFAULT_STORAGE_PATH):
os.makedirs(self.tide.rootpath / DEFAULT_STORAGE_PATH, exist_ok=True)

return DEFAULT_STORAGE_PATH / f"{self.session_id}.bash"
return self.tide.rootpath / DEFAULT_STORAGE_PATH / f"{self.session_id}.bash"

@staticmethod
def trim_messages(messages, tokenizer_fn, max_tokens :Optional[int]=None):
Expand Down Expand Up @@ -220,12 +220,12 @@ def commit(self, message :str):
"""
try:
# Open the repository
repo = self.repo
repo = self.tide.repo

# Get author and committer information
config = repo.config
author_name = config.get('user.name', 'Unknown Author')
author_email = config.get('user.email', 'unknown@example.com')
author_name = config._get('user.name')[1].value or 'Unknown Author'
author_email = config._get('user.email')[1].value or 'unknown@example.com'

author = pygit2.Signature(author_name, author_email)
committer = author # Typically same as author
Expand Down
3 changes: 2 additions & 1 deletion codetide/agents/tide/prompts.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,8 @@
"""

CMD_TRIGGER_PLANNING_STEPS = """

You must operate in a multi-step planning and execution mode: first outline the plan step by step in a sequential way, then ask for my revision.
Do not start implementing the steps without my approval.
"""

CMD_WRITE_TESTS_PROMPT = """
Expand Down
2 changes: 2 additions & 0 deletions codetide/core/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@

BREAKLINE = "\n"

DEFAULT_BYTES_CONTENT_PLACEHOLDERS = "< suppressed bytes content >"

CODETIDE_ASCII_ART = """

███████╗ ██████╗ ██████╗ ███████╗████████╗██╗██████╗ ███████╗
Expand Down
12 changes: 10 additions & 2 deletions codetide/mcp/tools/patch_code/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from .parser import Parser, patch_to_commit
# from ....core.common import writeFile

from typing import Dict, Tuple, List, Callable
from typing import Dict, Optional, Tuple, List, Callable, Union
import pathlib
import os

Expand Down Expand Up @@ -108,18 +108,24 @@ def process_patch(
open_fn: Callable[[str], str],
write_fn: Callable[[str, str], None],
remove_fn: Callable[[str], None],
exists_fn: Callable[[str], bool]
exists_fn: Callable[[str], bool],
root_path: Optional[Union[str, pathlib.Path]]=None
) -> List[str]:
"""The main entrypoint function to process a patch from text to filesystem."""
if not os.path.exists(patch_path):
raise DiffError("Patch path {patch_path} does not exist.")

if root_path is not None:
root_path = pathlib.Path(root_path)

# Normalize line endings before processing
text = open_fn(patch_path)

# FIX: Check for existence of files to be added before parsing.
paths_to_add = identify_files_added(text)
for p in paths_to_add:
if root_path is not None:
p = str(root_path / p)
if exists_fn(p):
raise DiffError(f"Add File Error - file already exists: {p}")

Expand All @@ -128,6 +134,8 @@ def process_patch(
# Load files with normalized line endings
orig_files = {}
for path in paths_needed:
if root_path is not None:
path = str(root_path / path)
orig_files[path] = open_fn(path)

patch, _fuzz = text_to_patch(text, orig_files)
Expand Down
5 changes: 3 additions & 2 deletions codetide/parsers/generic_parser.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from ..core.models import CodeBase, CodeFileModel, ImportStatement
from ..core.defaults import DEFAULT_BYTES_CONTENT_PLACEHOLDERS
from ..parsers.base_parser import BaseParser
from ..core.common import readFile

Expand Down Expand Up @@ -54,10 +55,10 @@ async def parse_file(self, file_path: Union[str, Path], root_path: Optional[Unio

return codeFile

def parse_code(self, file_path :Path, code :str):
def parse_code(self, file_path :Path, code :Optional[str]=None):
codeFile = CodeFileModel(
file_path=str(file_path),
raw=code
raw=code if not isinstance(code, bytes) else DEFAULT_BYTES_CONTENT_PLACEHOLDERS
)
return codeFile

Expand Down
10 changes: 5 additions & 5 deletions examples/hf_demo_space/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from aicore.llm import Llm, LlmConfig
from aicore.config import Config

from git_utils import push_new_branch, validate_git_url, checkout_new_branch
from git_utils import commit_and_push_changes, validate_git_url, checkout_new_branch
from chainlit.cli import run_chainlit
from typing import Optional
from pathlib import Path
Expand Down Expand Up @@ -153,6 +153,7 @@ async def start_chatr():

agent_tide_ui.agent_tide.llm = Llm.from_config(config.llm)
agent_tide_ui.agent_tide.llm.provider.session_id = agent_tide_ui.agent_tide.session_id
agent_tide_ui.agent_tide.pass_custom_logger_fn()

session_dir_path = DEFAULT_SESSIONS_WORKSPACE / session_id
if not os.path.exists(session_dir_path):
Expand Down Expand Up @@ -260,10 +261,9 @@ async def on_stop_steps(action :cl.Action):
@cl.action_callback("checkout_commit_push")
async def on_checkout_commit_push(action :cl.Action):
agent_tide_ui: AgentTideUi = cl.user_session.get("AgentTideUi")
await agent_tide_ui.agent_tide.prepare_commit()
agent_tide_ui.agent_tide.commit("AgentTide - add all and push")

push_new_branch(agent_tide_ui.agent_tide.tide.repo, branch_name=cl.user_session.get("current_branch_name"))
# await agent_tide_ui.agent_tide.prepare_commit()
# agent_tide_ui.agent_tide.commit("AgentTide - add all and push")
await commit_and_push_changes(agent_tide_ui.agent_tide.tide.rootpath, branch_name=cl.user_session.get("current_branch_name"), commit_message="AgentTide - add all and push", checkout=False)

@cl.action_callback("inspect_code_context")
async def on_inspect_context(action :cl.Action):
Expand Down
39 changes: 18 additions & 21 deletions examples/hf_demo_space/git_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ async def validate_git_url(url) -> None:
except subprocess.CalledProcessError as e:
raise ValueError(f"Invalid Git repository URL: {url}. Error: {e.stderr}") from e

async def commit_and_push_changes(repo_path: Path, branch_name: str = None, commit_message: str = "Auto-commit: Save changes") -> None:
async def commit_and_push_changes(repo_path: Path, branch_name: str = None, commit_message: str = "Auto-commit: Save changes", checkout :bool=True) -> None:
"""Add all changes, commit with default message, and push to remote."""

repo_path_str = str(repo_path)
Expand All @@ -51,27 +51,26 @@ async def commit_and_push_changes(repo_path: Path, branch_name: str = None, comm
if not branch_name:
branch_name = f"agent-tide-{ulid()}"

# Create and checkout new branch
process = await asyncio.create_subprocess_exec(
"git", "checkout", "-b", branch_name,
cwd=repo_path_str,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
text=True
)

stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=10)

if process.returncode != 0:
raise subprocess.CalledProcessError(process.returncode, ["git", "checkout", "-b", branch_name], stdout, stderr)

if checkout:
# Create and checkout new branch
process = await asyncio.create_subprocess_exec(
"git", "checkout", "-b", branch_name,
cwd=repo_path_str,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=10)
if process.returncode != 0:
raise subprocess.CalledProcessError(process.returncode, ["git", "checkout", "-b", branch_name], stdout, stderr)
# Add all changes
process = await asyncio.create_subprocess_exec(
"git", "add", ".",
cwd=repo_path_str,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
text=True
stderr=asyncio.subprocess.PIPE
)

stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=30)
Expand All @@ -84,8 +83,7 @@ async def commit_and_push_changes(repo_path: Path, branch_name: str = None, comm
"git", "commit", "-m", commit_message,
cwd=repo_path_str,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
text=True
stderr=asyncio.subprocess.PIPE
)

stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=30)
Expand All @@ -101,8 +99,7 @@ async def commit_and_push_changes(repo_path: Path, branch_name: str = None, comm
"git", "push", "origin", branch_name,
cwd=repo_path_str,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
text=True
stderr=asyncio.subprocess.PIPE
)

stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=60)
Expand Down