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
toolplane/__init__.py ADDED
@@ -0,0 +1,106 @@
1
+ """Modular Toolplane client package."""
2
+
3
+ from .common import (
4
+ CACHE_TTL_SECONDS,
5
+ DEFAULT_BUFFER_SIZE,
6
+ DEFAULT_HEARTBEAT_INTERVAL,
7
+ DEFAULT_MAX_RETRIES,
8
+ DEFAULT_MAX_WORKERS,
9
+ DEFAULT_POLL_INTERVAL,
10
+ DEFAULT_REQUEST_TIMEOUT,
11
+ GRPC_DEFAULT_PORT,
12
+ HTTP_DEFAULT_PORT,
13
+ BaseConfig,
14
+ BaseConnectionManager,
15
+ BaseSessionManager,
16
+ BaseToolManager,
17
+ TTLCache,
18
+ cache_with_ttl,
19
+ format_error_message,
20
+ generate_session_id,
21
+ normalize_url,
22
+ parse_json_safe,
23
+ validate_session_name,
24
+ validate_tool_name,
25
+ with_retry,
26
+ )
27
+ from .core import (
28
+ ClientConfig,
29
+ ConnectionError,
30
+ MachineError,
31
+ RequestError,
32
+ SessionContext,
33
+ SessionError,
34
+ TaskError,
35
+ ToolError,
36
+ ToolplaneAlreadyExistsError,
37
+ ToolplaneAPIError,
38
+ ToolplaneCancelledError,
39
+ ToolplaneConnectionError,
40
+ ToolplaneError,
41
+ ToolplaneFailedPreconditionError,
42
+ ToolplaneInternalError,
43
+ ToolplaneInvalidArgumentError,
44
+ ToolplaneNotFoundError,
45
+ ToolplanePermissionDeniedError,
46
+ ToolplaneResourceExhaustedError,
47
+ ToolplaneTimeoutError,
48
+ ToolplaneUnauthenticatedError,
49
+ ToolplaneUnavailableError,
50
+ )
51
+ from .http_core import HTTPClientConfig, HTTPSessionContext
52
+ from .provider_runtime import ProviderRuntime
53
+ from .toolplane_client import Toolplane
54
+ from .toolplane_http_client import ToolplaneHTTP
55
+
56
+ __all__ = [
57
+ "Toolplane",
58
+ "ProviderRuntime",
59
+ "SessionContext",
60
+ "ClientConfig",
61
+ "ToolplaneError",
62
+ "ConnectionError",
63
+ "ToolplaneConnectionError",
64
+ "ToolError",
65
+ "SessionError",
66
+ "MachineError",
67
+ "RequestError",
68
+ "TaskError",
69
+ "ToolplaneAPIError",
70
+ "ToolplaneNotFoundError",
71
+ "ToolplaneInvalidArgumentError",
72
+ "ToolplaneFailedPreconditionError",
73
+ "ToolplaneResourceExhaustedError",
74
+ "ToolplaneUnauthenticatedError",
75
+ "ToolplanePermissionDeniedError",
76
+ "ToolplaneAlreadyExistsError",
77
+ "ToolplaneTimeoutError",
78
+ "ToolplaneUnavailableError",
79
+ "ToolplaneCancelledError",
80
+ "ToolplaneInternalError",
81
+ "ToolplaneHTTP",
82
+ "HTTPSessionContext",
83
+ "HTTPClientConfig",
84
+ "BaseConfig",
85
+ "BaseToolManager",
86
+ "BaseSessionManager",
87
+ "BaseConnectionManager",
88
+ "generate_session_id",
89
+ "validate_tool_name",
90
+ "validate_session_name",
91
+ "parse_json_safe",
92
+ "format_error_message",
93
+ "with_retry",
94
+ "cache_with_ttl",
95
+ "TTLCache",
96
+ "normalize_url",
97
+ "DEFAULT_HEARTBEAT_INTERVAL",
98
+ "DEFAULT_MAX_WORKERS",
99
+ "DEFAULT_POLL_INTERVAL",
100
+ "DEFAULT_REQUEST_TIMEOUT",
101
+ "DEFAULT_MAX_RETRIES",
102
+ "DEFAULT_BUFFER_SIZE",
103
+ "CACHE_TTL_SECONDS",
104
+ "GRPC_DEFAULT_PORT",
105
+ "HTTP_DEFAULT_PORT",
106
+ ]
@@ -0,0 +1,93 @@
1
+ """Common functionality shared between gRPC and HTTP Toolplane clients."""
2
+
3
+ from .base_config import BaseConfig
4
+ from .base_connection_manager import BaseConnectionManager
5
+ from .base_session_manager import BaseSessionManager
6
+ from .base_tool_manager import BaseToolManager
7
+ from .constants import (
8
+ BACKPRESSURE_THRESHOLD,
9
+ CACHE_TTL_SECONDS,
10
+ DEFAULT_BUFFER_SIZE,
11
+ DEFAULT_HEARTBEAT_INTERVAL,
12
+ DEFAULT_MAX_RETRIES,
13
+ DEFAULT_MAX_WORKERS,
14
+ DEFAULT_POLL_INTERVAL,
15
+ DEFAULT_REQUEST_TIMEOUT,
16
+ DEFAULT_RETRY_BACKOFF_MS,
17
+ ERROR_CONNECTION_FAILED,
18
+ ERROR_INVALID_PARAMETERS,
19
+ ERROR_SESSION_NOT_FOUND,
20
+ ERROR_STREAM_INTERRUPTED,
21
+ ERROR_TIMEOUT,
22
+ ERROR_TOOL_NOT_FOUND,
23
+ GRPC_DEFAULT_PORT,
24
+ HTTP_DEFAULT_PORT,
25
+ MAX_DESCRIPTION_LENGTH,
26
+ MAX_SESSION_NAME_LENGTH,
27
+ MAX_TOOL_EXECUTION_TIME,
28
+ MAX_TOOL_NAME_LENGTH,
29
+ STREAM_CHUNK_SIZE,
30
+ )
31
+ from .utils import (
32
+ TTLCache,
33
+ cache_with_ttl,
34
+ deep_merge,
35
+ extract_host_port,
36
+ format_error_message,
37
+ generate_session_id,
38
+ is_valid_json,
39
+ merge_dicts,
40
+ normalize_url,
41
+ parse_json_safe,
42
+ sanitize_input,
43
+ timeout_wrapper,
44
+ validate_description,
45
+ validate_session_name,
46
+ validate_tool_name,
47
+ with_retry,
48
+ )
49
+
50
+ __all__ = [
51
+ "BaseConfig",
52
+ "BaseToolManager",
53
+ "BaseSessionManager",
54
+ "BaseConnectionManager",
55
+ "generate_session_id",
56
+ "validate_tool_name",
57
+ "validate_session_name",
58
+ "validate_description",
59
+ "parse_json_safe",
60
+ "format_error_message",
61
+ "with_retry",
62
+ "cache_with_ttl",
63
+ "TTLCache",
64
+ "normalize_url",
65
+ "extract_host_port",
66
+ "merge_dicts",
67
+ "sanitize_input",
68
+ "is_valid_json",
69
+ "deep_merge",
70
+ "timeout_wrapper",
71
+ "DEFAULT_HEARTBEAT_INTERVAL",
72
+ "DEFAULT_MAX_WORKERS",
73
+ "DEFAULT_POLL_INTERVAL",
74
+ "DEFAULT_REQUEST_TIMEOUT",
75
+ "DEFAULT_MAX_RETRIES",
76
+ "DEFAULT_BUFFER_SIZE",
77
+ "DEFAULT_RETRY_BACKOFF_MS",
78
+ "CACHE_TTL_SECONDS",
79
+ "GRPC_DEFAULT_PORT",
80
+ "HTTP_DEFAULT_PORT",
81
+ "MAX_SESSION_NAME_LENGTH",
82
+ "MAX_TOOL_NAME_LENGTH",
83
+ "MAX_DESCRIPTION_LENGTH",
84
+ "MAX_TOOL_EXECUTION_TIME",
85
+ "STREAM_CHUNK_SIZE",
86
+ "BACKPRESSURE_THRESHOLD",
87
+ "ERROR_CONNECTION_FAILED",
88
+ "ERROR_TOOL_NOT_FOUND",
89
+ "ERROR_SESSION_NOT_FOUND",
90
+ "ERROR_INVALID_PARAMETERS",
91
+ "ERROR_TIMEOUT",
92
+ "ERROR_STREAM_INTERRUPTED",
93
+ ]
@@ -0,0 +1,129 @@
1
+ """Base configuration class shared between gRPC and HTTP clients."""
2
+
3
+ from abc import ABC, abstractmethod
4
+ from dataclasses import dataclass, field
5
+ from typing import Any, Dict, Optional
6
+
7
+ from .constants import (
8
+ DEFAULT_BUFFER_SIZE,
9
+ DEFAULT_HEARTBEAT_INTERVAL,
10
+ DEFAULT_MAX_RETRIES,
11
+ DEFAULT_MAX_WORKERS,
12
+ DEFAULT_POLL_INTERVAL,
13
+ DEFAULT_REQUEST_TIMEOUT,
14
+ DEFAULT_RETRY_BACKOFF_MS,
15
+ )
16
+ from .utils import validate_description, validate_session_name
17
+
18
+
19
+ @dataclass
20
+ class BaseConfig(ABC):
21
+ """Base configuration for Toolplane clients."""
22
+
23
+ # Common configuration fields
24
+ api_key: Optional[str] = None
25
+ user_id: Optional[str] = None
26
+ session_name: Optional[str] = None
27
+ session_description: Optional[str] = None
28
+ session_namespace: Optional[str] = None
29
+
30
+ # Performance settings
31
+ heartbeat_interval: int = DEFAULT_HEARTBEAT_INTERVAL
32
+ max_workers: int = DEFAULT_MAX_WORKERS
33
+ poll_interval: float = DEFAULT_POLL_INTERVAL
34
+ request_timeout: int = DEFAULT_REQUEST_TIMEOUT
35
+ max_retries: int = DEFAULT_MAX_RETRIES
36
+ max_buffer_size: int = DEFAULT_BUFFER_SIZE
37
+ retry_backoff_ms: int = DEFAULT_RETRY_BACKOFF_MS
38
+
39
+ # Additional configuration
40
+ extra_config: Dict[str, Any] = field(default_factory=dict)
41
+
42
+ def __post_init__(self):
43
+ """Validate configuration after initialization."""
44
+ self.validate()
45
+ self.normalize()
46
+
47
+ def validate(self):
48
+ """Validate configuration parameters."""
49
+ if self.session_name and not validate_session_name(self.session_name):
50
+ raise ValueError(f"Invalid session name: {self.session_name}")
51
+
52
+ if self.session_description and not validate_description(
53
+ self.session_description
54
+ ):
55
+ raise ValueError(
56
+ f"Session description too long: {len(self.session_description)} characters"
57
+ )
58
+
59
+ if self.heartbeat_interval <= 0:
60
+ raise ValueError("Heartbeat interval must be positive")
61
+
62
+ if self.max_workers <= 0:
63
+ raise ValueError("Max workers must be positive")
64
+
65
+ if self.poll_interval <= 0:
66
+ raise ValueError("Poll interval must be positive")
67
+
68
+ if self.request_timeout <= 0:
69
+ raise ValueError("Request timeout must be positive")
70
+
71
+ if self.max_retries < 0:
72
+ raise ValueError("Max retries must be non-negative")
73
+
74
+ if self.max_buffer_size <= 0:
75
+ raise ValueError("Max buffer size must be positive")
76
+
77
+ if self.retry_backoff_ms < 0:
78
+ raise ValueError("Retry backoff must be non-negative")
79
+
80
+ @abstractmethod
81
+ def normalize(self):
82
+ """Normalize configuration values (protocol-specific)."""
83
+ pass
84
+
85
+ @abstractmethod
86
+ def get_auth_info(self) -> Dict[str, Any]:
87
+ """Get authentication information (protocol-specific)."""
88
+ pass
89
+
90
+ def get_common_fields(self) -> Dict[str, Any]:
91
+ """Get common configuration fields."""
92
+ return {
93
+ "api_key": self.api_key,
94
+ "user_id": self.user_id,
95
+ "session_name": self.session_name,
96
+ "session_description": self.session_description,
97
+ "session_namespace": self.session_namespace,
98
+ "heartbeat_interval": self.heartbeat_interval,
99
+ "max_workers": self.max_workers,
100
+ "poll_interval": self.poll_interval,
101
+ "request_timeout": self.request_timeout,
102
+ "max_retries": self.max_retries,
103
+ "max_buffer_size": self.max_buffer_size,
104
+ "retry_backoff_ms": self.retry_backoff_ms,
105
+ }
106
+
107
+ def update_from_dict(self, config_dict: Dict[str, Any]):
108
+ """Update configuration from dictionary."""
109
+ for key, value in config_dict.items():
110
+ if hasattr(self, key):
111
+ setattr(self, key, value)
112
+ else:
113
+ self.extra_config[key] = value
114
+
115
+ def to_dict(self) -> Dict[str, Any]:
116
+ """Convert configuration to dictionary."""
117
+ result = self.get_common_fields()
118
+ result.update(self.extra_config)
119
+ return result
120
+
121
+ def copy(self) -> "BaseConfig":
122
+ """Create a copy of the configuration."""
123
+ # This will be implemented by subclasses
124
+ raise NotImplementedError("Subclasses must implement copy method")
125
+
126
+ def merge(self, other: "BaseConfig") -> "BaseConfig":
127
+ """Merge this configuration with another."""
128
+ # This will be implemented by subclasses
129
+ raise NotImplementedError("Subclasses must implement merge method")
@@ -0,0 +1,171 @@
1
+ """Base connection manager class shared between gRPC and HTTP clients."""
2
+
3
+ import threading
4
+ import time
5
+ from abc import ABC, abstractmethod
6
+ from typing import Any, Dict
7
+
8
+ try:
9
+ from ..core.errors import ConnectionError
10
+ except ImportError:
11
+ # Fallback for standalone testing
12
+ class ConnectionError(Exception):
13
+ """Connection error for standalone testing."""
14
+
15
+ pass
16
+
17
+
18
+ from .constants import (
19
+ DEFAULT_MAX_RETRIES,
20
+ DEFAULT_RETRY_BACKOFF_MS,
21
+ )
22
+ from .utils import with_retry
23
+
24
+
25
+ class BaseConnectionManager(ABC):
26
+ """Base class for connection management."""
27
+
28
+ def __init__(self, config):
29
+ """Initialize base connection manager."""
30
+ self.config = config
31
+ self.connected = False
32
+ self.connection_lock = threading.RLock()
33
+ self.last_heartbeat = 0
34
+ self.connection_stats = {
35
+ "connect_time": 0,
36
+ "last_activity": 0,
37
+ "total_requests": 0,
38
+ "failed_requests": 0,
39
+ "reconnect_count": 0,
40
+ }
41
+
42
+ @abstractmethod
43
+ def connect(self):
44
+ """Establish connection (protocol-specific)."""
45
+ pass
46
+
47
+ @abstractmethod
48
+ def disconnect(self):
49
+ """Close connection (protocol-specific)."""
50
+ pass
51
+
52
+ @abstractmethod
53
+ def is_connected(self) -> bool:
54
+ """Check if connection is active (protocol-specific)."""
55
+ pass
56
+
57
+ def ensure_connected(self):
58
+ """Ensure connection is established."""
59
+ if not self.is_connected():
60
+ self.connect()
61
+
62
+ @with_retry(max_retries=DEFAULT_MAX_RETRIES, backoff_ms=DEFAULT_RETRY_BACKOFF_MS)
63
+ def reconnect(self):
64
+ """Reconnect with retry logic."""
65
+ try:
66
+ self.disconnect()
67
+ time.sleep(0.1) # Brief pause before reconnecting
68
+ self.connect()
69
+
70
+ with self.connection_lock:
71
+ self.connection_stats["reconnect_count"] += 1
72
+
73
+ except Exception as e:
74
+ raise ConnectionError(f"Failed to reconnect: {e}")
75
+
76
+ def health_check(self) -> bool:
77
+ """Perform health check."""
78
+ try:
79
+ return self._perform_health_check()
80
+ except Exception:
81
+ return False
82
+
83
+ @abstractmethod
84
+ def _perform_health_check(self) -> bool:
85
+ """Perform protocol-specific health check."""
86
+ pass
87
+
88
+ def heartbeat(self):
89
+ """Send heartbeat to server."""
90
+ try:
91
+ if self.is_connected():
92
+ current_time = time.time()
93
+ if current_time - self.last_heartbeat >= self.config.heartbeat_interval:
94
+ self._send_heartbeat()
95
+ self.last_heartbeat = current_time
96
+ except Exception:
97
+ # Heartbeat failures are not critical
98
+ pass
99
+
100
+ @abstractmethod
101
+ def _send_heartbeat(self):
102
+ """Send protocol-specific heartbeat."""
103
+ pass
104
+
105
+ def get_connection_info(self) -> Dict[str, Any]:
106
+ """Get connection information."""
107
+ with self.connection_lock:
108
+ return {
109
+ "connected": self.is_connected(),
110
+ "config": self.config.to_dict(),
111
+ "stats": self.connection_stats.copy(),
112
+ "last_heartbeat": self.last_heartbeat,
113
+ }
114
+
115
+ def reset_stats(self):
116
+ """Reset connection statistics."""
117
+ with self.connection_lock:
118
+ self.connection_stats = {
119
+ "connect_time": time.time() if self.is_connected() else 0,
120
+ "last_activity": time.time(),
121
+ "total_requests": 0,
122
+ "failed_requests": 0,
123
+ "reconnect_count": 0,
124
+ }
125
+
126
+ def _record_request(self, success: bool = True):
127
+ """Record request statistics."""
128
+ with self.connection_lock:
129
+ self.connection_stats["total_requests"] += 1
130
+ self.connection_stats["last_activity"] = time.time()
131
+ if not success:
132
+ self.connection_stats["failed_requests"] += 1
133
+
134
+ def get_request_stats(self) -> Dict[str, Any]:
135
+ """Get request statistics."""
136
+ with self.connection_lock:
137
+ stats = self.connection_stats.copy()
138
+
139
+ # Calculate success rate
140
+ total = stats["total_requests"]
141
+ failed = stats["failed_requests"]
142
+ success_rate = ((total - failed) / total * 100) if total > 0 else 0
143
+
144
+ return {
145
+ "total_requests": total,
146
+ "failed_requests": failed,
147
+ "success_rate": success_rate,
148
+ "reconnect_count": stats["reconnect_count"],
149
+ "uptime": (
150
+ time.time() - stats["connect_time"]
151
+ if stats["connect_time"] > 0
152
+ else 0
153
+ ),
154
+ "last_activity": stats["last_activity"],
155
+ }
156
+
157
+ def __enter__(self):
158
+ """Context manager entry."""
159
+ self.connect()
160
+ return self
161
+
162
+ def __exit__(self, exc_type, exc_val, exc_tb):
163
+ """Context manager exit."""
164
+ self.disconnect()
165
+
166
+ def __del__(self):
167
+ """Destructor to ensure connection is closed."""
168
+ try:
169
+ self.disconnect()
170
+ except Exception:
171
+ pass