toolplane-python-client 0.1.0__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 (81) hide show
  1. toolplane/__init__.py +106 -0
  2. toolplane/common/__init__.py +93 -0
  3. toolplane/common/base_config.py +129 -0
  4. toolplane/common/base_connection_manager.py +171 -0
  5. toolplane/common/base_session_manager.py +321 -0
  6. toolplane/common/base_tool_manager.py +347 -0
  7. toolplane/common/constants.py +47 -0
  8. toolplane/common/utils.py +310 -0
  9. toolplane/core/__init__.py +67 -0
  10. toolplane/core/config.py +107 -0
  11. toolplane/core/connection.py +285 -0
  12. toolplane/core/errors.py +298 -0
  13. toolplane/core/machine.py +480 -0
  14. toolplane/core/request.py +775 -0
  15. toolplane/core/session.py +332 -0
  16. toolplane/core/session_context.py +514 -0
  17. toolplane/core/task.py +130 -0
  18. toolplane/core/tool.py +329 -0
  19. toolplane/http_core/__init__.py +37 -0
  20. toolplane/http_core/http_config.py +97 -0
  21. toolplane/http_core/http_connection.py +409 -0
  22. toolplane/http_core/http_machine.py +298 -0
  23. toolplane/http_core/http_request.py +748 -0
  24. toolplane/http_core/http_session.py +348 -0
  25. toolplane/http_core/http_session_context.py +491 -0
  26. toolplane/http_core/http_task.py +101 -0
  27. toolplane/http_core/http_tool.py +400 -0
  28. toolplane/interfaces/__init__.py +27 -0
  29. toolplane/interfaces/client_interface.py +122 -0
  30. toolplane/interfaces/connection_interface.py +193 -0
  31. toolplane/interfaces/event_interface.py +290 -0
  32. toolplane/interfaces/request_interface.py +439 -0
  33. toolplane/interfaces/session_interface.py +288 -0
  34. toolplane/interfaces/tool_interface.py +441 -0
  35. toolplane/proto/__init__.py +0 -0
  36. toolplane/proto/service_pb2.py +315 -0
  37. toolplane/proto/service_pb2_grpc.py +2240 -0
  38. toolplane/provider_cli.py +268 -0
  39. toolplane/provider_registry.py +77 -0
  40. toolplane/provider_runtime.py +302 -0
  41. toolplane/toolkits/__init__.py +0 -0
  42. toolplane/toolkits/standalone_tools/__init__.py +0 -0
  43. toolplane/toolkits/standalone_tools/create_directory.py +94 -0
  44. toolplane/toolkits/standalone_tools/create_file.py +124 -0
  45. toolplane/toolkits/standalone_tools/file_search.py +229 -0
  46. toolplane/toolkits/standalone_tools/grep_search.py +372 -0
  47. toolplane/toolkits/standalone_tools/launcher.py +146 -0
  48. toolplane/toolkits/standalone_tools/list_dir.py +395 -0
  49. toolplane/toolkits/standalone_tools/read_file.py +346 -0
  50. toolplane/toolkits/standalone_tools/replace_string_in_file.py +407 -0
  51. toolplane/toolkits/standalone_tools/run_tests.py +66 -0
  52. toolplane/toolkits/standalone_tools/semantic_search.py +485 -0
  53. toolplane/toolkits/standalone_tools/standalone_toolkit.py +979 -0
  54. toolplane/toolkits/standalone_tools/test_failure_analysis.py +618 -0
  55. toolplane/toolkits/standalone_tools/test_standalone_toolkit.py +517 -0
  56. toolplane/toolkits/swe/__init__.py +35 -0
  57. toolplane/toolkits/swe/create_directory.py +15 -0
  58. toolplane/toolkits/swe/create_file.py +15 -0
  59. toolplane/toolkits/swe/descriptions.py +273 -0
  60. toolplane/toolkits/swe/execute_bash.py +93 -0
  61. toolplane/toolkits/swe/file_editor.py +775 -0
  62. toolplane/toolkits/swe/file_search.py +16 -0
  63. toolplane/toolkits/swe/finish.py +50 -0
  64. toolplane/toolkits/swe/grep_search.py +19 -0
  65. toolplane/toolkits/swe/list_dir.py +407 -0
  66. toolplane/toolkits/swe/read_file.py +18 -0
  67. toolplane/toolkits/swe/replace_string_in_file.py +17 -0
  68. toolplane/toolkits/swe/search.py +260 -0
  69. toolplane/toolkits/swe/semantic_search.py +20 -0
  70. toolplane/toolkits/swe/str_replace_editor.py +647 -0
  71. toolplane/toolkits/swe/submit.py +29 -0
  72. toolplane/toolkits/swe/swe_toolkit.py +1296 -0
  73. toolplane/toolplane_client.py +686 -0
  74. toolplane/toolplane_http_client.py +681 -0
  75. toolplane/utils/__init__.py +3 -0
  76. toolplane/utils/schema.py +146 -0
  77. toolplane_python_client-0.1.0.dist-info/METADATA +543 -0
  78. toolplane_python_client-0.1.0.dist-info/RECORD +81 -0
  79. toolplane_python_client-0.1.0.dist-info/WHEEL +5 -0
  80. toolplane_python_client-0.1.0.dist-info/entry_points.txt +2 -0
  81. toolplane_python_client-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,193 @@
