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