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,47 @@
1
+ """Constants used across Toolplane client implementations."""
2
+
3
+ # Server-side ceiling for wait_timeout_seconds on the synchronous execution
4
+ # entrypoints (the same ceiling as timeout_seconds). The server rejects
5
+ # values above it with OUT_OF_RANGE / TIMEOUT_ABOVE_MAX.
6
+ WAIT_TIMEOUT_MAX_SECONDS = 3600
7
+
8
+ # gRPC message-size ladder, sized from the server's chunk limits: replay
9
+ # window reads (8 MiB) and chunk batch writes (16 MiB) plus envelope
10
+ # headroom. The gRPC defaults (4 MiB receive) reject a full window read at
11
+ # the client before it reaches application code.
12
+ GRPC_MAX_RECEIVE_MESSAGE_LENGTH = 9 * 1024 * 1024
13
+ GRPC_MAX_SEND_MESSAGE_LENGTH = 17 * 1024 * 1024
14
+
15
+ # Default configuration values
16
+ DEFAULT_HEARTBEAT_INTERVAL = 60
17
+ DEFAULT_MAX_WORKERS = 10
18
+ DEFAULT_POLL_INTERVAL = 0.5
19
+ DEFAULT_REQUEST_TIMEOUT = 60
20
+ DEFAULT_MAX_RETRIES = 3
21
+ DEFAULT_BUFFER_SIZE = 4 * 1024 * 1024 # 4MB
22
+ DEFAULT_RETRY_BACKOFF_MS = 250
23
+
24
+ # Cache settings
25
+ CACHE_TTL_SECONDS = 0.5
26
+
27
+ # Protocol-specific defaults
28
+ GRPC_DEFAULT_PORT = 9001
29
+ HTTP_DEFAULT_PORT = 8080
30
+
31
+ # Session settings
32
+ MAX_SESSION_NAME_LENGTH = 100
33
+ MAX_TOOL_NAME_LENGTH = 50
34
+ MAX_DESCRIPTION_LENGTH = 500
35
+
36
+ # Tool execution settings
37
+ MAX_TOOL_EXECUTION_TIME = 300 # 5 minutes
38
+ STREAM_CHUNK_SIZE = 1024
39
+ BACKPRESSURE_THRESHOLD = 0.8 # 80% of buffer size
40
+
41
+ # Error messages
42
+ ERROR_CONNECTION_FAILED = "Failed to connect to server"
43
+ ERROR_TOOL_NOT_FOUND = "Tool not found"
44
+ ERROR_SESSION_NOT_FOUND = "Session not found"
45
+ ERROR_INVALID_PARAMETERS = "Invalid parameters"
46
+ ERROR_TIMEOUT = "Operation timed out"
47
+ ERROR_STREAM_INTERRUPTED = "Stream was interrupted"
@@ -0,0 +1,310 @@
1
+ """Utility functions shared across Toolplane client implementations."""
2
+
3
+ import functools
4
+ import json
5
+ import re
6
+ import time
7
+ import uuid
8
+ from threading import RLock
9
+ from typing import Any, Callable, Dict, Optional, TypeVar
10
+
11
+ from .constants import (
12
+ CACHE_TTL_SECONDS,
13
+ MAX_DESCRIPTION_LENGTH,
14
+ MAX_SESSION_NAME_LENGTH,
15
+ MAX_TOOL_NAME_LENGTH,
16
+ )
17
+
18
+ T = TypeVar("T")
19
+
20
+
21
+ def generate_session_id() -> str:
22
+ """Generate a unique session ID."""
23
+ return str(uuid.uuid4())
24
+
25
+
26
+ def validate_tool_name(name: str) -> bool:
27
+ """Validate tool name format."""
28
+ if not name or len(name) > MAX_TOOL_NAME_LENGTH:
29
+ return False
30
+
31
+ # Tool names should be alphanumeric with underscores/hyphens
32
+ return re.match(r"^[a-zA-Z0-9_-]+$", name) is not None
33
+
34
+
35
+ def validate_session_name(name: str) -> bool:
36
+ """Validate session name format."""
37
+ if not name or len(name) > MAX_SESSION_NAME_LENGTH:
38
+ return False
39
+
40
+ # Session names allow more characters
41
+ return re.match(r"^[a-zA-Z0-9_\-\s\.]+$", name) is not None
42
+
43
+
44
+ def validate_description(description: str) -> bool:
45
+ """Validate description length."""
46
+ return len(description) <= MAX_DESCRIPTION_LENGTH
47
+
48
+
49
+ def parse_json_safe(json_str: str, default: Any = None) -> Any:
50
+ """Safely parse JSON string."""
51
+ try:
52
+ return json.loads(json_str)
53
+ except (json.JSONDecodeError, TypeError):
54
+ return default
55
+
56
+
57
+ def proto_enum_name(value: Any, enum_type: Any) -> str:
58
+ """Return the symbolic name for a v1 enum value.
59
+
60
+ The gRPC transport delivers proto3 enums as plain ints while the HTTP
61
+ transport delivers the symbolic name; normalize both to the name so
62
+ callers can map it through normalize_status_name.
63
+ """
64
+ if isinstance(value, str):
65
+ return value
66
+ try:
67
+ return enum_type.Name(value)
68
+ except (ValueError, AttributeError, TypeError):
69
+ return ""
70
+
71
+
72
+ def timestamp_to_iso(value: Any) -> str:
73
+ """Render a v1 Timestamp message as an RFC3339 string.
74
+
75
+ Tolerates strings (HTTP transport), None, and unset (epoch-zero)
76
+ timestamps so callers always get a plain str.
77
+ """
78
+ if value is None:
79
+ return ""
80
+ to_json = getattr(value, "ToJsonString", None)
81
+ if callable(to_json):
82
+ try:
83
+ if not getattr(value, "seconds", 1) and not getattr(value, "nanos", 1):
84
+ return ""
85
+ return to_json()
86
+ except Exception:
87
+ return ""
88
+ return value if isinstance(value, str) else str(value)
89
+
90
+
91
+ def format_error_message(error: Exception, context: str = "") -> str:
92
+ """Format error message with context."""
93
+ if context:
94
+ return f"{context}: {str(error)}"
95
+ return str(error)
96
+
97
+
98
+ def with_retry(
99
+ max_retries: int = 3, backoff_ms: int = 250, exceptions: tuple = (Exception,)
100
+ ) -> Callable:
101
+ """Decorator for retrying operations."""
102
+
103
+ def decorator(func: Callable[..., T]) -> Callable[..., T]:
104
+ @functools.wraps(func)
105
+ def wrapper(*args, **kwargs) -> T:
106
+ last_exception = None
107
+
108
+ for attempt in range(max_retries + 1):
109
+ try:
110
+ return func(*args, **kwargs)
111
+ except exceptions as e:
112
+ last_exception = e
113
+ if attempt < max_retries:
114
+ # Exponential backoff
115
+ sleep_time = (backoff_ms * (2**attempt)) / 1000
116
+ time.sleep(sleep_time)
117
+ else:
118
+ break
119
+
120
+ # If all retries failed, raise the last exception
121
+ raise last_exception
122
+
123
+ return wrapper
124
+
125
+ return decorator
126
+
127
+
128
+ class TTLCache:
129
+ """Thread-safe cache with TTL (Time To Live)."""
130
+
131
+ def __init__(self, ttl_seconds: int = CACHE_TTL_SECONDS):
132
+ self.ttl_seconds = ttl_seconds
133
+ self.cache: Dict[str, tuple] = {} # key -> (timestamp, value)
134
+ self.lock = RLock()
135
+
136
+ def get(self, key: str) -> Optional[Any]:
137
+ """Get value from cache if not expired."""
138
+ with self.lock:
139
+ if key in self.cache:
140
+ timestamp, value = self.cache[key]
141
+ if time.time() - timestamp < self.ttl_seconds:
142
+ return value
143
+ else:
144
+ # Remove expired entry
145
+ del self.cache[key]
146
+ return None
147
+
148
+ def set(self, key: str, value: Any) -> None:
149
+ """Set value in cache with current timestamp."""
150
+ with self.lock:
151
+ self.cache[key] = (time.time(), value)
152
+
153
+ def clear(self) -> None:
154
+ """Clear all cache entries."""
155
+ with self.lock:
156
+ self.cache.clear()
157
+
158
+ def cleanup_expired(self) -> None:
159
+ """Remove expired entries from cache."""
160
+ now = time.time()
161
+ with self.lock:
162
+ expired_keys = [
163
+ key
164
+ for key, (timestamp, _) in self.cache.items()
165
+ if now - timestamp >= self.ttl_seconds
166
+ ]
167
+ for key in expired_keys:
168
+ del self.cache[key]
169
+
170
+
171
+ def cache_with_ttl(ttl_seconds: int = CACHE_TTL_SECONDS) -> Callable:
172
+ """Decorator for caching function results with TTL."""
173
+
174
+ def decorator(func: Callable[..., T]) -> Callable[..., T]:
175
+ cache = TTLCache(ttl_seconds)
176
+
177
+ @functools.wraps(func)
178
+ def wrapper(*args, **kwargs) -> T:
179
+ # Create cache key from function name and arguments
180
+ key = f"{func.__name__}:{hash((args, tuple(sorted(kwargs.items()))))}"
181
+
182
+ # Try to get from cache first
183
+ cached_result = cache.get(key)
184
+ if cached_result is not None:
185
+ return cached_result
186
+
187
+ # If not in cache, execute function and cache result
188
+ result = func(*args, **kwargs)
189
+ cache.set(key, result)
190
+ return result
191
+
192
+ # Add cache management methods
193
+ wrapper.cache_clear = cache.clear
194
+ wrapper.cache_cleanup = cache.cleanup_expired
195
+
196
+ return wrapper
197
+
198
+ return decorator
199
+
200
+
201
+ def normalize_url(url: str, default_protocol: str = "http") -> str:
202
+ """Normalize URL by adding protocol if missing."""
203
+ if not url.startswith(("http://", "https://")):
204
+ url = f"{default_protocol}://{url}"
205
+ return url.rstrip("/")
206
+
207
+
208
+ def extract_host_port(url: str) -> tuple[str, int]:
209
+ """Extract host and port from URL."""
210
+ # Remove protocol if present
211
+ if "://" in url:
212
+ url = url.split("://")[1]
213
+
214
+ # Remove path if present
215
+ if "/" in url:
216
+ url = url.split("/")[0]
217
+
218
+ # Split host and port
219
+ if ":" in url:
220
+ host, port_str = url.rsplit(":", 1)
221
+ try:
222
+ port = int(port_str)
223
+ except ValueError:
224
+ port = 80 if "http" in url else 443
225
+ else:
226
+ host = url
227
+ port = 80 if "http" in url else 443
228
+
229
+ return host, port
230
+
231
+
232
+ def merge_dicts(base: Dict[str, Any], override: Dict[str, Any]) -> Dict[str, Any]:
233
+ """Merge two dictionaries, with override taking precedence."""
234
+ result = base.copy()
235
+ result.update(override)
236
+ return result
237
+
238
+
239
+ def sanitize_input(value: str, max_length: int = 1000) -> str:
240
+ """Sanitize input string by limiting length and removing control characters."""
241
+ if not isinstance(value, str):
242
+ value = str(value)
243
+
244
+ # Remove control characters except newlines and tabs
245
+ sanitized = re.sub(r"[\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\x9f]", "", value)
246
+
247
+ # Limit length
248
+ if len(sanitized) > max_length:
249
+ sanitized = sanitized[:max_length]
250
+
251
+ return sanitized
252
+
253
+
254
+ def is_valid_json(json_str: str) -> bool:
255
+ """Check if string is valid JSON."""
256
+ try:
257
+ json.loads(json_str)
258
+ return True
259
+ except (json.JSONDecodeError, TypeError):
260
+ return False
261
+
262
+
263
+ def deep_merge(dict1: Dict[str, Any], dict2: Dict[str, Any]) -> Dict[str, Any]:
264
+ """Deep merge two dictionaries."""
265
+ result = dict1.copy()
266
+
267
+ for key, value in dict2.items():
268
+ if key in result and isinstance(result[key], dict) and isinstance(value, dict):
269
+ result[key] = deep_merge(result[key], value)
270
+ else:
271
+ result[key] = value
272
+
273
+ return result
274
+
275
+
276
+ def timeout_wrapper(timeout_seconds: int) -> Callable:
277
+ """Decorator to add timeout to function execution."""
278
+
279
+ def decorator(func: Callable[..., T]) -> Callable[..., T]:
280
+ @functools.wraps(func)
281
+ def wrapper(*args, **kwargs) -> T:
282
+ import threading
283
+
284
+ result = [None]
285
+ exception = [None]
286
+
287
+ def target():
288
+ try:
289
+ result[0] = func(*args, **kwargs)
290
+ except Exception as e:
291
+ exception[0] = e
292
+
293
+ thread = threading.Thread(target=target)
294
+ thread.daemon = True
295
+ thread.start()
296
+ thread.join(timeout_seconds)
297
+
298
+ if thread.is_alive():
299
+ raise TimeoutError(
300
+ f"Function {func.__name__} timed out after {timeout_seconds} seconds"
301
+ )
302
+
303
+ if exception[0]:
304
+ raise exception[0]
305
+
306
+ return result[0]
307
+
308
+ return wrapper
309
+
310
+ return decorator
@@ -0,0 +1,67 @@
1
+ """Core modules for Toolplane client."""
2
+
3
+ from .config import ClientConfig
4
+ from .connection import ConnectionManager
5
+ from .errors import (
6
+ ConnectionError,
7
+ MachineError,
8
+ RequestError,
9
+ SessionError,
10
+ TaskError,
11
+ ToolError,
12
+ ToolplaneAlreadyExistsError,
13
+ ToolplaneAPIError,
14
+ ToolplaneCancelledError,
15
+ ToolplaneConnectionError,
16
+ ToolplaneError,
17
+ ToolplaneFailedPreconditionError,
18
+ ToolplaneInternalError,
19
+ ToolplaneInvalidArgumentError,
20
+ ToolplaneNotFoundError,
21
+ ToolplanePermissionDeniedError,
22
+ ToolplaneResourceExhaustedError,
23
+ ToolplaneTimeoutError,
24
+ ToolplaneUnauthenticatedError,
25
+ ToolplaneUnavailableError,
26
+ api_error_from_http_response,
27
+ api_error_from_rpc_error,
28
+ )
29
+ from .machine import MachineManager
30
+ from .request import RequestManager
31
+ from .session import SessionManager
32
+ from .session_context import SessionContext
33
+ from .task import TaskManager
34
+ from .tool import ToolManager
35
+
36
+ __all__ = [
37
+ "ConnectionManager",
38
+ "MachineManager",
39
+ "ToolManager",
40
+ "RequestManager",
41
+ "TaskManager",
42
+ "SessionManager",
43
+ "SessionContext",
44
+ "ToolplaneError",
45
+ "ConnectionError",
46
+ "ToolplaneConnectionError",
47
+ "ToolError",
48
+ "SessionError",
49
+ "MachineError",
50
+ "RequestError",
51
+ "TaskError",
52
+ "ClientConfig",
53
+ "ToolplaneAPIError",
54
+ "ToolplaneNotFoundError",
55
+ "ToolplaneInvalidArgumentError",
56
+ "ToolplaneFailedPreconditionError",
57
+ "ToolplaneResourceExhaustedError",
58
+ "ToolplaneUnauthenticatedError",
59
+ "ToolplanePermissionDeniedError",
60
+ "ToolplaneAlreadyExistsError",
61
+ "ToolplaneTimeoutError",
62
+ "ToolplaneUnavailableError",
63
+ "ToolplaneCancelledError",
64
+ "ToolplaneInternalError",
65
+ "api_error_from_rpc_error",
66
+ "api_error_from_http_response",
67
+ ]
@@ -0,0 +1,107 @@
1
+ """Configuration management for Toolplane gRPC client."""
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any, Dict, List, Optional
5
+
6
+ from ..common.base_config import BaseConfig
7
+ from ..common.constants import GRPC_DEFAULT_PORT
8
+
9
+
10
+ @dataclass
11
+ class ClientConfig(BaseConfig):
12
+ """Configuration for Toolplane gRPC client."""
13
+
14
+ server_host: str = "localhost"
15
+ server_port: int = GRPC_DEFAULT_PORT
16
+ use_tls: bool = False
17
+ tls_cert_path: Optional[str] = None
18
+ tls_key_path: Optional[str] = None
19
+ tls_ca_cert_path: Optional[str] = None
20
+ tls_server_name: Optional[str] = None
21
+ # Retry configuration parameters
22
+ max_retries: int = 3
23
+ retry_base_delay: float = 1.0
24
+ retry_max_delay: float = 60.0
25
+ retry_backoff_factor: float = 2.0
26
+
27
+ def normalize(self):
28
+ """Normalize gRPC-specific configuration values."""
29
+ # Normalize server host
30
+ if self.server_host.startswith("http://"):
31
+ self.server_host = self.server_host[7:]
32
+ elif self.server_host.startswith("https://"):
33
+ self.server_host = self.server_host[8:]
34
+ self.use_tls = True
35
+
36
+ # Extract port from host if present
37
+ if ":" in self.server_host:
38
+ host_parts = self.server_host.split(":")
39
+ if len(host_parts) == 2:
40
+ try:
41
+ self.server_port = int(host_parts[1])
42
+ self.server_host = host_parts[0]
43
+ except ValueError:
44
+ pass
45
+
46
+ # Ensure retry values are reasonable
47
+ if self.max_retries < 0:
48
+ self.max_retries = 3
49
+ if self.retry_base_delay <= 0:
50
+ self.retry_base_delay = 1.0
51
+ if self.retry_max_delay <= 0:
52
+ self.retry_max_delay = 60.0
53
+ if self.retry_backoff_factor <= 1.0:
54
+ self.retry_backoff_factor = 2.0
55
+
56
+ def get_auth_info(self) -> Dict[str, Any]:
57
+ """Get gRPC authentication information."""
58
+ return {
59
+ "metadata": self.get_metadata(),
60
+ "use_tls": self.use_tls,
61
+ "tls_cert_path": self.tls_cert_path,
62
+ "tls_key_path": self.tls_key_path,
63
+ "tls_ca_cert_path": self.tls_ca_cert_path,
64
+ "tls_server_name": self.tls_server_name,
65
+ }
66
+
67
+ def get_metadata(self) -> List[tuple]:
68
+ """Get gRPC metadata headers."""
69
+ metadata = []
70
+ if self.api_key:
71
+ metadata.append(("authorization", f"Bearer {self.api_key}"))
72
+ metadata.append(("api_key", self.api_key))
73
+ return metadata
74
+
75
+ def get_server_address(self) -> str:
76
+ """Get formatted server address."""
77
+ return f"{self.server_host}:{self.server_port}"
78
+
79
+ def copy(self) -> "ClientConfig":
80
+ """Create a copy of the configuration."""
81
+ return ClientConfig(**self.to_dict())
82
+
83
+ def merge(self, other: "ClientConfig") -> "ClientConfig":
84
+ """Merge this configuration with another."""
85
+ merged_dict = self.to_dict()
86
+ merged_dict.update(other.to_dict())
87
+ return ClientConfig(**merged_dict)
88
+
89
+ def to_dict(self) -> Dict[str, Any]:
90
+ """Convert configuration to dictionary."""
91
+ result = super().to_dict()
92
+ result.update(
93
+ {
94
+ "server_host": self.server_host,
95
+ "server_port": self.server_port,
96
+ "use_tls": self.use_tls,
97
+ "tls_cert_path": self.tls_cert_path,
98
+ "tls_key_path": self.tls_key_path,
99
+ "tls_ca_cert_path": self.tls_ca_cert_path,
100
+ "tls_server_name": self.tls_server_name,
101
+ "max_retries": self.max_retries,
102
+ "retry_base_delay": self.retry_base_delay,
103
+ "retry_max_delay": self.retry_max_delay,
104
+ "retry_backoff_factor": self.retry_backoff_factor,
105
+ }
106
+ )
107
+ return result