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,439 @@
|
|
|
1
|
+
"""Request management interface definitions."""
|
|
2
|
+
|
|
3
|
+
import uuid
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from enum import Enum
|
|
8
|
+
from typing import Any, Dict, List, Optional, Protocol, runtime_checkable
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RequestStatus(Enum):
|
|
12
|
+
"""Request status enumeration."""
|
|
13
|
+
|
|
14
|
+
PENDING = "pending"
|
|
15
|
+
RUNNING = "running"
|
|
16
|
+
DONE = "done"
|
|
17
|
+
FAILURE = "failure"
|
|
18
|
+
PROCESSING = "running"
|
|
19
|
+
COMPLETED = "done"
|
|
20
|
+
FAILED = "failure"
|
|
21
|
+
TIMEOUT = "timeout"
|
|
22
|
+
CANCELLED = "cancelled"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class RequestPriority(Enum):
|
|
26
|
+
"""Request priority levels."""
|
|
27
|
+
|
|
28
|
+
LOW = "low"
|
|
29
|
+
NORMAL = "normal"
|
|
30
|
+
HIGH = "high"
|
|
31
|
+
URGENT = "urgent"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class RequestInfo:
|
|
36
|
+
"""Information about a request."""
|
|
37
|
+
|
|
38
|
+
request_id: str
|
|
39
|
+
session_id: str
|
|
40
|
+
tool_name: str
|
|
41
|
+
parameters: Dict[str, Any]
|
|
42
|
+
status: RequestStatus
|
|
43
|
+
priority: RequestPriority = RequestPriority.NORMAL
|
|
44
|
+
created_at: datetime = None
|
|
45
|
+
started_at: Optional[datetime] = None
|
|
46
|
+
completed_at: Optional[datetime] = None
|
|
47
|
+
result: Any = None
|
|
48
|
+
error: Optional[str] = None
|
|
49
|
+
metadata: Dict[str, Any] = None
|
|
50
|
+
retry_count: int = 0
|
|
51
|
+
max_retries: int = 3
|
|
52
|
+
|
|
53
|
+
def __post_init__(self):
|
|
54
|
+
if self.created_at is None:
|
|
55
|
+
self.created_at = datetime.now()
|
|
56
|
+
if self.metadata is None:
|
|
57
|
+
self.metadata = {}
|
|
58
|
+
if not self.request_id:
|
|
59
|
+
self.request_id = str(uuid.uuid4())
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def execution_time(self) -> Optional[float]:
|
|
63
|
+
"""Get execution time in seconds."""
|
|
64
|
+
if self.started_at and self.completed_at:
|
|
65
|
+
return (self.completed_at - self.started_at).total_seconds()
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def wait_time(self) -> Optional[float]:
|
|
70
|
+
"""Get wait time in seconds."""
|
|
71
|
+
if self.started_at:
|
|
72
|
+
return (self.started_at - self.created_at).total_seconds()
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
76
|
+
"""Convert to dictionary."""
|
|
77
|
+
return {
|
|
78
|
+
"request_id": self.request_id,
|
|
79
|
+
"session_id": self.session_id,
|
|
80
|
+
"tool_name": self.tool_name,
|
|
81
|
+
"parameters": self.parameters,
|
|
82
|
+
"status": self.status.value,
|
|
83
|
+
"priority": self.priority.value,
|
|
84
|
+
"created_at": self.created_at.isoformat() if self.created_at else None,
|
|
85
|
+
"started_at": self.started_at.isoformat() if self.started_at else None,
|
|
86
|
+
"completed_at": (
|
|
87
|
+
self.completed_at.isoformat() if self.completed_at else None
|
|
88
|
+
),
|
|
89
|
+
"result": self.result,
|
|
90
|
+
"error": self.error,
|
|
91
|
+
"metadata": self.metadata,
|
|
92
|
+
"retry_count": self.retry_count,
|
|
93
|
+
"max_retries": self.max_retries,
|
|
94
|
+
"execution_time": self.execution_time,
|
|
95
|
+
"wait_time": self.wait_time,
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@runtime_checkable
|
|
100
|
+
class IRequestProcessor(Protocol):
|
|
101
|
+
"""Protocol interface for request processing."""
|
|
102
|
+
|
|
103
|
+
def process_request(self, request: RequestInfo) -> RequestInfo:
|
|
104
|
+
"""Process a single request."""
|
|
105
|
+
...
|
|
106
|
+
|
|
107
|
+
def can_process(self, request: RequestInfo) -> bool:
|
|
108
|
+
"""Check if processor can handle this request."""
|
|
109
|
+
...
|
|
110
|
+
|
|
111
|
+
def get_processing_capacity(self) -> int:
|
|
112
|
+
"""Get current processing capacity."""
|
|
113
|
+
...
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@runtime_checkable
|
|
117
|
+
class IRequestManager(Protocol):
|
|
118
|
+
"""Protocol interface for request management."""
|
|
119
|
+
|
|
120
|
+
def submit_request(
|
|
121
|
+
self,
|
|
122
|
+
session_id: str,
|
|
123
|
+
tool_name: str,
|
|
124
|
+
parameters: Dict[str, Any],
|
|
125
|
+
priority: RequestPriority = RequestPriority.NORMAL,
|
|
126
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
127
|
+
) -> str:
|
|
128
|
+
"""Submit a new request."""
|
|
129
|
+
...
|
|
130
|
+
|
|
131
|
+
def get_request_status(self, request_id: str) -> Optional[RequestInfo]:
|
|
132
|
+
"""Get request status."""
|
|
133
|
+
...
|
|
134
|
+
|
|
135
|
+
def cancel_request(self, request_id: str) -> bool:
|
|
136
|
+
"""Cancel a request."""
|
|
137
|
+
...
|
|
138
|
+
|
|
139
|
+
def list_requests(
|
|
140
|
+
self, session_id: Optional[str] = None, status: Optional[RequestStatus] = None
|
|
141
|
+
) -> List[RequestInfo]:
|
|
142
|
+
"""List requests with optional filtering."""
|
|
143
|
+
...
|
|
144
|
+
|
|
145
|
+
def start_polling(self, interval: float) -> None:
|
|
146
|
+
"""Start request polling."""
|
|
147
|
+
...
|
|
148
|
+
|
|
149
|
+
def stop_polling(self) -> None:
|
|
150
|
+
"""Stop request polling."""
|
|
151
|
+
...
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class IRequestQueue(ABC):
|
|
155
|
+
"""Abstract interface for request queuing."""
|
|
156
|
+
|
|
157
|
+
@abstractmethod
|
|
158
|
+
def enqueue(self, request: RequestInfo) -> None:
|
|
159
|
+
"""Add request to queue."""
|
|
160
|
+
pass
|
|
161
|
+
|
|
162
|
+
@abstractmethod
|
|
163
|
+
def dequeue(self) -> Optional[RequestInfo]:
|
|
164
|
+
"""Remove and return next request from queue."""
|
|
165
|
+
pass
|
|
166
|
+
|
|
167
|
+
@abstractmethod
|
|
168
|
+
def peek(self) -> Optional[RequestInfo]:
|
|
169
|
+
"""Look at next request without removing it."""
|
|
170
|
+
pass
|
|
171
|
+
|
|
172
|
+
@abstractmethod
|
|
173
|
+
def size(self) -> int:
|
|
174
|
+
"""Get queue size."""
|
|
175
|
+
pass
|
|
176
|
+
|
|
177
|
+
@abstractmethod
|
|
178
|
+
def is_empty(self) -> bool:
|
|
179
|
+
"""Check if queue is empty."""
|
|
180
|
+
pass
|
|
181
|
+
|
|
182
|
+
@abstractmethod
|
|
183
|
+
def clear(self) -> None:
|
|
184
|
+
"""Clear all requests from queue."""
|
|
185
|
+
pass
|
|
186
|
+
|
|
187
|
+
@abstractmethod
|
|
188
|
+
def remove_request(self, request_id: str) -> bool:
|
|
189
|
+
"""Remove specific request from queue."""
|
|
190
|
+
pass
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
class PriorityRequestQueue(IRequestQueue):
|
|
194
|
+
"""Priority-based request queue implementation."""
|
|
195
|
+
|
|
196
|
+
def __init__(self):
|
|
197
|
+
import heapq
|
|
198
|
+
|
|
199
|
+
self._queue = []
|
|
200
|
+
self._entry_finder = {} # Maps request_id to entry
|
|
201
|
+
self._counter = 0 # Unique sequence count
|
|
202
|
+
self.heapq = heapq
|
|
203
|
+
|
|
204
|
+
def enqueue(self, request: RequestInfo) -> None:
|
|
205
|
+
"""Add request to priority queue."""
|
|
206
|
+
# Lower number = higher priority
|
|
207
|
+
priority_map = {
|
|
208
|
+
RequestPriority.URGENT: 0,
|
|
209
|
+
RequestPriority.HIGH: 1,
|
|
210
|
+
RequestPriority.NORMAL: 2,
|
|
211
|
+
RequestPriority.LOW: 3,
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
priority = priority_map.get(request.priority, 2)
|
|
215
|
+
|
|
216
|
+
# Remove existing entry if updating
|
|
217
|
+
if request.request_id in self._entry_finder:
|
|
218
|
+
self.remove_request(request.request_id)
|
|
219
|
+
|
|
220
|
+
count = self._counter
|
|
221
|
+
self._counter += 1
|
|
222
|
+
|
|
223
|
+
# Entry format: [priority, count, request]
|
|
224
|
+
entry = [priority, count, request]
|
|
225
|
+
self._entry_finder[request.request_id] = entry
|
|
226
|
+
self.heapq.heappush(self._queue, entry)
|
|
227
|
+
|
|
228
|
+
def dequeue(self) -> Optional[RequestInfo]:
|
|
229
|
+
"""Remove and return highest priority request."""
|
|
230
|
+
while self._queue:
|
|
231
|
+
priority, count, request = self.heapq.heappop(self._queue)
|
|
232
|
+
|
|
233
|
+
# Check if this entry was marked as removed
|
|
234
|
+
if request.request_id not in self._entry_finder:
|
|
235
|
+
continue
|
|
236
|
+
|
|
237
|
+
del self._entry_finder[request.request_id]
|
|
238
|
+
return request
|
|
239
|
+
|
|
240
|
+
return None
|
|
241
|
+
|
|
242
|
+
def peek(self) -> Optional[RequestInfo]:
|
|
243
|
+
"""Look at highest priority request without removing it."""
|
|
244
|
+
while self._queue:
|
|
245
|
+
priority, count, request = self._queue[0]
|
|
246
|
+
|
|
247
|
+
# Check if this entry was marked as removed
|
|
248
|
+
if request.request_id not in self._entry_finder:
|
|
249
|
+
self.heapq.heappop(self._queue)
|
|
250
|
+
continue
|
|
251
|
+
|
|
252
|
+
return request
|
|
253
|
+
|
|
254
|
+
return None
|
|
255
|
+
|
|
256
|
+
def size(self) -> int:
|
|
257
|
+
"""Get queue size."""
|
|
258
|
+
return len(self._entry_finder)
|
|
259
|
+
|
|
260
|
+
def is_empty(self) -> bool:
|
|
261
|
+
"""Check if queue is empty."""
|
|
262
|
+
return len(self._entry_finder) == 0
|
|
263
|
+
|
|
264
|
+
def clear(self) -> None:
|
|
265
|
+
"""Clear all requests from queue."""
|
|
266
|
+
self._queue.clear()
|
|
267
|
+
self._entry_finder.clear()
|
|
268
|
+
self._counter = 0
|
|
269
|
+
|
|
270
|
+
def remove_request(self, request_id: str) -> bool:
|
|
271
|
+
"""Remove specific request from queue."""
|
|
272
|
+
if request_id in self._entry_finder:
|
|
273
|
+
self._entry_finder.pop(request_id)
|
|
274
|
+
# Mark as removed by clearing the request_id
|
|
275
|
+
# The actual removal happens during dequeue
|
|
276
|
+
return True
|
|
277
|
+
return False
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
class FIFORequestQueue(IRequestQueue):
|
|
281
|
+
"""First-in-first-out request queue implementation."""
|
|
282
|
+
|
|
283
|
+
def __init__(self):
|
|
284
|
+
from collections import deque
|
|
285
|
+
|
|
286
|
+
self._queue = deque()
|
|
287
|
+
self._request_ids = set()
|
|
288
|
+
|
|
289
|
+
def enqueue(self, request: RequestInfo) -> None:
|
|
290
|
+
"""Add request to end of queue."""
|
|
291
|
+
if request.request_id not in self._request_ids:
|
|
292
|
+
self._queue.append(request)
|
|
293
|
+
self._request_ids.add(request.request_id)
|
|
294
|
+
|
|
295
|
+
def dequeue(self) -> Optional[RequestInfo]:
|
|
296
|
+
"""Remove and return first request from queue."""
|
|
297
|
+
if self._queue:
|
|
298
|
+
request = self._queue.popleft()
|
|
299
|
+
self._request_ids.remove(request.request_id)
|
|
300
|
+
return request
|
|
301
|
+
return None
|
|
302
|
+
|
|
303
|
+
def peek(self) -> Optional[RequestInfo]:
|
|
304
|
+
"""Look at first request without removing it."""
|
|
305
|
+
return self._queue[0] if self._queue else None
|
|
306
|
+
|
|
307
|
+
def size(self) -> int:
|
|
308
|
+
"""Get queue size."""
|
|
309
|
+
return len(self._queue)
|
|
310
|
+
|
|
311
|
+
def is_empty(self) -> bool:
|
|
312
|
+
"""Check if queue is empty."""
|
|
313
|
+
return len(self._queue) == 0
|
|
314
|
+
|
|
315
|
+
def clear(self) -> None:
|
|
316
|
+
"""Clear all requests from queue."""
|
|
317
|
+
self._queue.clear()
|
|
318
|
+
self._request_ids.clear()
|
|
319
|
+
|
|
320
|
+
def remove_request(self, request_id: str) -> bool:
|
|
321
|
+
"""Remove specific request from queue."""
|
|
322
|
+
if request_id in self._request_ids:
|
|
323
|
+
# Find and remove the request
|
|
324
|
+
for i, request in enumerate(self._queue):
|
|
325
|
+
if request.request_id == request_id:
|
|
326
|
+
del self._queue[i]
|
|
327
|
+
self._request_ids.remove(request_id)
|
|
328
|
+
return True
|
|
329
|
+
return False
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
class RequestQueueFactory:
|
|
333
|
+
"""Factory for creating request queues."""
|
|
334
|
+
|
|
335
|
+
_queue_types = {
|
|
336
|
+
"priority": PriorityRequestQueue,
|
|
337
|
+
"fifo": FIFORequestQueue,
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
@classmethod
|
|
341
|
+
def create_queue(cls, queue_type: str = "priority") -> IRequestQueue:
|
|
342
|
+
"""Create a request queue of specified type."""
|
|
343
|
+
if queue_type not in cls._queue_types:
|
|
344
|
+
raise ValueError(f"Unknown queue type: {queue_type}")
|
|
345
|
+
|
|
346
|
+
queue_class = cls._queue_types[queue_type]
|
|
347
|
+
return queue_class()
|
|
348
|
+
|
|
349
|
+
@classmethod
|
|
350
|
+
def register_queue_type(cls, name: str, queue_class: type) -> None:
|
|
351
|
+
"""Register a new queue type."""
|
|
352
|
+
cls._queue_types[name] = queue_class
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
class RequestMetrics:
|
|
356
|
+
"""Metrics collection for request processing."""
|
|
357
|
+
|
|
358
|
+
def __init__(self):
|
|
359
|
+
self.reset()
|
|
360
|
+
|
|
361
|
+
def reset(self) -> None:
|
|
362
|
+
"""Reset all metrics."""
|
|
363
|
+
self.total_requests = 0
|
|
364
|
+
self.completed_requests = 0
|
|
365
|
+
self.failed_requests = 0
|
|
366
|
+
self.cancelled_requests = 0
|
|
367
|
+
self.timeout_requests = 0
|
|
368
|
+
self.total_execution_time = 0.0
|
|
369
|
+
self.total_wait_time = 0.0
|
|
370
|
+
self.requests_by_tool = {}
|
|
371
|
+
self.requests_by_session = {}
|
|
372
|
+
self.requests_by_priority = {p: 0 for p in RequestPriority}
|
|
373
|
+
|
|
374
|
+
def record_request(self, request: RequestInfo) -> None:
|
|
375
|
+
"""Record request metrics."""
|
|
376
|
+
self.total_requests += 1
|
|
377
|
+
|
|
378
|
+
# Count by status
|
|
379
|
+
if request.status == RequestStatus.COMPLETED:
|
|
380
|
+
self.completed_requests += 1
|
|
381
|
+
elif request.status == RequestStatus.FAILED:
|
|
382
|
+
self.failed_requests += 1
|
|
383
|
+
elif request.status == RequestStatus.CANCELLED:
|
|
384
|
+
self.cancelled_requests += 1
|
|
385
|
+
elif request.status == RequestStatus.TIMEOUT:
|
|
386
|
+
self.timeout_requests += 1
|
|
387
|
+
|
|
388
|
+
# Record timing
|
|
389
|
+
if request.execution_time:
|
|
390
|
+
self.total_execution_time += request.execution_time
|
|
391
|
+
if request.wait_time:
|
|
392
|
+
self.total_wait_time += request.wait_time
|
|
393
|
+
|
|
394
|
+
# Count by tool
|
|
395
|
+
tool_name = request.tool_name
|
|
396
|
+
self.requests_by_tool[tool_name] = self.requests_by_tool.get(tool_name, 0) + 1
|
|
397
|
+
|
|
398
|
+
# Count by session
|
|
399
|
+
session_id = request.session_id
|
|
400
|
+
self.requests_by_session[session_id] = (
|
|
401
|
+
self.requests_by_session.get(session_id, 0) + 1
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
# Count by priority
|
|
405
|
+
self.requests_by_priority[request.priority] += 1
|
|
406
|
+
|
|
407
|
+
def get_metrics(self) -> Dict[str, Any]:
|
|
408
|
+
"""Get all metrics."""
|
|
409
|
+
success_rate = (
|
|
410
|
+
self.completed_requests / self.total_requests
|
|
411
|
+
if self.total_requests > 0
|
|
412
|
+
else 0
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
avg_execution_time = (
|
|
416
|
+
self.total_execution_time / self.completed_requests
|
|
417
|
+
if self.completed_requests > 0
|
|
418
|
+
else 0
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
avg_wait_time = (
|
|
422
|
+
self.total_wait_time / self.total_requests if self.total_requests > 0 else 0
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
return {
|
|
426
|
+
"total_requests": self.total_requests,
|
|
427
|
+
"completed_requests": self.completed_requests,
|
|
428
|
+
"failed_requests": self.failed_requests,
|
|
429
|
+
"cancelled_requests": self.cancelled_requests,
|
|
430
|
+
"timeout_requests": self.timeout_requests,
|
|
431
|
+
"success_rate": success_rate,
|
|
432
|
+
"avg_execution_time": avg_execution_time,
|
|
433
|
+
"avg_wait_time": avg_wait_time,
|
|
434
|
+
"requests_by_tool": self.requests_by_tool.copy(),
|
|
435
|
+
"requests_by_session": self.requests_by_session.copy(),
|
|
436
|
+
"requests_by_priority": {
|
|
437
|
+
p.value: count for p, count in self.requests_by_priority.items()
|
|
438
|
+
},
|
|
439
|
+
}
|