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,332 @@
1
+ """Session management for Toolplane gRPC client."""
2
+
3
+ import threading
4
+ from typing import Any, Dict, List, Optional
5
+
6
+ from toolplane.proto.service_pb2 import (
7
+ BulkDeleteSessionsRequest,
8
+ CreateApiKeyRequest,
9
+ CreateSessionRequest,
10
+ DeleteSessionRequest,
11
+ GetSessionRequest,
12
+ GetSessionStatsRequest,
13
+ InvalidateSessionRequest,
14
+ ListApiKeysRequest,
15
+ ListSessionsRequest,
16
+ ListUserSessionsRequest,
17
+ RevokeApiKeyRequest,
18
+ UpdateSessionRequest,
19
+ )
20
+
21
+ from ..common.base_session_manager import BaseSessionManager
22
+ from ..common.utils import timestamp_to_iso
23
+ from .connection import ConnectionManager
24
+ from .errors import SessionError
25
+
26
+
27
+ class SessionManager(BaseSessionManager):
28
+ """Manages session lifecycle for gRPC client."""
29
+
30
+ def __init__(self, connection_manager: ConnectionManager):
31
+ """Initialize session manager."""
32
+ super().__init__(connection_manager)
33
+ self.session_contexts: Dict[str, Any] = {}
34
+ self.session_contexts_lock = threading.RLock()
35
+
36
+ def _normalize_session(self, session: Any) -> Dict[str, Any]:
37
+ created_by = getattr(session, "created_by", "")
38
+ return {
39
+ "id": session.id,
40
+ "name": session.name,
41
+ "description": session.description,
42
+ "namespace": session.namespace,
43
+ "created_at": timestamp_to_iso(session.created_at),
44
+ "created_by": created_by,
45
+ "user_id": created_by,
46
+ "api_key": "",
47
+ "status": "active",
48
+ }
49
+
50
+ def _normalize_api_key(self, api_key: Any) -> Dict[str, Any]:
51
+ return {
52
+ "id": api_key.id,
53
+ "name": api_key.name,
54
+ "key": api_key.key,
55
+ "key_preview": getattr(api_key, "key_preview", ""),
56
+ "capabilities": list(getattr(api_key, "capabilities", [])),
57
+ "session_id": api_key.session_id,
58
+ "created_at": timestamp_to_iso(api_key.created_at),
59
+ "created_by": api_key.created_by,
60
+ "revoked_at": timestamp_to_iso(getattr(api_key, "revoked_at", None)),
61
+ }
62
+
63
+ def _create_session_on_server(
64
+ self,
65
+ session_id: str,
66
+ user_id: Optional[str],
67
+ name: Optional[str],
68
+ description: Optional[str],
69
+ namespace: Optional[str],
70
+ api_key: Optional[str],
71
+ ) -> str:
72
+ """Create session on gRPC server."""
73
+ request = CreateSessionRequest(
74
+ user_id=user_id or "",
75
+ name=name or "",
76
+ description=description or "",
77
+ session_id=session_id or "",
78
+ namespace=namespace or "",
79
+ )
80
+
81
+ response = self.connection_manager.session_stub.CreateSession(
82
+ request, metadata=self.connection_manager.get_metadata()
83
+ )
84
+
85
+ return response.session.id
86
+
87
+ def _get_session_from_server(self, session_id: str) -> Optional[Dict[str, Any]]:
88
+ """Get session from gRPC server."""
89
+ request = GetSessionRequest(session_id=session_id)
90
+
91
+ try:
92
+ response = self.connection_manager.session_stub.GetSession(
93
+ request, metadata=self.connection_manager.get_metadata()
94
+ )
95
+
96
+ return self._normalize_session(response)
97
+
98
+ except Exception:
99
+ return None
100
+
101
+ def _delete_session_on_server(self, session_id: str):
102
+ """Delete session on gRPC server."""
103
+ request = DeleteSessionRequest(session_id=session_id)
104
+ self.connection_manager.session_stub.DeleteSession(
105
+ request, metadata=self.connection_manager.get_metadata()
106
+ )
107
+
108
+ def _list_sessions_on_server(self) -> List[Dict[str, Any]]:
109
+ """List sessions on gRPC server."""
110
+ user_id = getattr(self.connection_manager.config, "user_id", "") or ""
111
+ request = ListSessionsRequest(user_id=user_id)
112
+
113
+ response = self.connection_manager.session_stub.ListSessions(
114
+ request, metadata=self.connection_manager.get_metadata()
115
+ )
116
+
117
+ sessions = []
118
+ for session in response.sessions:
119
+ sessions.append(self._normalize_session(session))
120
+
121
+ return sessions
122
+
123
+ def _update_session_on_server(
124
+ self,
125
+ session_id: str,
126
+ name: Optional[str],
127
+ description: Optional[str],
128
+ namespace: Optional[str],
129
+ ) -> Dict[str, Any]:
130
+ """Update session on gRPC server."""
131
+ request = UpdateSessionRequest(
132
+ session_id=session_id,
133
+ name=name or "",
134
+ description=description or "",
135
+ namespace=namespace or "",
136
+ )
137
+
138
+ response = self.connection_manager.session_stub.UpdateSession(
139
+ request, metadata=self.connection_manager.get_metadata()
140
+ )
141
+
142
+ return self._normalize_session(response)
143
+
144
+ def create_api_key(
145
+ self,
146
+ session_id: str,
147
+ name: str,
148
+ capabilities: List[str],
149
+ ) -> Dict[str, Any]:
150
+ """Create a new API key for a session.
151
+
152
+ capabilities is required and must be non-empty: keys are minted
153
+ least-privilege and explicit (the server rejects empty capability
154
+ lists with INVALID_ARGUMENT).
155
+ """
156
+ if not capabilities or not any(str(c).strip() for c in capabilities):
157
+ raise SessionError(
158
+ "create_api_key requires an explicit non-empty capabilities list"
159
+ )
160
+ try:
161
+ self.connection_manager.ensure_connected()
162
+
163
+ request = CreateApiKeyRequest(
164
+ session_id=session_id,
165
+ name=name,
166
+ capabilities=list(capabilities),
167
+ )
168
+ response = self.connection_manager.session_stub.CreateApiKey(
169
+ request, metadata=self.connection_manager.get_metadata()
170
+ )
171
+ return self._normalize_api_key(response)
172
+
173
+ except Exception as e:
174
+ raise SessionError(
175
+ f"Failed to create API key for session {session_id}: {e}"
176
+ )
177
+
178
+ def list_api_keys(self, session_id: str) -> List[Dict[str, Any]]:
179
+ """List active API keys for a session."""
180
+ try:
181
+ self.connection_manager.ensure_connected()
182
+
183
+ request = ListApiKeysRequest(session_id=session_id)
184
+ response = self.connection_manager.session_stub.ListApiKeys(
185
+ request, metadata=self.connection_manager.get_metadata()
186
+ )
187
+ return [self._normalize_api_key(api_key) for api_key in response.api_keys]
188
+
189
+ except Exception as e:
190
+ raise SessionError(f"Failed to list API keys for session {session_id}: {e}")
191
+
192
+ def revoke_api_key(self, session_id: str, key_id: str) -> bool:
193
+ """Revoke an API key for a session."""
194
+ try:
195
+ self.connection_manager.ensure_connected()
196
+
197
+ request = RevokeApiKeyRequest(session_id=session_id, key_id=key_id)
198
+ response = self.connection_manager.session_stub.RevokeApiKey(
199
+ request, metadata=self.connection_manager.get_metadata()
200
+ )
201
+ return response.success
202
+
203
+ except Exception as e:
204
+ raise SessionError(
205
+ f"Failed to revoke API key {key_id} for session {session_id}: {e}"
206
+ )
207
+
208
+ # New methods for user session management
209
+ def list_user_sessions(
210
+ self, user_id: str, page_size: int = 10, page_token: str = "", filter: str = ""
211
+ ) -> Dict[str, Any]:
212
+ """List user sessions with pagination and filtering.
213
+
214
+ page_token is the opaque cursor from a previous page's response;
215
+ an empty string starts from the first page.
216
+ """
217
+ try:
218
+ self.connection_manager.ensure_connected()
219
+
220
+ request = ListUserSessionsRequest(
221
+ user_id=user_id,
222
+ page_size=page_size,
223
+ page_token=page_token or "",
224
+ filter=filter,
225
+ )
226
+
227
+ response = self.connection_manager.session_stub.ListUserSessions(
228
+ request, metadata=self.connection_manager.get_metadata()
229
+ )
230
+
231
+ sessions = []
232
+ for session in response.sessions:
233
+ sessions.append(self._normalize_session(session))
234
+
235
+ return {
236
+ "sessions": sessions,
237
+ "total_count": response.page.total_size,
238
+ "next_page_token": response.page.next_page_token,
239
+ }
240
+
241
+ except Exception as e:
242
+ raise SessionError(f"Failed to list user sessions: {e}")
243
+
244
+ def bulk_delete_sessions(
245
+ self, user_id: str, session_ids: Optional[List[str]] = None, filter: str = ""
246
+ ) -> Dict[str, Any]:
247
+ """Bulk delete sessions for a user."""
248
+ try:
249
+ self.connection_manager.ensure_connected()
250
+
251
+ request = BulkDeleteSessionsRequest(
252
+ user_id=user_id, session_ids=session_ids or [], filter=filter
253
+ )
254
+
255
+ response = self.connection_manager.session_stub.BulkDeleteSessions(
256
+ request, metadata=self.connection_manager.get_metadata()
257
+ )
258
+
259
+ return {
260
+ "deleted_count": response.deleted_count,
261
+ "failed_deletions": response.failed_deletions,
262
+ }
263
+
264
+ except Exception as e:
265
+ raise SessionError(f"Failed to bulk delete sessions: {e}")
266
+
267
+ def get_session_stats(self, user_id: str) -> Dict[str, int]:
268
+ """Get session statistics for a user."""
269
+ try:
270
+ self.connection_manager.ensure_connected()
271
+
272
+ request = GetSessionStatsRequest(user_id=user_id)
273
+
274
+ response = self.connection_manager.session_stub.GetSessionStats(
275
+ request, metadata=self.connection_manager.get_metadata()
276
+ )
277
+
278
+ return {
279
+ "total_sessions": response.total_sessions,
280
+ "active_sessions": response.active_sessions,
281
+ "expired_sessions": response.expired_sessions,
282
+ }
283
+
284
+ except Exception as e:
285
+ raise SessionError(f"Failed to get session stats: {e}")
286
+
287
+ def invalidate_session(self, session_id: str, reason: str = "") -> bool:
288
+ """Invalidate a session."""
289
+ try:
290
+ self.connection_manager.ensure_connected()
291
+
292
+ request = InvalidateSessionRequest(session_id=session_id, reason=reason)
293
+
294
+ response = self.connection_manager.session_stub.InvalidateSession(
295
+ request, metadata=self.connection_manager.get_metadata()
296
+ )
297
+
298
+ return response.success
299
+
300
+ except Exception as e:
301
+ raise SessionError(f"Failed to invalidate session: {e}")
302
+
303
+ def register_session_context(self, session_id: str, context: Any):
304
+ """Register a session context."""
305
+ with self.session_contexts_lock:
306
+ self.session_contexts[session_id] = context
307
+
308
+ def get_session_context(self, session_id: str) -> Optional[Any]:
309
+ """Get session context."""
310
+ with self.session_contexts_lock:
311
+ return self.session_contexts.get(session_id)
312
+
313
+ def list_session_contexts(self) -> List[Any]:
314
+ """List all session contexts."""
315
+ with self.session_contexts_lock:
316
+ return list(self.session_contexts.values())
317
+
318
+ def cleanup_session_context(self, session_id: str):
319
+ """Cleanup session context."""
320
+ with self.session_contexts_lock:
321
+ context = self.session_contexts.pop(session_id, None)
322
+ if context and hasattr(context, "cleanup"):
323
+ context.cleanup()
324
+
325
+ def cleanup_all(self):
326
+ """Cleanup all sessions and contexts."""
327
+ with self.session_contexts_lock:
328
+ session_ids = list(self.session_contexts.keys())
329
+ for session_id in session_ids:
330
+ self.cleanup_session_context(session_id)
331
+
332
+ super().cleanup_all()