deepcode-hku 1.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. cli/__init__.py +18 -0
  2. cli/cli_app.py +296 -0
  3. cli/cli_interface.py +744 -0
  4. cli/cli_launcher.py +155 -0
  5. cli/main_cli.py +243 -0
  6. cli/workflows/__init__.py +11 -0
  7. cli/workflows/cli_workflow_adapter.py +336 -0
  8. deepcode.py +219 -0
  9. deepcode_hku-1.0.1.dist-info/METADATA +695 -0
  10. deepcode_hku-1.0.1.dist-info/RECORD +44 -0
  11. deepcode_hku-1.0.1.dist-info/WHEEL +5 -0
  12. deepcode_hku-1.0.1.dist-info/entry_points.txt +2 -0
  13. deepcode_hku-1.0.1.dist-info/licenses/LICENSE +21 -0
  14. deepcode_hku-1.0.1.dist-info/top_level.txt +6 -0
  15. tools/__init__.py +0 -0
  16. tools/code_implementation_server.py +1045 -0
  17. tools/code_indexer.py +1657 -0
  18. tools/code_reference_indexer.py +486 -0
  19. tools/command_executor.py +324 -0
  20. tools/git_command.py +356 -0
  21. tools/pdf_converter.py +640 -0
  22. tools/pdf_downloader.py +1370 -0
  23. tools/pdf_utils.py +52 -0
  24. ui/__init__.py +43 -0
  25. ui/app.py +13 -0
  26. ui/components.py +1450 -0
  27. ui/handlers.py +773 -0
  28. ui/layout.py +106 -0
  29. ui/streamlit_app.py +38 -0
  30. ui/styles.py +2116 -0
  31. utils/__init__.py +17 -0
  32. utils/cli_interface.py +459 -0
  33. utils/dialogue_logger.py +671 -0
  34. utils/file_processor.py +426 -0
  35. utils/simple_llm_logger.py +198 -0
  36. workflows/__init__.py +31 -0
  37. workflows/agent_orchestration_engine.py +1371 -0
  38. workflows/agents/__init__.py +13 -0
  39. workflows/agents/code_implementation_agent.py +1093 -0
  40. workflows/agents/memory_agent_concise.py +923 -0
  41. workflows/agents/memory_agent_concise_index.py +935 -0
  42. workflows/code_implementation_workflow.py +924 -0
  43. workflows/code_implementation_workflow_index.py +931 -0
  44. workflows/codebase_index_workflow.py +726 -0
