Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
18b17c1
added two html based custom elements
BrunoV21 Aug 30, 2025
49c70ed
updated GET_CODE_IDENTIFIERS_SYSTEM_PROMPT
BrunoV21 Aug 30, 2025
aa84284
refactored parse_commit_blocks to more general parse_blocks
BrunoV21 Aug 30, 2025
ffbb953
updated agent to integrate GET_CODE_IDENTIFIERS_SYSTEM_PROMPT change…
BrunoV21 Aug 30, 2025
f0979ab
updarted chainlit app to display custom element with context ideneifi…
BrunoV21 Aug 30, 2025
220f2fe
added loading animation
BrunoV21 Aug 30, 2025
ac99478
updated reasoner block
BrunoV21 Aug 31, 2025
057960c
updated laoding msg custom element
BrunoV21 Aug 31, 2025
77b022f
added _as_file_paths to ensure full laoding for commits
BrunoV21 Aug 31, 2025
c12c3ca
integrated _as_file_paths into codeIdentifiers
BrunoV21 Aug 31, 2025
fff6ea0
added cached_elements property and expanded type annotation
BrunoV21 Aug 31, 2025
f4c650b
updated steps prompt to get context and modify identifiers
BrunoV21 Aug 31, 2025
2e99003
updated Step models
BrunoV21 Aug 31, 2025
a8589fd
reversed laodingMessage
BrunoV21 Aug 31, 2025
ffb43c1
refactored patch_cide_blocks into patch tool to support process-patch…
BrunoV21 Aug 31, 2025
3298ffd
fixed to allow new files to pass through
BrunoV21 Aug 31, 2025
cfa5c0d
minor improvements promtps
BrunoV21 Aug 31, 2025
67c8dee
improved loading message / reasoning message robustness
BrunoV21 Aug 31, 2025
7ac1c38
included added path in all_paths_needed for process_patch changed fil…
BrunoV21 Aug 31, 2025
3c1acdb
update CMD_COMMIT_PROMPT
BrunoV21 Aug 31, 2025
45fe168
updated hf example app to latest spec
BrunoV21 Aug 31, 2025
eb18520
added None protection
BrunoV21 Aug 31, 2025
a577657
updated protection
BrunoV21 Aug 31, 2025
4f01657
improved parse_patch blocks robustness
BrunoV21 Aug 31, 2025
6754ae5
fixed planning prompts
BrunoV21 Aug 31, 2025
2d295eb
adde dprotection
BrunoV21 Aug 31, 2025
a6fda24
improved_parse_steps_markdown
BrunoV21 Aug 31, 2025
3cabf96
updated / fadded tests
BrunoV21 Aug 31, 2025
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
17 changes: 16 additions & 1 deletion codetide/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -577,4 +577,19 @@ def get(
as_string=as_string,
as_list_str=as_string_list,
preloaded_files=requested_files
)
)

def _as_file_paths(self, code_identifiers: Union[str, List[str]])->List[str]:
if isinstance(code_identifiers, str):
code_identifiers = [code_identifiers]

as_file_paths = []
for code_identifier in code_identifiers:
if self.rootpath / code_identifier in self.files:
as_file_paths.append(code_identifier)
elif element := self.codebase.cached_elements.get(code_identifier):
as_file_paths.append(element.file_path)
else: ### covers new files
as_file_paths.append(element)

return as_file_paths
31 changes: 25 additions & 6 deletions codetide/agents/tide/agent.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
from functools import partial
from codetide import CodeTide
from ...mcp.tools.patch_code import file_exists, open_file, process_patch, remove_file, write_file
from ...mcp.tools.patch_code import file_exists, open_file, process_patch, remove_file, write_file, parse_patch_blocks
from ...core.defaults import DEFAULT_ENCODING, DEFAULT_STORAGE_PATH
from ...autocomplete import AutoComplete
from .models import Steps
from .prompts import (
AGENT_TIDE_SYSTEM_PROMPT, GET_CODE_IDENTIFIERS_SYSTEM_PROMPT, REJECT_PATCH_FEEDBACK_TEMPLATE,
STAGED_DIFFS_TEMPLATE, STEPS_SYSTEM_PROMPT, WRITE_PATCH_SYSTEM_PROMPT
)
from .utils import delete_file, parse_commit_blocks, parse_patch_blocks, parse_steps_markdown, trim_to_patch_section
from .utils import delete_file, parse_blocks, parse_steps_markdown, trim_to_patch_section
from .consts import AGENT_TIDE_ASCII_ART

try:
Expand Down Expand Up @@ -49,6 +49,10 @@ class AgentTide(BaseModel):
changed_paths :List[str]=Field(default_factory=list)
request_human_confirmation :bool=False

contextIdentifiers :Optional[List[str]]=None
modifyIdentifiers :Optional[List[str]]=None
reasoning :Optional[str]=None

