Skip to content

Commit 7262a32

Browse files
committed
feat: add prerelease tools
1 parent d2ffeca commit 7262a32

File tree

13 files changed

+968
-0
lines changed

13 files changed

+968
-0
lines changed

.DS_Store

6 KB
Binary file not shown.

examples/.DS_Store

6 KB
Binary file not shown.

examples/code/coding_agent.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import os
2+
import tempfile
3+
from typing import List, Dict, Any, Optional, Union, Literal
4+
from pathlib import Path
5+
6+
from deepagents import create_deep_agent, SubAgent
7+
from post_model_hook import create_coding_agent_post_model_hook
8+
from subagents import code_reviewer_agent, test_generator_agent
9+
from langgraph.types import Command
10+
from state import CodingAgentState
11+
from coding_instructions import get_coding_instructions
12+
from tools import execute_bash, http_request, web_search
13+
14+
from langsmith import Client
15+
from langsmith.wrappers import wrap_openai
16+
from langchain_core.tracers.langchain import LangChainTracer
17+
import dotenv
18+
19+
dotenv.load_dotenv()
20+
21+
langsmith_client = None
22+
langchain_tracer = None
23+
24+
if os.environ.get("LANGCHAIN_API_KEY"):
25+
try:
26+
langsmith_client = Client()
27+
langchain_tracer = LangChainTracer(
28+
project_name=os.environ.get("LANGCHAIN_PROJECT", "coding-agent"),
29+
client=langsmith_client
30+
)
31+
except Exception as e:
32+
print(f"Warning: Failed to initialize LangSmith tracing: {e}")
33+
34+
35+
coding_instructions = get_coding_instructions()
36+
37+
post_model_hook = create_coding_agent_post_model_hook()
38+
39+
config = {"recursion_limit": 1000}
40+
41+
if langchain_tracer:
42+
config["callbacks"] = [langchain_tracer]
43+
44+
agent = create_deep_agent(
45+
[execute_bash, http_request, web_search],
46+
coding_instructions,
47+
subagents=[code_reviewer_agent, test_generator_agent],
48+
local_filesystem=True,
49+
state_schema=CodingAgentState,
50+
post_model_hook=post_model_hook,
51+
).with_config(config)

examples/code/coding_instructions.py

Lines changed: 380 additions & 0 deletions
Large diffs are not rendered by default.

examples/code/constants.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
"""
2+
Constants for Open SWE V2 coding agent.
3+
"""
4+
5+
# File operation commands that require approval in the approval system
6+
FILE_EDIT_COMMANDS = {"write_file", "str_replace_based_edit_tool", "edit_file"}
7+
8+
# All commands that require approval (includes file operations plus other system operations)
9+
WRITE_COMMANDS = {
10+
"write_file",
11+
"execute_bash",
12+
"str_replace_based_edit_tool",
13+
"ls",
14+
"edit_file",
15+
"glob",
16+
"grep"
17+
}

examples/code/langgraph.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"dependencies": ["."],
3+
"graphs": {
4+
"coding": "./coding_agent.py:agent"
5+
},
6+
"env": ".env"
7+
}