@@ -0,0 +1,924 @@
1
+ """
2
+ Paper Code Implementation Workflow - MCP-compliant Iterative Development
3
+
4
+ Features:
5
+ 1. File Tree Creation
6
+ 2. Code Implementation - Based on aisi-basic-agent iterative development
7
+
8
+ MCP Architecture:
9
+ - MCP Server: tools/code_implementation_server.py
10
+ - MCP Client: Called through mcp_agent framework
11
+ - Configuration: mcp_agent.config.yaml
12
+ """
13
+
14
+ import json
15
+ import logging
16
+ import os
17
+ import sys
18
+ import time
19
+ import yaml
20
+ from pathlib import Path
21
+ from typing import Dict, Any, Optional, List
22
+
23
+ # MCP Agent imports
24
+ from mcp_agent.agents.agent import Agent
25
+ from mcp_agent.workflows.llm.augmented_llm_anthropic import AnthropicAugmentedLLM
26
+ from mcp_agent.workflows.llm.augmented_llm_openai import OpenAIAugmentedLLM
27
+
28
+ # Local imports
29
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
30
+ from prompts.code_prompts import STRUCTURE_GENERATOR_PROMPT
31
+ from prompts.code_prompts import (
32
+ GENERAL_CODE_IMPLEMENTATION_SYSTEM_PROMPT,
33
+ )
34
+ from workflows.agents import CodeImplementationAgent
35
+ from workflows.agents.memory_agent_concise import ConciseMemoryAgent
36
+ from config.mcp_tool_definitions import get_mcp_tools
37
+ # DialogueLogger removed - no longer needed
38
+
39
+
40
+ def get_preferred_llm_class(config_path: str = "mcp_agent.secrets.yaml"):
41
+ """
42
+ Automatically select the LLM class based on API key availability in configuration.
43
+
44
+ Reads from YAML config file and returns AnthropicAugmentedLLM if anthropic.api_key
45
+ is available, otherwise returns OpenAIAugmentedLLM.
46
+
47
+ Args:
48
+ config_path: Path to the YAML configuration file
49
+
50
+ Returns:
51
+ class: The preferred LLM class
52
+ """
53
+ try:
54
+ # Try to read the configuration file
55
+ if os.path.exists(config_path):
56
+ with open(config_path, "r", encoding="utf-8") as f:
57
+ config = yaml.safe_load(f)
58
+
59
+ # Check for anthropic API key in config
60
+ anthropic_config = config.get("anthropic", {})
61
+ anthropic_key = anthropic_config.get("api_key", "")
62
+
63
+ if anthropic_key and anthropic_key.strip() and not anthropic_key == "":
64
+ # print("🤖 Using AnthropicAugmentedLLM (Anthropic API key found in config)")
65
+ return AnthropicAugmentedLLM
66
+ else:
67
+ # print("🤖 Using OpenAIAugmentedLLM (Anthropic API key not configured)")
68
+ return OpenAIAugmentedLLM
69
+ else:
70
+ print(f"🤖 Config file {config_path} not found, using OpenAIAugmentedLLM")
71
+ return OpenAIAugmentedLLM
72
+
73
+ except Exception as e:
74
+ print(f"🤖 Error reading config file {config_path}: {e}")
75
+ print("🤖 Falling back to OpenAIAugmentedLLM")
76
+ return OpenAIAugmentedLLM
77
+
78
+
79
+ def get_default_models(config_path: str = "mcp_agent.config.yaml"):
80
+ """
81
+ Get default models from configuration file.
82
+
83
+ Args:
84
+ config_path: Path to the configuration file
85
+
86
+ Returns:
87
+ dict: Dictionary with 'anthropic' and 'openai' default models
88
+ """
89
+ try:
90
+ if os.path.exists(config_path):
91
+ with open(config_path, "r", encoding="utf-8") as f:
92
+ config = yaml.safe_load(f)
93
+
94
+ anthropic_model = config.get("anthropic", {}).get(
95
+ "default_model", "claude-sonnet-4-20250514"
96
+ )
97
+ openai_model = config.get("openai", {}).get("default_model", "o3-mini")
98
+
99
+ return {"anthropic": anthropic_model, "openai": openai_model}
100
+ else:
101
+ print(f"Config file {config_path} not found, using default models")
102
+ return {"anthropic": "claude-sonnet-4-20250514", "openai": "o3-mini"}
103
+
104
+ except Exception as e:
105
+ print(f"Error reading config file {config_path}: {e}")
106
+ return {"anthropic": "claude-sonnet-4-20250514", "openai": "o3-mini"}
107
+
108
+
109
+ class CodeImplementationWorkflow:
110
+ """
111
+ Paper Code Implementation Workflow Manager
112
+
113
+ Uses standard MCP architecture:
114
+ 1. Connect to code-implementation server via MCP client
115
+ 2. Use MCP protocol for tool calls
116
+ 3. Support workspace management and operation history tracking
117
+ """
118
+
119
+ # ==================== 1. Class Initialization and Configuration (Infrastructure Layer) ====================
120
+
121
+ def __init__(self, config_path: str = "mcp_agent.secrets.yaml"):
122
+ """Initialize workflow with configuration"""
123
+ self.config_path = config_path
124
+ self.api_config = self._load_api_config()
125
+ self.default_models = get_default_models("mcp_agent.config.yaml")
126
+ self.logger = self._create_logger()
127
+ self.mcp_agent = None
128
+ self.enable_read_tools = (
129
+ True # Default value, will be overridden by run_workflow parameter
130
+ )
131
+
132
+ def _load_api_config(self) -> Dict[str, Any]:
133
+ """Load API configuration from YAML file"""
134
+ try:
135
+ with open(self.config_path, "r", encoding="utf-8") as f:
136
+ return yaml.safe_load(f)
137
+ except Exception as e:
138
+ raise Exception(f"Failed to load API config: {e}")
139
+
140
+ def _create_logger(self) -> logging.Logger:
141
+ """Create and configure logger"""
142
+ logger = logging.getLogger(__name__)
143
+ # Don't add handlers to child loggers - let them propagate to root
144
+ logger.setLevel(logging.INFO)
145
+ return logger
146
+
147
+ def _read_plan_file(self, plan_file_path: str) -> str:
148
+ """Read implementation plan file"""
149
+ plan_path = Path(plan_file_path)
150
+ if not plan_path.exists():
151
+ raise FileNotFoundError(
152
+ f"Implementation plan file not found: {plan_file_path}"
153
+ )
154
+
155
+ with open(plan_path, "r", encoding="utf-8") as f:
156
+ return f.read()
157
+
158
+ def _check_file_tree_exists(self, target_directory: str) -> bool:
159
+ """Check if file tree structure already exists"""
160
+ code_directory = os.path.join(target_directory, "generate_code")
161
+ return os.path.exists(code_directory) and len(os.listdir(code_directory)) > 0
162
+
163
+ # ==================== 2. Public Interface Methods (External API Layer) ====================
164
+
165
+ async def run_workflow(
166
+ self,
167
+ plan_file_path: str,
168
+ target_directory: Optional[str] = None,
169
+ pure_code_mode: bool = False,
170
+ enable_read_tools: bool = True,
171
+ ):
172
+ """Run complete workflow - Main public interface"""
173
+ # Set the read tools configuration
174
+ self.enable_read_tools = enable_read_tools
175
+
176
+ try:
177
+ plan_content = self._read_plan_file(plan_file_path)
178
+
179
+ if target_directory is None:
180
+ target_directory = str(Path(plan_file_path).parent)
181
+
182
+ # Calculate code directory for workspace alignment
183
+ code_directory = os.path.join(target_directory, "generate_code")
184
+
185
+ self.logger.info("=" * 80)
186
+ self.logger.info("🚀 STARTING CODE IMPLEMENTATION WORKFLOW")
187
+ self.logger.info("=" * 80)
188
+ self.logger.info(f"📄 Plan file: {plan_file_path}")
189
+ self.logger.info(f"📂 Plan file parent: {target_directory}")
190
+ self.logger.info(f"🎯 Code directory (MCP workspace): {code_directory}")
191
+ self.logger.info(
192
+ f"⚙️ Read tools: {'ENABLED' if self.enable_read_tools else 'DISABLED'}"
193
+ )
194
+ self.logger.info("=" * 80)
195
+
196
+ results = {}
197
+
198
+ # Check if file tree exists
199
+ if self._check_file_tree_exists(target_directory):
200
+ self.logger.info("File tree exists, skipping creation")
201
+ results["file_tree"] = "Already exists, skipped creation"
202
+ else:
203
+ self.logger.info("Creating file tree...")
204
+ results["file_tree"] = await self.create_file_structure(
205
+ plan_content, target_directory
206
+ )
207
+
208
+ # Code implementation
209
+ if pure_code_mode:
210
+ self.logger.info("Starting pure code implementation...")
211
+ results["code_implementation"] = await self.implement_code_pure(
212
+ plan_content, target_directory, code_directory
213
+ )
214
+ else:
215
+ pass
216
+
217
+ self.logger.info("Workflow execution successful")
218
+
219
+ return {
220
+ "status": "success",
221
+ "plan_file": plan_file_path,
222
+ "target_directory": target_directory,
223
+ "code_directory": os.path.join(target_directory, "generate_code"),
224
+ "results": results,
225
+ "mcp_architecture": "standard",
226
+ }
227
+
228
+ except Exception as e:
229
+ self.logger.error(f"Workflow execution failed: {e}")
230
+
231
+ return {"status": "error", "message": str(e), "plan_file": plan_file_path}
232
+ finally:
233
+ await self._cleanup_mcp_agent()
234
+
235
+ async def create_file_structure(
236
+ self, plan_content: str, target_directory: str
237
+ ) -> str:
238
+ """Create file tree structure based on implementation plan"""
239
+ self.logger.info("Starting file tree creation...")
240
+
241
+ structure_agent = Agent(
242
+ name="StructureGeneratorAgent",
243
+ instruction=STRUCTURE_GENERATOR_PROMPT,
244
+ server_names=["command-executor"],
245
+ )
246
+
247
+ async with structure_agent:
248
+ creator = await structure_agent.attach_llm(
249
+ get_preferred_llm_class(self.config_path)
250
+ )
251
+
252
+ message = f"""Analyze the following implementation plan and generate shell commands to create the file tree structure.
253
+
254
+ Target Directory: {target_directory}/generate_code
255
+
256
+ Implementation Plan:
257
+ {plan_content}
258
+
259
+ Tasks:
260
+ 1. Find the file tree structure in the implementation plan
261
+ 2. Generate shell commands (mkdir -p, touch) to create that structure
262
+ 3. Use the execute_commands tool to run the commands and create the file structure
263
+
264
+ Requirements:
265
+ - Use mkdir -p to create directories
266
+ - Use touch to create files
267
+ - Include __init__.py file for Python packages
268
+ - Use relative paths to the target directory
269
+ - Execute commands to actually create the file structure"""
270
+
271
+ result = await creator.generate_str(message=message)
272
+ self.logger.info("File tree structure creation completed")
273
+ return result
274
+
275
+ async def implement_code_pure(
276
+ self, plan_content: str, target_directory: str, code_directory: str = None
277
+ ) -> str:
278
+ """Pure code implementation - focus on code writing without testing"""
279
+ self.logger.info("Starting pure code implementation (no testing)...")
280
+
281
+ # Use provided code_directory or calculate it (for backwards compatibility)
282
+ if code_directory is None:
283
+ code_directory = os.path.join(target_directory, "generate_code")
284
+
285
+ self.logger.info(f"🎯 Using code directory (MCP workspace): {code_directory}")
286
+
287
+ if not os.path.exists(code_directory):
288
+ raise FileNotFoundError(
289
+ "File tree structure not found, please run file tree creation first"
290
+ )
291
+
292
+ try:
293
+ client, client_type = await self._initialize_llm_client()
294
+ await self._initialize_mcp_agent(code_directory)
295
+
296
+ tools = self._prepare_mcp_tool_definitions()
297
+ system_message = GENERAL_CODE_IMPLEMENTATION_SYSTEM_PROMPT
298
+ messages = []
299
+
300
+ # implementation_message = f"""**TASK: Implement Research Paper Reproduction Code**
301
+
302
+ # You are implementing a complete, working codebase that reproduces the core algorithms, experiments, and methods described in a research paper. Your goal is to create functional code that can replicate the paper's key results and contributions.
303
+
304
+ # **What you need to do:**
305
+ # - Analyze the paper content and reproduction plan to understand requirements
306
+ # - Implement all core algorithms mentioned in the main body of the paper
307
+ # - Create the necessary components following the planned architecture
308
+ # - Test each component to ensure functionality
309
+ # - Integrate components into a cohesive, executable system
310
+ # - Focus on reproducing main contributions rather than appendix-only experiments
311
+
312
+ # **RESOURCES:**
313
+ # - **Paper & Reproduction Plan**: `{target_directory}/` (contains .md paper files and initial_plan.txt with detailed implementation guidance)
314
+ # - **Reference Code Indexes**: `{target_directory}/indexes/` (JSON files with implementation patterns from related codebases)
315
+ # - **Implementation Directory**: `{code_directory}/` (your working directory for all code files)
316
+
317
+ # **CURRENT OBJECTIVE:**
318
+ # Start by reading the reproduction plan (`{target_directory}/initial_plan.txt`) to understand the implementation strategy, then examine the paper content to identify the first priority component to implement. Use the search_code tool to find relevant reference implementations from the indexes directory (`{target_directory}/indexes/*.json`) before coding.
319
+
320
+ # ---
321
+ # **START:** Review the plan above and begin implementation."""
322
+ implementation_message = f"""**Task: Implement code based on the following reproduction plan**
323
+
324
+ **Code Reproduction Plan:**
325
+ {plan_content}
326
+
327
+ **Working Directory:** {code_directory}
328
+
329
+ **Current Objective:** Begin implementation by analyzing the plan structure, examining the current project layout, and implementing the first foundation file according to the plan's priority order."""
330
+
331
+ messages.append({"role": "user", "content": implementation_message})
332
+
333
+ result = await self._pure_code_implementation_loop(
334
+ client,
335
+ client_type,
336
+ system_message,
337
+ messages,
338
+ tools,
339
+ plan_content,
340
+ target_directory,
341
+ )
342
+
343
+ return result
344
+
345
+ finally:
346
+ await self._cleanup_mcp_agent()
347
+
348
+ # ==================== 3. Core Business Logic (Implementation Layer) ====================
349
+
350
+ async def _pure_code_implementation_loop(
351
+ self,
352
+ client,
353
+ client_type,
354
+ system_message,
355
+ messages,
356
+ tools,
357
+ plan_content,
358
+ target_directory,
359
+ ):
360
+ """Pure code implementation loop with memory optimization and phase consistency"""
361
+ max_iterations = 100
362
+ iteration = 0
363
+ start_time = time.time()
364
+ max_time = 2400 # 40 minutes
365
+
366
+ # Initialize specialized agents
367
+ code_agent = CodeImplementationAgent(
368
+ self.mcp_agent, self.logger, self.enable_read_tools
369
+ )
370
+ memory_agent = ConciseMemoryAgent(plan_content, self.logger, target_directory)
371
+
372
+ # Log read tools configuration
373
+ read_tools_status = "ENABLED" if self.enable_read_tools else "DISABLED"
374
+ self.logger.info(
375
+ f"🔧 Read tools (read_file, read_code_mem): {read_tools_status}"
376
+ )
377
+ if not self.enable_read_tools:
378
+ self.logger.info(
379
+ "🚫 No read mode: read_file and read_code_mem tools will be skipped"
380
+ )
381
+
382
+ # Connect code agent with memory agent for summary generation
383
+ # Note: Concise memory agent doesn't need LLM client for summary generation
384
+ code_agent.set_memory_agent(memory_agent, client, client_type)
385
+
386
+ # Initialize memory agent with iteration 0
387
+ memory_agent.start_new_round(iteration=0)
388
+
389
+ while iteration < max_iterations:
390
+ iteration += 1
391
+ elapsed_time = time.time() - start_time
392
+
393
+ if elapsed_time > max_time:
394
+ self.logger.warning(f"Time limit reached: {elapsed_time:.2f}s")
395
+ break
396
+
397
+ # # Test simplified memory approach if we have files implemented
398
+ # if iteration == 5 and code_agent.get_files_implemented_count() > 0:
399
+ # self.logger.info("🧪 Testing simplified memory approach...")
400
+ # test_results = await memory_agent.test_simplified_memory_approach()
401
+ # self.logger.info(f"Memory test results: {test_results}")
402
+
403
+ # self.logger.info(f"Pure code implementation iteration {iteration}: generating code")
404
+
405
+ messages = self._validate_messages(messages)
406
+ current_system_message = code_agent.get_system_prompt()
407
+
408
+ # Round logging removed
409
+
410
+ # Call LLM
411
+ response = await self._call_llm_with_tools(
412
+ client, client_type, current_system_message, messages, tools
413
+ )
414
+
415
+ response_content = response.get("content", "").strip()
416
+ if not response_content:
417
+ response_content = "Continue implementing code files..."
418
+
419
+ messages.append({"role": "assistant", "content": response_content})
420
+
421
+ # Handle tool calls
422
+ if response.get("tool_calls"):
423
+ tool_results = await code_agent.execute_tool_calls(
424
+ response["tool_calls"]
425
+ )
426
+
427
+ # Record essential tool results in concise memory agent
428
+ for tool_call, tool_result in zip(response["tool_calls"], tool_results):
429
+ memory_agent.record_tool_result(
430
+ tool_name=tool_call["name"],
431
+ tool_input=tool_call["input"],
432
+ tool_result=tool_result.get("result"),
433
+ )
434
+
435
+ # NEW LOGIC: Check if write_file was called and trigger memory optimization immediately
436
+
437
+ # Determine guidance based on results
438
+ has_error = self._check_tool_results_for_errors(tool_results)
439
+ files_count = code_agent.get_files_implemented_count()
440
+
441
+ if has_error:
442
+ guidance = self._generate_error_guidance()
443
+ else:
444
+ guidance = self._generate_success_guidance(files_count)
445
+
446
+ compiled_response = self._compile_user_response(tool_results, guidance)
447
+ messages.append({"role": "user", "content": compiled_response})
448
+
449
+ # NEW LOGIC: Apply memory optimization immediately after write_file detection
450
+ if memory_agent.should_trigger_memory_optimization(
451
+ messages, code_agent.get_files_implemented_count()
452
+ ):
453
+ # Memory optimization triggered
454
+
455
+ # Apply concise memory optimization
456
+ files_implemented_count = code_agent.get_files_implemented_count()
457
+ current_system_message = code_agent.get_system_prompt()
458
+ messages = memory_agent.apply_memory_optimization(
459
+ current_system_message, messages, files_implemented_count
460
+ )
461
+
462
+ # Memory optimization completed
463
+
464
+ else:
465
+ files_count = code_agent.get_files_implemented_count()
466
+ no_tools_guidance = self._generate_no_tools_guidance(files_count)
467
+ messages.append({"role": "user", "content": no_tools_guidance})
468
+
469
+ # Check for analysis loop and provide corrective guidance
470
+ if code_agent.is_in_analysis_loop():
471
+ analysis_loop_guidance = code_agent.get_analysis_loop_guidance()
472
+ messages.append({"role": "user", "content": analysis_loop_guidance})
473
+ self.logger.warning(
474
+ "Analysis loop detected and corrective guidance provided"
475
+ )
476
+
477
+ # Record file implementations in memory agent (for the current round)
478
+ for file_info in code_agent.get_implementation_summary()["completed_files"]:
479
+ memory_agent.record_file_implementation(file_info["file"])
480
+
481
+ # REMOVED: Old memory optimization logic - now happens immediately after write_file
482
+ # Memory optimization is now triggered immediately after write_file detection
483
+
484
+ # Start new round for next iteration, sync with workflow iteration
485
+ memory_agent.start_new_round(iteration=iteration)
486
+
487
+ # Check completion
488
+ if any(
489
+ keyword in response_content.lower()
490
+ for keyword in [
491
+ "all files implemented",
492
+ "implementation complete",
493
+ "all phases completed",
494
+ "reproduction plan fully implemented",
495
+ ]
496
+ ):
497
+ self.logger.info("Code implementation declared complete")
498
+ break
499
+
500
+ # Emergency trim if too long
501
+ if len(messages) > 50:
502
+ self.logger.warning(
503
+ "Emergency message trim - applying concise memory optimization"
504
+ )
505
+
506
+ current_system_message = code_agent.get_system_prompt()
507
+ files_implemented_count = code_agent.get_files_implemented_count()
508
+ messages = memory_agent.apply_memory_optimization(
509
+ current_system_message, messages, files_implemented_count
510
+ )
511
+
512
+ return await self._generate_pure_code_final_report_with_concise_agents(
513
+ iteration, time.time() - start_time, code_agent, memory_agent
514
+ )
515
+
516
+ # ==================== 4. MCP Agent and LLM Communication Management (Communication Layer) ====================
517
+
518
+ async def _initialize_mcp_agent(self, code_directory: str):
519
+ """Initialize MCP agent and connect to code-implementation server"""
520
+ try:
521
+ self.mcp_agent = Agent(
522
+ name="CodeImplementationAgent",
523
+ instruction="You are a code implementation assistant, using MCP tools to implement paper code replication.",
524
+ server_names=["code-implementation", "code-reference-indexer"],
525
+ )
526
+
527
+ await self.mcp_agent.__aenter__()
528
+ llm = await self.mcp_agent.attach_llm(
529
+ get_preferred_llm_class(self.config_path)
530
+ )
531
+
532
+ # Set workspace to the target code directory
533
+ workspace_result = await self.mcp_agent.call_tool(
534
+ "set_workspace", {"workspace_path": code_directory}
535
+ )
536
+ self.logger.info(f"Workspace setup result: {workspace_result}")
537
+
538
+ return llm
539
+
540
+ except Exception as e:
541
+ self.logger.error(f"Failed to initialize MCP agent: {e}")
542
+ if self.mcp_agent:
543
+ try:
544
+ await self.mcp_agent.__aexit__(None, None, None)
545
+ except Exception:
546
+ pass
547
+ self.mcp_agent = None
548
+ raise
549
+
550
+ async def _cleanup_mcp_agent(self):
551
+ """Clean up MCP agent resources"""
552
+ if self.mcp_agent:
553
+ try:
554
+ await self.mcp_agent.__aexit__(None, None, None)
555
+ self.logger.info("MCP agent connection closed")
556
+ except Exception as e:
557
+ self.logger.warning(f"Error closing MCP agent: {e}")
558
+ finally:
559
+ self.mcp_agent = None
560
+
561
+ async def _initialize_llm_client(self):
562
+ """Initialize LLM client (Anthropic or OpenAI) based on API key availability"""
563
+ # Check which API has available key and try that first
564
+ anthropic_key = self.api_config.get("anthropic", {}).get("api_key", "")
565
+ openai_key = self.api_config.get("openai", {}).get("api_key", "")
566
+
567
+ # Try Anthropic API first if key is available
568
+ if anthropic_key and anthropic_key.strip():
569
+ try:
570
+ from anthropic import AsyncAnthropic
571
+
572
+ client = AsyncAnthropic(api_key=anthropic_key)
573
+ # Test connection with default model from config
574
+ await client.messages.create(
575
+ model=self.default_models["anthropic"],
576
+ max_tokens=10,
577
+ messages=[{"role": "user", "content": "test"}],
578
+ )
579
+ self.logger.info(
580
+ f"Using Anthropic API with model: {self.default_models['anthropic']}"
581
+ )
582
+ return client, "anthropic"
583
+ except Exception as e:
584
+ self.logger.warning(f"Anthropic API unavailable: {e}")
585
+
586
+ # Try OpenAI API if Anthropic failed or key not available
587
+ if openai_key and openai_key.strip():
588
+ try:
589
+ from openai import AsyncOpenAI
590
+
591
+ # Handle custom base_url if specified
592
+ openai_config = self.api_config.get("openai", {})
593
+ base_url = openai_config.get("base_url")
594
+
595
+ if base_url:
596
+ client = AsyncOpenAI(api_key=openai_key, base_url=base_url)
597
+ else:
598
+ client = AsyncOpenAI(api_key=openai_key)
599
+
600
+ # Test connection with default model from config
601
+ await client.chat.completions.create(
602
+ model=self.default_models["openai"],
603
+ max_tokens=10,
604
+ messages=[{"role": "user", "content": "test"}],
605
+ )
606
+ self.logger.info(
607
+ f"Using OpenAI API with model: {self.default_models['openai']}"
608
+ )
609
+ if base_url:
610
+ self.logger.info(f"Using custom base URL: {base_url}")
611
+ return client, "openai"
612
+ except Exception as e:
613
+ self.logger.warning(f"OpenAI API unavailable: {e}")
614
+
615
+ raise ValueError(
616
+ "No available LLM API - please check your API keys in configuration"
617
+ )
618
+
619
+ async def _call_llm_with_tools(
620
+ self, client, client_type, system_message, messages, tools, max_tokens=8192
621
+ ):
622
+ """Call LLM with tools"""
623
+ try:
624
+ if client_type == "anthropic":
625
+ return await self._call_anthropic_with_tools(
626
+ client, system_message, messages, tools, max_tokens
627
+ )
628
+ elif client_type == "openai":
629
+ return await self._call_openai_with_tools(
630
+ client, system_message, messages, tools, max_tokens
631
+ )
632
+ else:
633
+ raise ValueError(f"Unsupported client type: {client_type}")
634
+ except Exception as e:
635
+ self.logger.error(f"LLM call failed: {e}")
636
+ raise
637
+
638
+ async def _call_anthropic_with_tools(
639
+ self, client, system_message, messages, tools, max_tokens
640
+ ):
641
+ """Call Anthropic API"""
642
+ validated_messages = self._validate_messages(messages)
643
+ if not validated_messages:
644
+ validated_messages = [
645
+ {"role": "user", "content": "Please continue implementing code"}
646
+ ]
647
+
648
+ try:
649
+ response = await client.messages.create(
650
+ model=self.default_models["anthropic"],
651
+ system=system_message,
652
+ messages=validated_messages,
653
+ tools=tools,
654
+ max_tokens=max_tokens,
655
+ temperature=0.2,
656
+ )
657
+ except Exception as e:
658
+ self.logger.error(f"Anthropic API call failed: {e}")
659
+ raise
660
+
661
+ content = ""
662
+ tool_calls = []
663
+
664
+ for block in response.content:
665
+ if block.type == "text":
666
+ content += block.text
667
+ elif block.type == "tool_use":
668
+ tool_calls.append(
669
+ {"id": block.id, "name": block.name, "input": block.input}
670
+ )
671
+
672
+ return {"content": content, "tool_calls": tool_calls}
673
+
674
+ async def _call_openai_with_tools(
675
+ self, client, system_message, messages, tools, max_tokens
676
+ ):
677
+ """Call OpenAI API"""
678
+ openai_tools = []
679
+ for tool in tools:
680
+ openai_tools.append(
681
+ {
682
+ "type": "function",
683
+ "function": {
684
+ "name": tool["name"],
685
+ "description": tool["description"],
686
+ "parameters": tool["input_schema"],
687
+ },
688
+ }
689
+ )
690
+
691
+ openai_messages = [{"role": "system", "content": system_message}]
692
+ openai_messages.extend(messages)
693
+
694
+ response = await client.chat.completions.create(
695
+ model=self.default_models["openai"],
696
+ messages=openai_messages,
697
+ tools=openai_tools if openai_tools else None,
698
+ max_tokens=max_tokens,
699
+ temperature=0.2,
700
+ )
701
+
702
+ message = response.choices[0].message
703
+ content = message.content or ""
704
+
705
+ tool_calls = []
706
+ if message.tool_calls:
707
+ for tool_call in message.tool_calls:
708
+ tool_calls.append(
709
+ {
710
+ "id": tool_call.id,
711
+ "name": tool_call.function.name,
712
+ "input": json.loads(tool_call.function.arguments),
713
+ }
714
+ )
715
+
716
+ return {"content": content, "tool_calls": tool_calls}
717
+
718
+ # ==================== 5. Tools and Utility Methods (Utility Layer) ====================
719
+
720
+ def _validate_messages(self, messages: List[Dict]) -> List[Dict]:
721
+ """Validate and clean message list"""
722
+ valid_messages = []
723
+ for msg in messages:
724
+ content = msg.get("content", "").strip()
725
+ if content:
726
+ valid_messages.append(
727
+ {"role": msg.get("role", "user"), "content": content}
728
+ )
729
+ else:
730
+ self.logger.warning(f"Skipping empty message: {msg}")
731
+ return valid_messages
732
+
733
+ def _prepare_mcp_tool_definitions(self) -> List[Dict[str, Any]]:
734
+ """Prepare tool definitions in Anthropic API standard format"""
735
+ return get_mcp_tools("code_implementation")
736
+
737
+ def _check_tool_results_for_errors(self, tool_results: List[Dict]) -> bool:
738
+ """Check tool results for errors"""
739
+ for result in tool_results:
740
+ try:
741
+ if hasattr(result["result"], "content") and result["result"].content:
742
+ content_text = result["result"].content[0].text
743
+ parsed_result = json.loads(content_text)
744
+ if parsed_result.get("status") == "error":
745
+ return True
746
+ elif isinstance(result["result"], str):
747
+ if "error" in result["result"].lower():
748
+ return True
749
+ except (json.JSONDecodeError, AttributeError, IndexError):
750
+ result_str = str(result["result"])
751
+ if "error" in result_str.lower():
752
+ return True
753
+ return False
754
+
755
+ # ==================== 6. User Interaction and Feedback (Interaction Layer) ====================
756
+
757
+ def _generate_success_guidance(self, files_count: int) -> str:
758
+ """Generate concise success guidance for continuing implementation"""
759
+ return f"""✅ File implementation completed successfully!
760
+
761
+ 📊 **Progress Status:** {files_count} files implemented
762
+
763
+ 🎯 **Next Action:** Check if ALL files from the reproduction plan are implemented.
764
+
765
+ ⚡ **Decision Process:**
766
+ 1. **If ALL files are implemented:** Use `execute_python` or `execute_bash` to test the complete implementation, then respond "**implementation complete**" to end the conversation
767
+ 2. **If MORE files need implementation:** Continue with dependency-aware workflow:
768
+ - **Start with `read_code_mem`** to understand existing implementations and dependencies
769
+ - **Then `write_file`** to implement the new component
770
+ - **Finally: Test** if needed
771
+
772
+ 💡 **Key Point:** Always verify completion status before continuing with new file creation."""
773
+
774
+ def _generate_error_guidance(self) -> str:
775
+ """Generate error guidance for handling issues"""
776
+ return """❌ Error detected during file implementation.
777
+
778
+ 🔧 **Action Required:**
779
+ 1. Review the error details above
780
+ 2. Fix the identified issue
781
+ 3. **Check if ALL files from the reproduction plan are implemented:**
782
+ - **If YES:** Use `execute_python` or `execute_bash` to test the complete implementation, then respond "**implementation complete**" to end the conversation
783
+ - **If NO:** Continue with proper development cycle for next file:
784
+ - **Start with `read_code_mem`** to understand existing implementations
785
+ - **Then `write_file`** to implement properly
786
+ - **Test** if needed
787
+ 4. Ensure proper error handling in future implementations
788
+
789
+ 💡 **Remember:** Always verify if all planned files are implemented before continuing with new file creation."""
790
+
791
+ def _generate_no_tools_guidance(self, files_count: int) -> str:
792
+ """Generate concise guidance when no tools are called"""
793
+ return f"""⚠️ No tool calls detected in your response.
794
+
795
+ 📊 **Current Progress:** {files_count} files implemented
796
+
797
+ 🚨 **Action Required:** You must use tools. **FIRST check if ALL files from the reproduction plan are implemented:**
798
+
799
+ ⚡ **Decision Process:**
800
+ 1. **If ALL files are implemented:** Use `execute_python` or `execute_bash` to test the complete implementation, then respond "**implementation complete**" to end the conversation
801
+ 2. **If MORE files need implementation:** Follow the development cycle:
802
+ - **Start with `read_code_mem`** to understand existing implementations
803
+ - **Then `write_file`** to implement the new component
804
+ - **Finally: Test** if needed
805
+
806
+ 🚨 **Critical:** Always verify completion status first, then use appropriate tools - not just explanations!"""
807
+
808
+ def _compile_user_response(self, tool_results: List[Dict], guidance: str) -> str:
809
+ """Compile tool results and guidance into a single user response"""
810
+ response_parts = []
811
+
812
+ if tool_results:
813
+ response_parts.append("🔧 **Tool Execution Results:**")
814
+ for tool_result in tool_results:
815
+ tool_name = tool_result["tool_name"]
816
+ result_content = tool_result["result"]
817
+ response_parts.append(
818
+ f"```\nTool: {tool_name}\nResult: {result_content}\n```"
819
+ )
820
+
821
+ if guidance:
822
+ response_parts.append("\n" + guidance)
823
+
824
+ return "\n\n".join(response_parts)
825
+
826
+ # ==================== 7. Reporting and Output (Output Layer) ====================
827
+
828
+ async def _generate_pure_code_final_report_with_concise_agents(
829
+ self,
830
+ iterations: int,
831
+ elapsed_time: float,
832
+ code_agent: CodeImplementationAgent,
833
+ memory_agent: ConciseMemoryAgent,
834
+ ):
835
+ """Generate final report using concise agent statistics"""
836
+ try:
837
+ code_stats = code_agent.get_implementation_statistics()
838
+ memory_stats = memory_agent.get_memory_statistics(
839
+ code_stats["files_implemented_count"]
840
+ )
841
+
842
+ if self.mcp_agent:
843
+ history_result = await self.mcp_agent.call_tool(
844
+ "get_operation_history", {"last_n": 30}
845
+ )
846
+ history_data = (
847
+ json.loads(history_result)
848
+ if isinstance(history_result, str)
849
+ else history_result
850
+ )
851
+ else:
852
+ history_data = {"total_operations": 0, "history": []}
853
+
854
+ write_operations = 0
855
+ files_created = []
856
+ if "history" in history_data:
857
+ for item in history_data["history"]:
858
+ if item.get("action") == "write_file":
859
+ write_operations += 1
860
+ file_path = item.get("details", {}).get("file_path", "unknown")
861
+ files_created.append(file_path)
862
+
863
+ report = f"""
864
+ # Pure Code Implementation Completion Report (Write-File-Based Memory Mode)
865
+
866
+ ## Execution Summary
867
+ - Implementation iterations: {iterations}
868
+ - Total elapsed time: {elapsed_time:.2f} seconds
869
+ - Files implemented: {code_stats['total_files_implemented']}
870
+ - File write operations: {write_operations}
871
+ - Total MCP operations: {history_data.get('total_operations', 0)}
872
+
873
+ ## Read Tools Configuration
874
+ - Read tools enabled: {code_stats['read_tools_status']['read_tools_enabled']}
875
+ - Status: {code_stats['read_tools_status']['status']}
876
+ - Tools affected: {', '.join(code_stats['read_tools_status']['tools_affected'])}
877
+
878
+ ## Agent Performance
879
+ ### Code Implementation Agent
880
+ - Files tracked: {code_stats['files_implemented_count']}
881
+ - Technical decisions: {code_stats['technical_decisions_count']}
882
+ - Constraints tracked: {code_stats['constraints_count']}
883
+ - Architecture notes: {code_stats['architecture_notes_count']}
884
+ - Dependency analysis performed: {code_stats['dependency_analysis_count']}
885
+ - Files read for dependencies: {code_stats['files_read_for_dependencies']}
886
+ - Last summary triggered at file count: {code_stats['last_summary_file_count']}
887
+
888
+ ### Concise Memory Agent (Write-File-Based)
889
+ - Last write_file detected: {memory_stats['last_write_file_detected']}
890
+ - Should clear memory next: {memory_stats['should_clear_memory_next']}
891
+ - Files implemented count: {memory_stats['implemented_files_tracked']}
892
+ - Current round: {memory_stats['current_round']}
893
+ - Concise mode active: {memory_stats['concise_mode_active']}
894
+ - Current round tool results: {memory_stats['current_round_tool_results']}
895
+ - Essential tools recorded: {memory_stats['essential_tools_recorded']}
896
+
897
+ ## Files Created
898
+ """
899
+ for file_path in files_created[-20:]:
900
+ report += f"- {file_path}\n"
901
+
902
+ if len(files_created) > 20:
903
+ report += f"... and {len(files_created) - 20} more files\n"
904
+
905
+ report += """
906
+ ## Architecture Features
907
+ ✅ WRITE-FILE-BASED Memory Agent - Clear after each file generation
908
+ ✅ After write_file: Clear history → Keep system prompt + initial plan + tool results
909
+ ✅ Tool accumulation: read_code_mem, read_file, search_reference_code until next write_file
910
+ ✅ Clean memory cycle: write_file → clear → accumulate → write_file → clear
911
+ ✅ Essential tool recording with write_file detection
912
+ ✅ Specialized agent separation for clean code organization
913
+ ✅ MCP-compliant tool execution
914
+ ✅ Production-grade code with comprehensive type hints
915
+ ✅ Intelligent dependency analysis and file reading
916
+ ✅ Automated read_file usage for implementation context
917
+ ✅ Eliminates conversation clutter between file generations
918
+ ✅ Focused memory for efficient next file generation
919
+ """
920
+ return report
921
+
922
+ except Exception as e:
923
+ self.logger.error(f"Failed to generate final report: {e}")
924
+ return f"Failed to generate final report: {str(e)}"