_skip_context_retrieval :bool=False
_last_code_identifers :Optional[Set[str]]=set()
_last_code_context :Optional[str] = None
Expand Down Expand Up @@ -104,14 +108,29 @@ async def agent_loop(self, codeIdentifiers :Optional[List[str]]=None):
)

if codeIdentifiers is None and not self._skip_context_retrieval:
codeIdentifiers = await self.llm.acomplete(
context_response = await self.llm.acomplete(
self.history,
system_prompt=[GET_CODE_IDENTIFIERS_SYSTEM_PROMPT.format(DATE=TODAY)],
prefix_prompt=repo_tree,
stream=False,
json_output=True
stream=False
# json_output=True
)

contextIdentifiers = parse_blocks(context_response, block_word="Context Identifiers", multiple=False)
modifyIdentifiers = parse_blocks(context_response, block_word="Modify Identifiers", multiple=False)

reasoning = context_response.split("*** Begin")
if not reasoning:
reasoning = [context_response]
self.reasoning = reasoning[0].strip()

self.contextIdentifiers = contextIdentifiers.splitlines() if isinstance(contextIdentifiers, str) else None
self.modifyIdentifiers = modifyIdentifiers.splitlines() if isinstance(modifyIdentifiers, str) else None
codeIdentifiers = self.contextIdentifiers or []

if self.modifyIdentifiers:
codeIdentifiers.extend(self.tide._as_file_paths(self.modifyIdentifiers))

codeContext = None
if codeIdentifiers:
autocomplete = AutoComplete(self.tide.cached_ids)
Expand Down Expand Up @@ -144,7 +163,7 @@ async def agent_loop(self, codeIdentifiers :Optional[List[str]]=None):
if not self.request_human_confirmation:
self.approve()

commitMessage = parse_commit_blocks(response, multiple=False)
commitMessage = parse_blocks(response, multiple=False, block_word="Commit")
if commitMessage:
self.commit(commitMessage)

Expand Down
12 changes: 10 additions & 2 deletions codetide/agents/tide/models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@

from typing import Callable, Dict, List, Optional
from pydantic import BaseModel, RootModel
from typing import Dict, List, Optional

STEP_INSTRUCTION_TEMPLATE = """
## Step {step}:
Expand All @@ -23,6 +22,7 @@ class Step(BaseModel):
description :str
instructions :str
context_identifiers :Optional[List[str]]=None
modify_identifiers: Optional[List[str]]=None

def as_instruction(self)->str:
return STEP_INSTRUCTION_TEMPLATE.format(
Expand All @@ -31,6 +31,14 @@ def as_instruction(self)->str:
instructions=self.instructions
)

def get_code_identifiers(self, validate_identifiers_fn :Callable)->Optional[List[str]]:
code_identifiers = self.context_identifiers or []

if self.modify_identifiers:
code_identifiers.extend(validate_identifiers_fn(code_identifiers))

return None or code_identifiers

class Steps(RootModel):
root :List[Step]

Expand Down
67 changes: 38 additions & 29 deletions codetide/agents/tide/prompts.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,34 +61,45 @@
**Instructions:**

1. Carefully read and interpret the user's request, identifying any references to files, modules, submodules, or code elements—either explicit or implied.
2. **Prioritize returning fully qualified code identifiers** (such as functions, classes, methods, variables, or attributes) that are directly related to the user's request or are elements of interest. The identifier format must use dot notation to represent the path-like structure, e.g., `module.submodule.Class.method` or `module.function`, without file extensions.
3. Only include full file paths (relative to the repository root) if:
2. **Segregate identifiers into two categories:**
- **Context Identifiers:** Code elements (functions, classes, methods, variables, attributes, or file paths) that are required to understand, reference, or provide context for the requested change, but are not themselves expected to be modified.
- **Modify Identifiers:** Code elements (functions, classes, methods, variables, attributes, or file paths) that are likely to require direct modification to fulfill the user's request.
3. **Prioritize returning fully qualified code identifiers** (using dot notation, e.g., `module.submodule.Class.method`), without file extensions. Only include file paths (relative to the repository root) if:
- The user explicitly requests file-level operations (such as adding, deleting, or renaming files), or
- No valid or relevant code identifiers can be determined for the request.
4. If the user refers to a file by name or path and the request is about code elements within that file, extract and include the relevant code identifiers from that file instead of the file path, unless the user specifically asks for the file path.
5. If fulfilling the request would likely depend on additional symbols or files—based on naming, structure, required context from other files/modules, or conventional design patterns—include those code identifiers as well.
5. If fulfilling the request would likely depend on additional symbols or files—based on naming, structure, required context from other files/modules, or conventional design patterns—include those code identifiers as context identifiers.
6. Only include identifiers or paths that are present in the provided tree structure. Never fabricate or guess paths or names that do not exist.
7. If no relevant code identifiers or file paths can be confidently identified, return an empty list.
7. If no relevant code identifiers or file paths can be confidently identified, leave the relevant section(s) empty - without any contents or lines, not even the word empty.

---

**Output Format (Strict JSON Only):**
**Output Format:**

Return a JSON array of strings. Each string must be:
- A fully qualified code identifier using dot notation (e.g., `module.submodule.Class.method`), without file extensions, or
- A valid file path relative to the repository root (only if explicitly required or no code identifiers are available).
Your response must include:

Your output must be a pure JSON list of strings. Do **not** include any explanation, comments, or formatting outside the JSON block.
1. A brief explanation (1-3 sentences) describing your reasoning and search process for selecting the identifiers.
2. The following delimited sections, each containing a newline-separated list of identifiers (or left empty if none):

*** Begin Context Identifiers
<one per line, or empty>
*** End Context Identifiers

*** Begin Modify Identifiers
<one per line, or empty>
*** End Modify Identifiers

Do **not** include any additional commentary, formatting, or output outside these sections.

---

**Evaluation Criteria:**

- You must identify all code identifiers directly referenced or implied in the user request, prioritizing them over file paths.
- You must identify all code identifiers directly referenced or implied in the user request, and correctly categorize them as context or modify identifiers.
- You must include any internal code elements that are clearly involved or required for the task.
- You must consider logical dependencies that may need to be modified together (e.g., helper modules, config files, related class methods).
- You must consider files that can be relevant as context to complete the user request, but only include their paths if code identifiers are not available or explicitly requested.
- You must return a clean and complete list of all relevant code identifiers and, if necessary, file paths.
- You must return a clean and complete list of all relevant code identifiers and, if necessary, file paths, in the correct section.
- Do not over-include; be minimal but thorough. Return only what is truly required.

"""
Expand Down Expand Up @@ -288,12 +299,16 @@
1. **step_description**
**instructions**: precise instructions of the task to be implemented in this step
**context_identifiers**:
- fully qualified code identifiers or file paths (as taken from the repo_tree) that this step touches, depends on, or must update
- fully qualified code identifiers or file paths (as taken from the repo_tree) that this step depends on for context (read/reference only)
**modify_identifiers**:
- fully qualified code identifiers or file paths (as taken from the repo_tree) that this step will directly modify or update
---
2. **next_step_description**
**instructions**: ...
**context_identifiers**:
- ...
**modify_identifiers**:
- ...
---
...
*** End Steps
Expand All @@ -308,22 +323,17 @@

4. **Granularity:** Break complex requirements into logical sub-steps. Order them so dependencies are respected (e.g., setup → implementation → validation → integration).

5. **Traceability:** Each steps `context_identifiers` must clearly tie that step to specific code areas; this enables downstream mapping to actual implementation targets.
5. **Traceability:** Each step's `context_identifiers` and `modify_identifiers` must clearly tie that step to specific code areas; this enables downstream mapping to actual implementation targets.

6. **Single-Responsibility per Step:** Aim for each numbered step to encapsulate a coherent unit of work. Avoid mixing unrelated concerns in one step.

7. **Decision Points:** If a step involves a choice or alternative, surface the options in the instructions and, if necessary, flag which you assume unless the user directs otherwise.

8. **Testing & Validation:** Where appropriate, include in steps the need for testing, how to validate success, and any edge cases to cover.

9. **Failure Modes & Corrections:** If the user’s request implies potential pitfalls (e.g., backward compatibility, race conditions, security), surface those in early steps or in the comments and include remediation as part of the plan.
9. **Failure Modes & Corrections:** If the use's request implies potential pitfalls (e.g., backward compatibility, race conditions, security), surface those in early steps or in the comments and include remediation as part of the plan.

10. **Succinctness of Format:** Strictly adhere to the step formatting with separators (`---`) and the beginning/end markers. Do not add extraneous numbering or narrative outside the prescribed structure.

---

`repo_tree`
{REPO_TREE}
"""

CMD_TRIGGER_PLANNING_STEPS = """
Expand All @@ -343,18 +353,17 @@

CMD_COMMIT_PROMPT = """
Generate a conventional commit message that summarizes the work done since the previous commit.
The message should have a clear subject line and a body explaining the problem solved and the implementation approach.

Important Instructions:

Place the commit message inside exactly this format:
*** Begin Commit
[commit message]
*** End Commit

You may include additional comments about the changes made outside of this block
**Instructions:**

If no diffs for staged files are provided in the context, reply that there's nothing to commit
1. First, write a body (before the commit block) that explains the problem solved and the implementation approach. This should be clear, concise, and provide context for the change.
2. Then, place the commit subject line (only) inside the commit block, using this format:
*** Begin Commit
[subject line only, up to 3 lines, straight to the point and descriptive of the broad changes]
*** End Commit
3. The subject line should follow the conventional commit format with a clear type/scope prefix, and summarize the broad changes made. Do not include the body or any explanation inside the commit block—only the subject line.
4. You may include additional comments about the changes made outside of this block, if needed.
5. If no diffs for staged files are provided in the context, reply that there's nothing to commit.context, reply that there's nothing to commit

The commit message should follow conventional commit format with a clear type/scope prefix
"""
Expand Down
52 changes: 49 additions & 3 deletions codetide/agents/tide/ui/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import asyncio
import json
import yaml
import time

@cl.password_auth_callback
def auth():
Expand Down Expand Up @@ -172,6 +173,7 @@ async def on_execute_steps(action :cl.Action):
latest_step_message :cl.Message = cl.user_session.get("latest_step_message")
if latest_step_message and latest_step_message.id == action.payload.get("msg_id"):
await latest_step_message.remove_actions()
await latest_step_message.send() # close message ?

if agent_tide_ui.current_step is None:
task_list = cl.TaskList("Steps")
Expand Down Expand Up @@ -213,7 +215,7 @@ async def on_execute_steps(action :cl.Action):
author="Agent Tide"
).send()