examples/code/post_model_hook.py

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
"""
2+
Post model hook for the coding agent with intelligent caching for command approvals.
3+
Similar to Claude Code's approval system - only prompts for new commands or new directories.
4+
"""
5+
6+
from typing import Dict, Any
7+
from langchain_core.messages import AIMessage
8+
from langgraph.types import interrupt
9+
import os
10+
11+
12+
def create_coding_agent_post_model_hook():
13+
"""Create a post model hook with intelligent approval caching."""
14+
15+
def get_approval_key(command: str, args: Dict[str, Any]) -> str:
16+
"""Generate a cache key for command approval based on command type and target directory."""
17+
# Extract directory from common file operations
18+
target_dir = None
19+
20+
if command in ["write_file", "str_replace_based_edit_tool", "edit_file"]:
21+
file_path = args.get("file_path") or args.get("path")
22+
if file_path:
23+
target_dir = os.path.dirname(os.path.abspath(file_path))
24+
elif command == "execute_bash":
25+
# For bash commands, use current working directory or cwd from args
26+
target_dir = args.get("cwd") or os.getcwd()
27+
elif command in ["ls", "glob", "grep"]:
28+
# For read operations, use the path if provided
29+
target_dir = args.get("path") or args.get("directory") or os.getcwd()
30+
31+
if not target_dir:
32+
target_dir = os.getcwd()
33+
34+
# Create a cache key: command_type:normalized_directory
35+
normalized_dir = os.path.normpath(target_dir)
36+
return f"{command}:{normalized_dir}"
37+
38+
def is_operation_approved(approved_operations: Dict[str, Any], command: str, args: Dict[str, Any]) -> bool:
39+
"""Check if a command/directory combination has been previously approved."""
40+
if not approved_operations:
41+
return False
42+
43+
approval_key = get_approval_key(command, args)
44+
return approval_key in approved_operations.get("cached_approvals", set())
45+
46+
def add_approved_operation(approved_operations: Dict[str, Any], command: str, args: Dict[str, Any]):
47+
"""Add a command/directory combination to the approved operations cache."""
48+
if "cached_approvals" not in approved_operations:
49+
approved_operations["cached_approvals"] = set()
50+
51+
approval_key = get_approval_key(command, args)
52+
approved_operations["cached_approvals"].add(approval_key)
53+
54+
def post_model_hook(state: Dict[str, Any]) -> Dict[str, Any]:
55+
"""
56+
Post model hook that checks for write tool calls and uses caching to avoid
57+
redundant approval prompts for the same command/directory combinations.
58+
"""
59+
# Get the last message from the state
60+
messages = state.get("messages", [])
61+
if not messages:
62+
return state
63+
64+
last_message = messages[-1]
65+
66+
if not isinstance(last_message, AIMessage) or not last_message.tool_calls:
67+
return state
68+
69+
# Get or initialize the approval cache from state
70+
approved_operations = state.get("approved_operations", {"cached_approvals": set()})
71+
72+
approved_tool_calls = []
73+
74+
# Define write tools that need approval
75+
write_tools = {
76+
"write_file", "execute_bash", "str_replace_based_edit_tool", "ls", "edit_file", "glob", "grep"
77+
}
78+
79+
for tool_call in last_message.tool_calls:
80+
tool_name = tool_call.get("name", "")
81+
tool_args = tool_call.get("args", {})
82+
83+
if tool_name in write_tools:
84+
# Check if this command/directory combination has been approved before
85+
if is_operation_approved(approved_operations, tool_name, tool_args):
86+
# Already approved - execute without prompting
87+
approved_tool_calls.append(tool_call)
88+
else:
89+
# New command or directory - ask for approval
90+
approval_key = get_approval_key(tool_name, tool_args)
91+
question = (
92+
f"New operation detected:\n\n"
93+
f"Command: {tool_name}\n"
94+
f"Directory: {approval_key.split(':', 1)[1]}\n"
95+
f"Args: {tool_args}\n\n"
96+
f"Approve this command for this directory? "
97+
f"(Future identical operations in this directory will be auto-approved)\n\n"
98+
f"Respond with True to approve or False to reject."
99+
)
100+
101+
is_approved = interrupt({
102+
"question": question,
103+
"command": tool_name,
104+
"args": tool_args,
105+
"approval_key": approval_key
106+
})
107+
108+
if is_approved:
109+
# Add to cache and approve the tool call
110+
add_approved_operation(approved_operations, tool_name, tool_args)
111+
approved_tool_calls.append(tool_call)
112+
113+
# Update the main state with the new approval cache
114+
state["approved_operations"] = approved_operations
115+
else:
116+
# Rejected - skip this tool call
117+
continue
118+
else:
119+
# For all other tools (read-only operations), allow them to execute without approval
120+
approved_tool_calls.append(tool_call)
121+
122+
# Update the message if any tool calls were filtered out
123+
if len(approved_tool_calls) != len(last_message.tool_calls):
124+
new_message = AIMessage(
125+
content=last_message.content,
126+
tool_calls=approved_tool_calls,
127+
additional_kwargs=last_message.additional_kwargs
128+
)
129+
130+
new_messages = messages[:-1] + [new_message]
131+
state["messages"] = new_messages
132+
133+
return state
134+
135+
return post_model_hook

examples/code/requirements.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Core requirements
2+
deepagents
3+
langgraph-cli[inmem]
4+
tavily-python
5+
langchain-anthropic
6+
pydantic

