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.
- toolplane/__init__.py +106 -0
- toolplane/common/__init__.py +93 -0
- toolplane/common/base_config.py +129 -0
- toolplane/common/base_connection_manager.py +171 -0
- toolplane/common/base_session_manager.py +321 -0
- toolplane/common/base_tool_manager.py +347 -0
- toolplane/common/constants.py +47 -0
- toolplane/common/utils.py +310 -0
- toolplane/core/__init__.py +67 -0
- toolplane/core/config.py +107 -0
- toolplane/core/connection.py +285 -0
- toolplane/core/errors.py +298 -0
- toolplane/core/machine.py +480 -0
- toolplane/core/request.py +775 -0
- toolplane/core/session.py +332 -0
- toolplane/core/session_context.py +514 -0
- toolplane/core/task.py +130 -0
- toolplane/core/tool.py +329 -0
- toolplane/http_core/__init__.py +37 -0
- toolplane/http_core/http_config.py +97 -0
- toolplane/http_core/http_connection.py +409 -0
- toolplane/http_core/http_machine.py +298 -0
- toolplane/http_core/http_request.py +748 -0
- toolplane/http_core/http_session.py +348 -0
- toolplane/http_core/http_session_context.py +491 -0
- toolplane/http_core/http_task.py +101 -0
- toolplane/http_core/http_tool.py +400 -0
- toolplane/interfaces/__init__.py +27 -0
- toolplane/interfaces/client_interface.py +122 -0
- toolplane/interfaces/connection_interface.py +193 -0
- toolplane/interfaces/event_interface.py +290 -0
- toolplane/interfaces/request_interface.py +439 -0
- toolplane/interfaces/session_interface.py +288 -0
- toolplane/interfaces/tool_interface.py +441 -0
- toolplane/proto/__init__.py +0 -0
- toolplane/proto/service_pb2.py +315 -0
- toolplane/proto/service_pb2_grpc.py +2240 -0
- toolplane/provider_cli.py +268 -0
- toolplane/provider_registry.py +77 -0
- toolplane/provider_runtime.py +302 -0
- toolplane/toolkits/__init__.py +0 -0
- toolplane/toolkits/standalone_tools/__init__.py +0 -0
- toolplane/toolkits/standalone_tools/create_directory.py +94 -0
- toolplane/toolkits/standalone_tools/create_file.py +124 -0
- toolplane/toolkits/standalone_tools/file_search.py +229 -0
- toolplane/toolkits/standalone_tools/grep_search.py +372 -0
- toolplane/toolkits/standalone_tools/launcher.py +146 -0
- toolplane/toolkits/standalone_tools/list_dir.py +395 -0
- toolplane/toolkits/standalone_tools/read_file.py +346 -0
- toolplane/toolkits/standalone_tools/replace_string_in_file.py +407 -0
- toolplane/toolkits/standalone_tools/run_tests.py +66 -0
- toolplane/toolkits/standalone_tools/semantic_search.py +485 -0
- toolplane/toolkits/standalone_tools/standalone_toolkit.py +979 -0
- toolplane/toolkits/standalone_tools/test_failure_analysis.py +618 -0
- toolplane/toolkits/standalone_tools/test_standalone_toolkit.py +517 -0
- toolplane/toolkits/swe/__init__.py +35 -0
- toolplane/toolkits/swe/create_directory.py +15 -0
- toolplane/toolkits/swe/create_file.py +15 -0
- toolplane/toolkits/swe/descriptions.py +273 -0
- toolplane/toolkits/swe/execute_bash.py +93 -0
- toolplane/toolkits/swe/file_editor.py +775 -0
- toolplane/toolkits/swe/file_search.py +16 -0
- toolplane/toolkits/swe/finish.py +50 -0
- toolplane/toolkits/swe/grep_search.py +19 -0
- toolplane/toolkits/swe/list_dir.py +407 -0
- toolplane/toolkits/swe/read_file.py +18 -0
- toolplane/toolkits/swe/replace_string_in_file.py +17 -0
- toolplane/toolkits/swe/search.py +260 -0
- toolplane/toolkits/swe/semantic_search.py +20 -0
- toolplane/toolkits/swe/str_replace_editor.py +647 -0
- toolplane/toolkits/swe/submit.py +29 -0
- toolplane/toolkits/swe/swe_toolkit.py +1296 -0
- toolplane/toolplane_client.py +686 -0
- toolplane/toolplane_http_client.py +681 -0
- toolplane/utils/__init__.py +3 -0
- toolplane/utils/schema.py +146 -0
- toolplane_python_client-0.1.0.dist-info/METADATA +543 -0
- toolplane_python_client-0.1.0.dist-info/RECORD +81 -0
- toolplane_python_client-0.1.0.dist-info/WHEEL +5 -0
- toolplane_python_client-0.1.0.dist-info/entry_points.txt +2 -0
- 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
|