await agent_loop(step_instructions_msg, codeIdentifiers=step.context_identifiers, agent_tide_ui=agent_tide_ui)
await agent_loop(step_instructions_msg, codeIdentifiers=step.get_code_identifiers(agent_tide_ui.agent_tide.tide._as_file_paths), agent_tide_ui=agent_tide_ui)

task_list.status = f"Waiting feedback on step {current_task_idx}"
await task_list.send()
Expand All @@ -225,6 +227,7 @@ async def on_stop_steps(action :cl.Action):
latest_step_message :cl.Message = cl.user_session.get("latest_step_message")
if latest_step_message and latest_step_message.id == action.payload.get("msg_id"):
await latest_step_message.remove_actions()
await latest_step_message.send() # close message ?

task_list = cl.user_session.get("StepsTaskList")
if task_list:
Expand Down Expand Up @@ -262,9 +265,45 @@ async def on_inspect_context(action :cl.Action):

await inspect_msg.send()

async def send_reasoning_msg(loading_msg :cl.message, context_msg :cl.Message, agent_tide_ui :AgentTideUi, st :float)->bool:
await loading_msg.remove()

context_data = {
key: value for key in ["contextIdentifiers", "modifyIdentifiers"]
if (value := getattr(agent_tide_ui.agent_tide, key, None))
}
context_msg.elements.append(
cl.CustomElement(
name="ReasoningMessage",
props={
"reasoning": agent_tide_ui.agent_tide.reasoning,
"data": context_data,
"title": f"Thought for {time.time()-st:.2f} seconds",
"defaultExpanded": False,
"showControls": False
}
)
)
await context_msg.send()
return True