examples/code/state.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
from deepagents.state import DeepAgentState
2+
from typing import Dict, Set, NotRequired
3+
from typing_extensions import TypedDict
4+
import os
5+
import hashlib
6+
from constants import FILE_EDIT_COMMANDS
7+
8+
class CodingAgentState(DeepAgentState):
9+
"""Extended state for coding agent with approval operation caching."""
10+
approved_operations: NotRequired[Dict[str, Set[str]]]
11+
12+
def get_approval_key(self, command: str, args: Dict) -> str:
13+
"""Generate a cache key for command approval based on command type and target directory."""
14+
target_dir = None
15+
16+
if command in FILE_EDIT_COMMANDS:
17+
file_path = args.get("file_path") or args.get("path")
18+
if file_path:
19+
target_dir = os.path.dirname(os.path.abspath(file_path))
20+
elif command == "execute_bash":
21+
target_dir = args.get("cwd") or os.getcwd()
22+
elif command in ["ls", "glob", "grep"]:
23+
target_dir = args.get("path") or args.get("directory") or os.getcwd()
24+
25+
if not target_dir:
26+
target_dir = os.getcwd()
27+
28+
# Create a cache key: command_type:normalized_directory
29+
normalized_dir = os.path.normpath(target_dir)
30+
return f"{command}:{normalized_dir}"
31+
32+
def is_operation_approved(self, command: str, args: Dict) -> bool:
33+
"""Check if a command/directory combination has been previously approved."""
34+
if not hasattr(self, 'approved_operations') or not self.approved_operations:
35+
return False
36+
37+
approval_key = self.get_approval_key(command, args)
38+
return approval_key in self.approved_operations.get("cached_approvals", set())
39+
40+
def add_approved_operation(self, command: str, args: Dict):
41+
"""Add a command/directory combination to the approved operations cache."""
42+
if not hasattr(self, 'approved_operations') or self.approved_operations is None:
43+
self.approved_operations = {"cached_approvals": set()}
44+
45+
if "cached_approvals" not in self.approved_operations:
46+
self.approved_operations["cached_approvals"] = set()
47+
48+
approval_key = self.get_approval_key(command, args)
49+
self.approved_operations["cached_approvals"].add(approval_key)

examples/code/subagents.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Sub-agent for code review and analysis
2+
code_reviewer_prompt = """You are an expert code reviewer for all programming languages. Your job is to analyze code for:
3+
4+
1. **Code Quality**: Check for clean, readable, and maintainable code
5+
2. **Best Practices**: Ensure adherence to language-specific best practices and conventions
6+
3. **Security**: Identify potential security vulnerabilities
7+
4. **Performance**: Suggest optimizations where applicable
8+
5. **Testing**: Evaluate test coverage and quality
9+
6. **Documentation**: Check for proper comments and documentation
10+
11+
When reviewing code, provide:
12+
- Specific line-by-line feedback
13+
- Language-specific suggestions for improvements
14+
- Security concerns (if any)
15+
- Performance optimization opportunities
16+
- Overall assessment and rating (1-10)
17+
18+
You can use bash commands to run linters, formatters, and other code analysis tools for any language.
19+
Be constructive and educational in your feedback. Focus on helping improve the code quality."""
20+
21+
code_reviewer_agent = {
22+
"name": "code-reviewer",
23+
"description": "Expert code reviewer that analyzes code in any programming language for quality, security, performance, and best practices. Use this when you need detailed code analysis and improvement suggestions.",
24+
"prompt": code_reviewer_prompt,
25+
"tools": ["execute_bash"],
26+
}
27+
28+
# Sub-agent for test generation
29+
test_generator_prompt = """You are an expert test engineer for all programming languages. Your job is to create comprehensive test suites for any codebase.
30+
31+
When generating tests:
32+
1. **Test Coverage**: Create tests that cover all functions, methods, and edge cases
33+
2. **Test Types**: Include unit tests, integration tests, and edge case tests
34+
3. **Frameworks**: Use appropriate testing frameworks for each language (Jest, pytest, JUnit, Go test, etc.)
35+
4. **Assertions**: Write meaningful assertions that validate expected behavior
36+
5. **Documentation**: Include clear test descriptions and comments
37+
38+
Test categories to consider:
39+
- **Happy Path**: Normal expected inputs and outputs
40+
- **Edge Cases**: Boundary conditions, empty inputs, large inputs
41+
- **Error Cases**: Invalid inputs, exception handling
42+
- **Integration**: How components work together
43+
44+
Use bash commands to run language-specific test frameworks and verify that tests execute successfully.
45+
Always verify that your tests can run successfully and provide meaningful feedback."""
46+
47+
test_generator_agent = {
48+
"name": "test-generator",
49+
"description": "Expert test engineer that creates comprehensive test suites for any programming language. Use when you need to generate thorough test suites for your code.",
50+
"prompt": test_generator_prompt,
51+
"tools": ["execute_bash"],
52+
}

0 commit comments

Comments
 (0)