1
+ """Connection management interface definitions."""
2
+
3
+ from abc import ABC, abstractmethod
4
+ from enum import Enum
5
+ from typing import Any, Dict, Protocol, runtime_checkable
6
+
7
+
8
+ class ConnectionState(Enum):
9
+ """Connection states."""
10
+
11
+ DISCONNECTED = "disconnected"
12
+ CONNECTING = "connecting"
13
+ CONNECTED = "connected"
14
+ RECONNECTING = "reconnecting"
15
+ ERROR = "error"
16
+
17
+
18
+ class ConnectionStrategy(Enum):
19
+ """Connection strategies."""
20
+
21
+ DIRECT = "direct"
22
+ POOLED = "pooled"
23
+ LOAD_BALANCED = "load_balanced"
24
+ CIRCUIT_BREAKER = "circuit_breaker"
25
+
26
+
27
+ @runtime_checkable
28
+ class IConnectionManager(Protocol):
29
+ """Protocol interface for connection management."""
30
+
31
+ @property
32
+ def connected(self) -> bool:
33
+ """Check if connected to server."""
34
+ ...
35
+
36
+ @property
37
+ def state(self) -> ConnectionState:
38
+ """Get current connection state."""
39
+ ...
40
+
41
+ def connect(self) -> bool:
42
+ """Connect to server."""
43
+ ...
44
+
45
+ def disconnect(self) -> None:
46
+ """Disconnect from server."""
47
+ ...
48
+
49
+ def health_check(self) -> bool:
50
+ """Perform health check."""
51
+ ...
52
+
53
+ def get_connection_info(self) -> Dict[str, Any]:
54
+ """Get connection information."""
55
+ ...
56
+
57
+
58
+ class IConnectionStrategy(ABC):
59
+ """Abstract strategy for connection management."""
60
+
61
+ @abstractmethod
62
+ def connect(self, config: Dict[str, Any]) -> bool:
63
+ """Execute connection strategy."""
64
+ pass
65
+
66
+ @abstractmethod
67
+ def disconnect(self) -> None:
68
+ """Execute disconnection strategy."""
69
+ pass
70
+
71
+ @abstractmethod
72
+ def health_check(self) -> bool:
73
+ """Execute health check strategy."""
74
+ pass
75
+
76
+ @abstractmethod
77
+ def get_metrics(self) -> Dict[str, Any]:
78
+ """Get connection metrics."""
79
+ pass
80
+
81
+
82
+ class DirectConnectionStrategy(IConnectionStrategy):
83
+ """Direct connection strategy implementation."""
84
+
85
+ def __init__(self):
86
+ self._connected = False
87
+ self._connection_info = {}
88
+
89
+ def connect(self, config: Dict[str, Any]) -> bool:
90
+ """Establish direct connection."""
91
+ try:
92
+ # Implementation would depend on protocol
93
+ # This is a template for the strategy pattern
94
+ self._connection_info = {
95
+ "host": config.get("server_host", "localhost"),
96
+ "port": config.get("server_port", 8080),
97
+ "protocol": config.get("protocol", "http"),
98
+ "connected_at": __import__("time").time(),
99
+ }
100
+ self._connected = True
101
+ return True
102
+ except Exception:
103
+ self._connected = False
104
+ return False
105
+
106
+ def disconnect(self) -> None:
107
+ """Close direct connection."""
108
+ self._connected = False
109
+ self._connection_info.clear()
110
+
111
+ def health_check(self) -> bool:
112
+ """Check direct connection health."""
113
+ return self._connected
114
+
115
+ def get_metrics(self) -> Dict[str, Any]:
116
+ """Get direct connection metrics."""
117
+ return {
118
+ "connected": self._connected,
119
+ "connection_info": self._connection_info.copy(),
120
+ "strategy": "direct",
121
+ }
122
+
123
+
124
+ class PooledConnectionStrategy(IConnectionStrategy):
125
+ """Pooled connection strategy implementation."""
126
+
127
+ def __init__(self, pool_size: int = 5):
128
+ self.pool_size = pool_size
129
+ self._pool = []
130
+ self._active_connections = 0
131
+
132
+ def connect(self, config: Dict[str, Any]) -> bool:
133
+ """Establish pooled connections."""
134
+ try:
135
+ # Initialize connection pool
136
+ for _ in range(self.pool_size):
137
+ conn_info = {
138
+ "id": len(self._pool),
139
+ "host": config.get("server_host", "localhost"),
140
+ "port": config.get("server_port", 8080),
141
+ "active": False,
142
+ "created_at": __import__("time").time(),
143
+ }
144
+ self._pool.append(conn_info)
145
+
146
+ self._active_connections = self.pool_size
147
+ return True
148
+ except Exception:
149
+ return False
150
+
151
+ def disconnect(self) -> None:
152
+ """Close all pooled connections."""
153
+ self._pool.clear()
154
+ self._active_connections = 0
155
+
156
+ def health_check(self) -> bool:
157
+ """Check pool health."""
158
+ return self._active_connections > 0
159
+
160
+ def get_metrics(self) -> Dict[str, Any]:
161
+ """Get pooled connection metrics."""
162
+ return {
163
+ "pool_size": len(self._pool),
164
+ "active_connections": self._active_connections,
165
+ "strategy": "pooled",
166
+ }
167
+
168
+
169
+ class ConnectionStrategyFactory:
170
+ """Factory for creating connection strategies."""
171
+
172
+ _strategies = {
173
+ ConnectionStrategy.DIRECT: DirectConnectionStrategy,
174
+ ConnectionStrategy.POOLED: PooledConnectionStrategy,
175
+ }
176
+
177
+ @classmethod
178
+ def create_strategy(
179
+ self, strategy_type: ConnectionStrategy, **kwargs
180
+ ) -> IConnectionStrategy:
181
+ """Create a connection strategy instance."""
182
+ if strategy_type not in self._strategies:
183
+ raise ValueError(f"Unsupported connection strategy: {strategy_type}")
184
+
185
+ strategy_class = self._strategies[strategy_type]
186
+ return strategy_class(**kwargs)
187
+
188
+ @classmethod
189
+ def register_strategy(
190
+ cls, strategy_type: ConnectionStrategy, strategy_class: type
191
+ ) -> None:
192
+ """Register a new connection strategy."""
193
+ cls._strategies[strategy_type] = strategy_class
@@ -0,0 +1,290 @@
1
+ """Event system interfaces for decoupled component communication."""
2
+
3
+ import logging
4
+ import weakref
5
+ from abc import ABC, abstractmethod
6
+ from dataclasses import dataclass, field
7
+ from datetime import datetime
8
+ from enum import Enum
9
+ from typing import Any, Callable, Dict, List, Optional, Protocol, runtime_checkable
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class EventType(Enum):
15
+ """Standard event types for Toolplane system."""
16
+
17
+ # Connection events
18
+ CONNECTION_ESTABLISHED = "connection.established"
19
+ CONNECTION_LOST = "connection.lost"
20
+ CONNECTION_ERROR = "connection.error"
21
+
22
+ # Session events
23
+ SESSION_CREATED = "session.created"
24
+ SESSION_DESTROYED = "session.destroyed"
25
+ SESSION_ERROR = "session.error"
26
+
27
+ # Tool events
28
+ TOOL_REGISTERED = "tool.registered"
29
+ TOOL_UNREGISTERED = "tool.unregistered"
30
+ TOOL_EXECUTED = "tool.executed"
31
+ TOOL_ERROR = "tool.error"
32
+
33
+ # Machine events
34
+ MACHINE_REGISTERED = "machine.registered"
35
+ MACHINE_UNREGISTERED = "machine.unregistered"
36
+ MACHINE_HEARTBEAT_FAILED = "machine.heartbeat_failed"
37
+ MACHINE_RECOVERY_STARTED = "machine.recovery_started"
38
+ MACHINE_RECOVERY_SUCCEEDED = "machine.recovery_succeeded"
39
+ MACHINE_RECOVERY_FAILED = "machine.recovery_failed"
40
+
41
+ # Request events
42
+ REQUEST_STARTED = "request.started"
43
+ REQUEST_COMPLETED = "request.completed"
44
+ REQUEST_FAILED = "request.failed"
45
+ REQUEST_TIMEOUT = "request.timeout"
46
+
47
+ # Client events
48
+ CLIENT_STARTED = "client.started"
49
+ CLIENT_STOPPED = "client.stopped"
50
+ CLIENT_ERROR = "client.error"
51
+
52
+
53
+ @dataclass
54
+ class Event:
55
+ """Represents an event in the system."""
56
+
57
+ type: EventType
58
+ source: str
59
+ data: Dict[str, Any] = field(default_factory=dict)
60
+ timestamp: datetime = field(default_factory=datetime.now)
61
+ correlation_id: Optional[str] = None
62
+
63
+ def to_dict(self) -> Dict[str, Any]:
64
+ """Convert event to dictionary."""
65
+ return {
66
+ "type": self.type.value,
67
+ "source": self.source,
68
+ "data": self.data,
69
+ "timestamp": self.timestamp.isoformat(),
70
+ "correlation_id": self.correlation_id,
71
+ }
72
+
73
+
74
+ @runtime_checkable
75
+ class IEventHandler(Protocol):
76
+ """Protocol for event handlers."""
77
+
78
+ def handle_event(self, event: Event) -> None:
79
+ """Handle an event."""
80
+ ...
81
+
82
+
83
+ class IEventEmitter(ABC):
84
+ """Abstract interface for event emission."""
85
+
86
+ @abstractmethod
87
+ def emit(self, event: Event) -> None:
88
+ """Emit an event."""
89
+ pass
90
+
91
+ @abstractmethod
92
+ def subscribe(
93
+ self,
94
+ event_type: EventType,
95
+ handler: IEventHandler,
96
+ filter_func: Optional[Callable[[Event], bool]] = None,
97
+ ) -> str:
98
+ """Subscribe to events of a specific type."""
99
+ pass
100
+
101
+ @abstractmethod
102
+ def unsubscribe(self, subscription_id: str) -> bool:
103
+ """Unsubscribe from events."""
104
+ pass
105
+
106
+ @abstractmethod
107
+ def get_subscribers(self, event_type: EventType) -> List[IEventHandler]:
108
+ """Get subscribers for an event type."""
109
+ pass
110
+
111
+
112
+ class EventBus(IEventEmitter):
113
+ """Central event bus implementation with weak references."""
114
+
115
+ def __init__(self):
116
+ self._subscribers: Dict[EventType, Dict[str, weakref.ReferenceType]] = {}
117
+ self._filters: Dict[str, Callable[[Event], bool]] = {}
118
+ self._subscription_counter = 0
119
+
120
+ def emit(self, event: Event) -> None:
121
+ """Emit an event to all subscribers."""
122
+ if event.type not in self._subscribers:
123
+ return
124
+
125
+ # Clean up dead references and call live handlers
126
+ dead_refs = []
127
+ for sub_id, handler_ref in self._subscribers[event.type].items():
128
+ handler = handler_ref()
129
+ if handler is None:
130
+ dead_refs.append(sub_id)
131
+ continue
132
+
133
+ # Apply filter if present
134
+ if sub_id in self._filters:
135
+ if not self._filters[sub_id](event):
136
+ continue
137
+
138
+ try:
139
+ handler.handle_event(event)
140
+ except Exception as e:
141
+ # Log error but don't stop other handlers
142
+ logger.warning("Error in event handler %s: %s", sub_id, e)
143
+
144
+ # Clean up dead references
145
+ for sub_id in dead_refs:
146
+ self._remove_subscription(event.type, sub_id)
147
+
148
+ def subscribe(
149
+ self,
150
+ event_type: EventType,
151
+ handler: IEventHandler,
152
+ filter_func: Optional[Callable[[Event], bool]] = None,
153
+ ) -> str:
154
+ """Subscribe to events of a specific type."""
155
+ if event_type not in self._subscribers:
156
+ self._subscribers[event_type] = {}
157
+
158
+ # Generate unique subscription ID
159
+ self._subscription_counter += 1
160
+ sub_id = f"sub_{self._subscription_counter}"
161
+
162
+ # Store weak reference to handler
163
+ self._subscribers[event_type][sub_id] = weakref.ref(handler)
164
+
165
+ # Store filter if provided
166
+ if filter_func:
167
+ self._filters[sub_id] = filter_func
168
+
169
+ return sub_id
170
+
171
+ def unsubscribe(self, subscription_id: str) -> bool:
172
+ """Unsubscribe from events."""
173
+ for event_type in self._subscribers:
174
+ if subscription_id in self._subscribers[event_type]:
175
+ self._remove_subscription(event_type, subscription_id)
176
+ return True
177
+ return False
178
+
179
+ def get_subscribers(self, event_type: EventType) -> List[IEventHandler]:
180
+ """Get live subscribers for an event type."""
181
+ if event_type not in self._subscribers:
182
+ return []
183
+
184
+ subscribers = []
185
+ dead_refs = []
186
+
187
+ for sub_id, handler_ref in self._subscribers[event_type].items():
188
+ handler = handler_ref()
189
+ if handler is None:
190
+ dead_refs.append(sub_id)
191
+ else:
192
+ subscribers.append(handler)
193
+
194
+ # Clean up dead references
195
+ for sub_id in dead_refs:
196
+ self._remove_subscription(event_type, sub_id)
197
+
198
+ return subscribers
199
+
200
+ def _remove_subscription(self, event_type: EventType, sub_id: str) -> None:
201
+ """Remove a subscription."""
202
+ if event_type in self._subscribers and sub_id in self._subscribers[event_type]:
203
+ del self._subscribers[event_type][sub_id]
204
+
205
+ if sub_id in self._filters:
206
+ del self._filters[sub_id]
207
+
208
+
209
+ class EventHandlerMixin:
210
+ """Mixin to add event handling capabilities to classes."""
211
+
212
+ def __init__(self, *args, **kwargs):
213
+ super().__init__(*args, **kwargs)
214
+ self._event_subscriptions: List[str] = []
215
+
216
+ def subscribe_to_events(
217
+ self,
218
+ event_bus: IEventEmitter,
219
+ event_types: List[EventType],
220
+ filter_func: Optional[Callable[[Event], bool]] = None,
221
+ ) -> None:
222
+ """Subscribe to multiple event types."""
223
+ for event_type in event_types:
224
+ sub_id = event_bus.subscribe(event_type, self, filter_func)
225
+ self._event_subscriptions.append(sub_id)
226
+
227
+ def unsubscribe_from_events(self, event_bus: IEventEmitter) -> None:
228
+ """Unsubscribe from all events."""
229
+ for sub_id in self._event_subscriptions:
230
+ event_bus.unsubscribe(sub_id)
231
+ self._event_subscriptions.clear()
232
+
233
+ def handle_event(self, event: Event) -> None:
234
+ """Default event handler - override in subclasses."""
235
+ pass
236
+
237
+
238
+ # Global event bus instance (singleton pattern)
239
+ _global_event_bus: Optional[EventBus] = None
240
+
241
+
242
+ def get_global_event_bus() -> EventBus:
243
+ """Get the global event bus instance."""
244
+ global _global_event_bus
245
+ if _global_event_bus is None:
246
+ _global_event_bus = EventBus()
247
+ return _global_event_bus
248
+
249
+
250
+ class EventLogger(IEventHandler):
251
+ """Event handler that logs events."""
252
+
253
+ def __init__(self, log_func: Optional[Callable[[str], None]] = None):
254
+ self.log_func = log_func or print
255
+
256
+ def handle_event(self, event: Event) -> None:
257
+ """Log the event."""
258
+ self.log_func(
259
+ f"Event: {event.type.value} from {event.source} "
260
+ f"at {event.timestamp.isoformat()}"
261
+ )
262
+
263
+
264
+ class EventMetrics(IEventHandler):
265
+ """Event handler that collects metrics."""
266
+
267
+ def __init__(self):
268
+ self.event_counts: Dict[EventType, int] = {}
269
+ self.error_counts: Dict[str, int] = {}
270
+
271
+ def handle_event(self, event: Event) -> None:
272
+ """Collect metrics from the event."""
273
+ # Count events by type
274
+ self.event_counts[event.type] = self.event_counts.get(event.type, 0) + 1
275
+
276
+ # Count errors by source
277
+ if "error" in event.type.value:
278
+ source = event.source
279
+ self.error_counts[source] = self.error_counts.get(source, 0) + 1
280
+
281
+ def get_metrics(self) -> Dict[str, Any]:
282
+ """Get collected metrics."""
283
+ return {
284
+ "event_counts": {
285
+ et.value: count for et, count in self.event_counts.items()
286
+ },
287
+ "error_counts": self.error_counts.copy(),
288
+ "total_events": sum(self.event_counts.values()),
289
+ "total_errors": sum(self.error_counts.values()),
290
+ }