@cl.on_message
async def agent_loop(message: Optional[cl.Message]=None, codeIdentifiers: Optional[list] = None, agent_tide_ui :Optional[AgentTideUi]=None):

loading_msg = await cl.Message(
content="",
elements=[
cl.CustomElement(
name="LoadingMessage",
props={
"messages": ["Working", "Syncing CodeTide", "Thinking", "Looking for context"],
"interval": 1500, # 1.5 seconds between messages
"showIcon": True
}
)
]
).send()

if agent_tide_ui is None:
agent_tide_ui = await loadAgentTideUi()

Expand All @@ -278,7 +317,8 @@ async def agent_loop(message: Optional[cl.Message]=None, codeIdentifiers: Option

chat_history.append({"role": "user", "content": message.content})
await agent_tide_ui.add_to_history(message.content)


context_msg = cl.Message(content="", author="AgentTide")
msg = cl.Message(content="", author="Agent Tide")
async with cl.Step("ApplyPatch", type="tool") as diff_step:
await diff_step.remove()
Expand Down Expand Up @@ -311,11 +351,17 @@ async def agent_loop(message: Optional[cl.Message]=None, codeIdentifiers: Option
global_fallback_msg=msg
)

st = time.time()
is_reasonig_sent = False
async for chunk in run_concurrent_tasks(agent_tide_ui, codeIdentifiers):
if chunk == STREAM_START_TOKEN:
is_reasonig_sent = await send_reasoning_msg(loading_msg, context_msg, agent_tide_ui, st)
continue

if chunk == STREAM_END_TOKEN:
elif not is_reasonig_sent:
is_reasonig_sent = await send_reasoning_msg(loading_msg, context_msg, agent_tide_ui, st)

elif chunk == STREAM_END_TOKEN:
# Handle any remaining content
await stream_processor.finalize()
break
Expand Down
Loading