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,348 @@
1
+ """HTTP session management for Toolplane client."""
2
+
3
+ import threading
4
+ from typing import Any, Dict, List, Optional, Set
5
+
6
+ from ..common.utils import format_error_message, validate_session_name
7
+ from ..core.errors import SessionError
8
+ from .http_connection import HTTPConnectionManager
9
+
10
+
11
+ class HTTPSessionManager:
12
+ """Manages session lifecycle for HTTP client."""
13
+
14
+ def __init__(self, connection_manager: HTTPConnectionManager):
15
+ """Initialize HTTP session manager."""
16
+ self.connection_manager = connection_manager
17
+ self.sessions: Dict[str, "HTTPSessionContext"] = {}
18
+ self.sessions_lock = threading.RLock()
19
+ self._owned_sessions: Set[str] = set()
20
+
21
+ def _normalize_session(self, session: Dict[str, Any]) -> Dict[str, Any]:
22
+ created_by = session.get("createdBy", session.get("created_by", ""))
23
+ return {
24
+ "id": session.get("id", ""),
25
+ "name": session.get("name", ""),
26
+ "description": session.get("description", ""),
27
+ "namespace": session.get("namespace", ""),
28
+ "created_at": session.get("createdAt", session.get("created_at", "")),
29
+ "created_by": created_by,
30
+ "user_id": created_by,
31
+ "api_key": "",
32
+ "status": "active",
33
+ }
34
+
35
+ def _normalize_api_key(self, api_key: Dict[str, Any]) -> Dict[str, Any]:
36
+ capabilities = api_key.get("capabilities", [])
37
+ if not isinstance(capabilities, list):
38
+ capabilities = []
39
+
40
+ return {
41
+ "id": api_key.get("id", ""),
42
+ "name": api_key.get("name", ""),
43
+ "key": api_key.get("key", ""),
44
+ "key_preview": api_key.get("keyPreview", api_key.get("key_preview", "")),
45
+ "capabilities": capabilities,
46
+ "session_id": api_key.get("sessionId", api_key.get("session_id", "")),
47
+ "created_at": api_key.get("createdAt", api_key.get("created_at", "")),
48
+ "created_by": api_key.get("createdBy", api_key.get("created_by", "")),
49
+ "revoked_at": api_key.get("revokedAt", api_key.get("revoked_at", "")),
50
+ }
51
+
52
+ def create_session(
53
+ self,
54
+ session_id: Optional[str] = None,
55
+ user_id: Optional[str] = None,
56
+ name: Optional[str] = None,
57
+ description: Optional[str] = None,
58
+ namespace: Optional[str] = None,
59
+ api_key: Optional[str] = None,
60
+ ) -> str:
61
+ """Create a new session."""
62
+ try:
63
+ # Validate session name if provided
64
+ if name and not validate_session_name(name):
65
+ raise SessionError(f"Invalid session name: {name}")
66
+
67
+ self.connection_manager.ensure_connected()
68
+
69
+ payload = {
70
+ "userId": user_id or "",
71
+ "name": name or "",
72
+ "description": description or "",
73
+ }
74
+
75
+ if namespace:
76
+ payload["namespace"] = namespace
77
+
78
+ if session_id:
79
+ payload["sessionId"] = session_id
80
+
81
+ response = self.connection_manager.create_session(payload)
82
+
83
+ # Extract session ID from response
84
+ created_session_id = response.get("session", {}).get("id") or response.get(
85
+ "id"
86
+ )
87
+ if not created_session_id:
88
+ raise SessionError("No session ID returned from server")
89
+
90
+ with self.sessions_lock:
91
+ self._owned_sessions.add(created_session_id)
92
+
93
+ return created_session_id
94
+
95
+ except Exception as e:
96
+ error_msg = format_error_message(e, "Failed to create session")
97
+ raise SessionError(error_msg)
98
+
99
+ def get_session(self, session_id: str) -> Optional[Dict[str, Any]]:
100
+ """Get session information from server."""
101
+ try:
102
+ self.connection_manager.ensure_connected()
103
+
104
+ response = self.connection_manager.get_session(session_id)
105
+ session = response.get("session", response)
106
+ if not isinstance(session, dict):
107
+ return None
108
+
109
+ return self._normalize_session(session)
110
+
111
+ except Exception:
112
+ return None
113
+
114
+ def update_session(
115
+ self,
116
+ session_id: str,
117
+ name: Optional[str] = None,
118
+ description: Optional[str] = None,
119
+ namespace: Optional[str] = None,
120
+ ) -> Dict[str, Any]:
121
+ """Update session information."""
122
+ try:
123
+ self.connection_manager.ensure_connected()
124
+
125
+ payload = {
126
+ "sessionId": session_id,
127
+ "name": name or "",
128
+ "description": description or "",
129
+ }
130
+
131
+ if namespace:
132
+ payload["namespace"] = namespace
133
+
134
+ response = self.connection_manager.update_session(payload)
135
+ session = response.get("session", response)
136
+ if isinstance(session, dict):
137
+ return self._normalize_session(session)
138
+ return self.get_session(session_id) or {}
139
+
140
+ except Exception as e:
141
+ error_msg = format_error_message(e, "Failed to update session")
142
+ raise SessionError(error_msg)
143
+
144
+ def delete_session(self, session_id: str) -> bool:
145
+ """Delete a session."""
146
+ try:
147
+ self.connection_manager.ensure_connected()
148
+
149
+ self.connection_manager.delete_session(session_id)
150
+ with self.sessions_lock:
151
+ self.sessions.pop(session_id, None)
152
+ self._owned_sessions.discard(session_id)
153
+ return True
154
+
155
+ except Exception as e:
156
+ raise SessionError(f"Failed to delete session: {e}")
157
+
158
+ def list_sessions(self, user_id: Optional[str] = None) -> List[Dict]:
159
+ """List sessions for a user."""
160
+ try:
161
+ self.connection_manager.ensure_connected()
162
+
163
+ resolved_user_id = user_id or getattr(
164
+ self.connection_manager.config, "user_id", ""
165
+ )
166
+ if not resolved_user_id:
167
+ raise SessionError(
168
+ "user_id is required to list sessions. Pass user_id or set it in client config."
169
+ )
170
+
171
+ response = self.connection_manager.list_sessions(resolved_user_id)
172
+ sessions = response.get("sessions", [])
173
+ normalized_sessions = []
174
+ for session in sessions:
175
+ normalized_sessions.append(self._normalize_session(session))
176
+ return normalized_sessions
177
+
178
+ except Exception as e:
179
+ raise SessionError(f"Failed to list sessions: {e}")
180
+
181
+ # New methods for user session management
182
+ def list_user_sessions(
183
+ self, user_id: str, page_size: int = 10, page_token: str = "", filter: str = ""
184
+ ) -> Dict[str, Any]:
185
+ """List user sessions with pagination and filtering.
186
+
187
+ page_token is the opaque cursor from a previous page's response;
188
+ an empty string starts from the first page.
189
+ """
190
+ try:
191
+ self.connection_manager.ensure_connected()
192
+
193
+ payload = {
194
+ "userId": user_id,
195
+ "pageSize": page_size,
196
+ "pageToken": page_token or "",
197
+ "filter": filter,
198
+ }
199
+
200
+ response = self.connection_manager.list_user_sessions(payload)
201
+ page = response.get("page", {}) if isinstance(response, dict) else {}
202
+ sessions = (
203
+ response.get("sessions", []) if isinstance(response, dict) else []
204
+ )
205
+ return {
206
+ "sessions": [self._normalize_session(entry) for entry in sessions],
207
+ "total_count": int(page.get("totalSize", 0) or 0),
208
+ "next_page_token": page.get("nextPageToken", ""),
209
+ }
210
+
211
+ except Exception as e:
212
+ raise SessionError(f"Failed to list user sessions: {e}")
213
+
214
+ def bulk_delete_sessions(
215
+ self, user_id: str, session_ids: Optional[List[str]] = None, filter: str = ""
216
+ ) -> Dict[str, Any]:
217
+ """Bulk delete sessions for a user."""
218
+ try:
219
+ self.connection_manager.ensure_connected()
220
+
221
+ payload = {
222
+ "userId": user_id,
223
+ "sessionIds": session_ids or [],
224
+ "filter": filter,
225
+ }
226
+
227
+ response = self.connection_manager.bulk_delete_sessions(payload)
228
+ return response
229
+
230
+ except Exception as e:
231
+ raise SessionError(f"Failed to bulk delete sessions: {e}")
232
+
233
+ def get_session_stats(self, user_id: str) -> Dict[str, int]:
234
+ """Get session statistics for a user."""
235
+ try:
236
+ self.connection_manager.ensure_connected()
237
+
238
+ payload = {"userId": user_id}
239
+ response = self.connection_manager.get_session_stats(payload)
240
+ return response
241
+
242
+ except Exception as e:
243
+ raise SessionError(f"Failed to get session stats: {e}")
244
+
245
+ def invalidate_session(self, session_id: str, reason: str = "") -> bool:
246
+ """Invalidate a session."""
247
+ try:
248
+ self.connection_manager.ensure_connected()
249
+
250
+ payload = {"sessionId": session_id, "reason": reason}
251
+ response = self.connection_manager.invalidate_session(payload)
252
+ return response.get("success", False)
253
+
254
+ except Exception as e:
255
+ raise SessionError(f"Failed to invalidate session: {e}")
256
+
257
+ def create_api_key(
258
+ self,
259
+ session_id: str,
260
+ name: str,
261
+ capabilities: List[str],
262
+ ) -> Dict[str, Any]:
263
+ """Create a new API key for a session.
264
+
265
+ capabilities is required and must be non-empty: keys are minted
266
+ least-privilege and explicit (the server rejects empty capability
267
+ lists with INVALID_ARGUMENT).
268
+ """
269
+ if not capabilities or not any(str(c).strip() for c in capabilities):
270
+ raise SessionError(
271
+ "create_api_key requires an explicit non-empty capabilities list"
272
+ )
273
+ try:
274
+ self.connection_manager.ensure_connected()
275
+
276
+ payload = {
277
+ "sessionId": session_id,
278
+ "name": name,
279
+ "capabilities": list(capabilities),
280
+ }
281
+ response = self.connection_manager.create_api_key(payload)
282
+ return self._normalize_api_key(response)
283
+
284
+ except Exception as e:
285
+ raise SessionError(
286
+ f"Failed to create API key for session {session_id}: {e}"
287
+ )
288
+
289
+ def list_api_keys(self, session_id: str) -> List[Dict[str, Any]]:
290
+ """List active API keys for a session."""
291
+ try:
292
+ self.connection_manager.ensure_connected()
293
+
294
+ response = self.connection_manager.list_api_keys({"sessionId": session_id})
295
+ api_keys = response.get("apiKeys", response.get("api_keys", []))
296
+ return [self._normalize_api_key(api_key) for api_key in api_keys]
297
+
298
+ except Exception as e:
299
+ raise SessionError(f"Failed to list API keys for session {session_id}: {e}")
300
+
301
+ def revoke_api_key(self, session_id: str, key_id: str) -> bool:
302
+ """Revoke an API key for a session."""
303
+ try:
304
+ self.connection_manager.ensure_connected()
305
+
306
+ response = self.connection_manager.revoke_api_key(
307
+ {"sessionId": session_id, "keyId": key_id}
308
+ )
309
+ return bool(response.get("success", False))
310
+
311
+ except Exception as e:
312
+ raise SessionError(
313
+ f"Failed to revoke API key {key_id} for session {session_id}: {e}"
314
+ )
315
+
316
+ def register_session_context(self, session_id: str, context: "HTTPSessionContext"):
317
+ """Register a session context."""
318
+ with self.sessions_lock:
319
+ self.sessions[session_id] = context
320
+
321
+ def get_session_context(self, session_id: str) -> Optional["HTTPSessionContext"]:
322
+ """Get session context."""
323
+ with self.sessions_lock:
324
+ return self.sessions.get(session_id)
325
+
326
+ def list_session_contexts(self) -> List["HTTPSessionContext"]:
327
+ """List all session contexts."""
328
+ with self.sessions_lock:
329
+ return list(self.sessions.values())
330
+
331
+ def remove_session_context(self, session_id: str):
332
+ """Remove session context."""
333
+ with self.sessions_lock:
334
+ self.sessions.pop(session_id, None)
335
+ self._owned_sessions.discard(session_id)
336
+
337
+ def cleanup_all(self):
338
+ """Cleanup all sessions."""
339
+ with self.sessions_lock:
340
+ self.sessions.clear()
341
+ self._owned_sessions.clear()
342
+
343
+
344
+ # Forward declaration for type hinting
345
+ class HTTPSessionContext:
346
+ """HTTP session context placeholder for type hinting."""
347
+
348
+ pass