code-puppy 0.0.127__py3-none-any.whl → 0.0.129__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 (35) hide show
  1. code_puppy/__init__.py +1 -0
  2. code_puppy/agent.py +65 -69
  3. code_puppy/agents/agent_code_puppy.py +0 -3
  4. code_puppy/agents/runtime_manager.py +231 -0
  5. code_puppy/command_line/command_handler.py +56 -25
  6. code_puppy/command_line/mcp_commands.py +1298 -0
  7. code_puppy/command_line/meta_command_handler.py +3 -2
  8. code_puppy/command_line/model_picker_completion.py +21 -8
  9. code_puppy/http_utils.py +1 -1
  10. code_puppy/main.py +99 -158
  11. code_puppy/mcp/__init__.py +23 -0
  12. code_puppy/mcp/async_lifecycle.py +237 -0
  13. code_puppy/mcp/circuit_breaker.py +218 -0
  14. code_puppy/mcp/config_wizard.py +437 -0
  15. code_puppy/mcp/dashboard.py +291 -0
  16. code_puppy/mcp/error_isolation.py +360 -0
  17. code_puppy/mcp/examples/retry_example.py +208 -0
  18. code_puppy/mcp/health_monitor.py +549 -0
  19. code_puppy/mcp/managed_server.py +346 -0
  20. code_puppy/mcp/manager.py +701 -0
  21. code_puppy/mcp/registry.py +412 -0
  22. code_puppy/mcp/retry_manager.py +321 -0
  23. code_puppy/mcp/server_registry_catalog.py +751 -0
  24. code_puppy/mcp/status_tracker.py +355 -0
  25. code_puppy/messaging/spinner/textual_spinner.py +6 -2
  26. code_puppy/model_factory.py +19 -4
  27. code_puppy/models.json +8 -6
  28. code_puppy/tui/app.py +19 -27
  29. code_puppy/tui/tests/test_agent_command.py +22 -15
  30. {code_puppy-0.0.127.data → code_puppy-0.0.129.data}/data/code_puppy/models.json +8 -6
  31. {code_puppy-0.0.127.dist-info → code_puppy-0.0.129.dist-info}/METADATA +4 -3
  32. {code_puppy-0.0.127.dist-info → code_puppy-0.0.129.dist-info}/RECORD +35 -19
  33. {code_puppy-0.0.127.dist-info → code_puppy-0.0.129.dist-info}/WHEEL +0 -0
  34. {code_puppy-0.0.127.dist-info → code_puppy-0.0.129.dist-info}/entry_points.txt +0 -0
  35. {code_puppy-0.0.127.dist-info → code_puppy-0.0.129.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,208 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Example usage of RetryManager with MCP server operations.
4
+
5
+ This demonstrates how the RetryManager can be integrated with MCP server calls
6
+ to handle transient failures gracefully with intelligent backoff strategies.
7
+ """
8
+
9
+ import asyncio
10
+ import random
11
+ import sys
12
+ from pathlib import Path
13
+ from typing import Any
14
+
15
+ # Add project root to path
16
+ project_root = Path(__file__).parents[3]
17
+ sys.path.insert(0, str(project_root))
18
+
19
+ from code_puppy.mcp.retry_manager import get_retry_manager, retry_mcp_call
20
+
21
+
22
+ class MockMCPServer:
23
+ """Mock MCP server for demonstration purposes."""
24
+
25
+ def __init__(self, failure_rate: float = 0.3):
26
+ """
27
+ Initialize the mock server.
28
+
29
+ Args:
30
+ failure_rate: Probability of failure (0.0 to 1.0)
31
+ """
32
+ self.failure_rate = failure_rate
33
+ self.call_count = 0
34
+
35
+ async def list_tools(self) -> list:
36
+ """Simulate listing available tools."""
37
+ self.call_count += 1
38
+
39
+ # Simulate random failures
40
+ if random.random() < self.failure_rate:
41
+ raise ConnectionError(f"Simulated connection failure (call #{self.call_count})")
42
+
43
+ return [
44
+ {"name": "read_file", "description": "Read a file"},
45
+ {"name": "write_file", "description": "Write a file"},
46
+ {"name": "list_directory", "description": "List directory contents"}
47
+ ]
48
+
49
+ async def call_tool(self, name: str, args: dict) -> Any:
50
+ """Simulate calling a tool."""
51
+ self.call_count += 1
52
+
53
+ # Simulate random failures
54
+ if random.random() < self.failure_rate:
55
+ if random.random() < 0.5:
56
+ raise ConnectionError(f"Connection failed for {name}")
57
+ else:
58
+ # Simulate a 500 error
59
+ import httpx
60
+ from unittest.mock import Mock
61
+ response = Mock()
62
+ response.status_code = 500
63
+ raise httpx.HTTPStatusError("Server Error", request=Mock(), response=response)
64
+
65
+ return f"Tool '{name}' executed with args: {args}"
66
+
67
+
68
+ async def demonstrate_basic_retry():
69
+ """Demonstrate basic retry functionality."""
70
+ print("=== Basic Retry Demonstration ===")
71
+
72
+ retry_manager = get_retry_manager()
73
+ server = MockMCPServer(failure_rate=0.5) # 50% failure rate
74
+
75
+ async def list_tools_call():
76
+ return await server.list_tools()
77
+
78
+ try:
79
+ result = await retry_manager.retry_with_backoff(
80
+ func=list_tools_call,
81
+ max_attempts=3,
82
+ strategy="exponential",
83
+ server_id="demo-server"
84
+ )
85
+ print(f"✅ Success: Retrieved {len(result)} tools")
86
+ print(f"Server call count: {server.call_count}")
87
+ except Exception as e:
88
+ print(f"❌ Failed after retries: {e}")
89
+
90
+ # Check retry stats
91
+ stats = await retry_manager.get_retry_stats("demo-server")
92
+ print(f"Retry stats: total={stats.total_retries}, successful={stats.successful_retries}")
93
+ print()
94
+
95
+
96
+ async def demonstrate_different_strategies():
97
+ """Demonstrate different backoff strategies."""
98
+ print("=== Backoff Strategies Demonstration ===")
99
+
100
+ strategies = ["fixed", "linear", "exponential", "exponential_jitter"]
101
+
102
+ for strategy in strategies:
103
+ print(f"\n{strategy.upper()} strategy:")
104
+ server = MockMCPServer(failure_rate=0.7) # High failure rate
105
+
106
+ try:
107
+ start_time = asyncio.get_event_loop().time()
108
+
109
+ result = await retry_mcp_call(
110
+ func=lambda: server.call_tool("read_file", {"path": "/example.txt"}),
111
+ server_id=f"server-{strategy}",
112
+ max_attempts=3,
113
+ strategy=strategy
114
+ )
115
+
116
+ end_time = asyncio.get_event_loop().time()
117
+ print(f" ✅ Success: {result}")
118
+ print(f" Time taken: {end_time - start_time:.2f}s")
119
+ print(f" Call count: {server.call_count}")
120
+ except Exception as e:
121
+ end_time = asyncio.get_event_loop().time()
122
+ print(f" ❌ Failed: {e}")
123
+ print(f" Time taken: {end_time - start_time:.2f}s")
124
+ print(f" Call count: {server.call_count}")
125
+
126
+
127
+ async def demonstrate_concurrent_retries():
128
+ """Demonstrate concurrent retry operations."""
129
+ print("\n=== Concurrent Retries Demonstration ===")
130
+
131
+ retry_manager = get_retry_manager()
132
+
133
+ # Create multiple servers with different failure rates
134
+ servers = [
135
+ ("reliable-server", MockMCPServer(failure_rate=0.1)),
136
+ ("unreliable-server", MockMCPServer(failure_rate=0.8)),
137
+ ("moderate-server", MockMCPServer(failure_rate=0.4))
138
+ ]
139
+
140
+ async def make_call(server_name: str, server: MockMCPServer):
141
+ """Make a call with retry handling."""
142
+ try:
143
+ result = await retry_manager.retry_with_backoff(
144
+ func=lambda: server.list_tools(),
145
+ max_attempts=3,
146
+ strategy="exponential_jitter",
147
+ server_id=server_name
148
+ )
149
+ return f"{server_name}: Success (calls: {server.call_count})"
150
+ except Exception as e:
151
+ return f"{server_name}: Failed - {e} (calls: {server.call_count})"
152
+
153
+ # Run concurrent calls
154
+ tasks = [make_call(name, server) for name, server in servers]
155
+ results = await asyncio.gather(*tasks)
156
+
157
+ print("Concurrent results:")
158
+ for result in results:
159
+ print(f" {result}")
160
+
161
+ # Show overall stats
162
+ print("\nOverall retry statistics:")
163
+ all_stats = await retry_manager.get_all_stats()
164
+ for server_id, stats in all_stats.items():
165
+ success_rate = (stats.successful_retries / max(stats.total_retries, 1)) * 100
166
+ print(f" {server_id}: {stats.total_retries} retries, {success_rate:.1f}% success rate")
167
+
168
+
169
+ async def demonstrate_error_classification():
170
+ """Demonstrate error classification for retry decisions."""
171
+ print("\n=== Error Classification Demonstration ===")
172
+
173
+ retry_manager = get_retry_manager()
174
+
175
+ # Test different error types
176
+ test_errors = [
177
+ ConnectionError("Network connection failed"),
178
+ asyncio.TimeoutError("Request timeout"),
179
+ ValueError("JSON decode error: invalid format"),
180
+ ValueError("Schema validation failed"),
181
+ Exception("Authentication failed"),
182
+ Exception("Permission denied")
183
+ ]
184
+
185
+ print("Error retry decisions:")
186
+ for error in test_errors:
187
+ should_retry = retry_manager.should_retry(error)
188
+ status = "✅ RETRY" if should_retry else "❌ NO RETRY"
189
+ print(f" {type(error).__name__}: {error} → {status}")
190
+
191
+
192
+ async def main():
193
+ """Run all demonstrations."""
194
+ print("RetryManager Example Demonstrations")
195
+ print("=" * 50)
196
+
197
+ await demonstrate_basic_retry()
198
+ await demonstrate_different_strategies()
199
+ await demonstrate_concurrent_retries()
200
+ await demonstrate_error_classification()
201
+
202
+ print("\n🎉 All demonstrations completed!")
203
+
204
+
205
+ if __name__ == "__main__":
206
+ # Set a seed for reproducible results in the demo
207
+ random.seed(42)
208
+ asyncio.run(main())