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,441 @@
1
+ """Tool management interface definitions."""
2
+
3
+ import inspect
4
+ from abc import ABC, abstractmethod
5
+ from dataclasses import dataclass
6
+ from enum import Enum
7
+ from typing import (
8
+ Any,
9
+ Callable,
10
+ Dict,
11
+ Iterator,
12
+ List,
13
+ Optional,
14
+ Protocol,
15
+ Tuple,
16
+ runtime_checkable,
17
+ )
18
+
19
+
20
+ class ToolExecutionStatus(Enum):
21
+ """Tool execution status."""
22
+
23
+ PENDING = "pending"
24
+ RUNNING = "running"
25
+ DONE = "done"
26
+ FAILURE = "failure"
27
+ COMPLETED = "done"
28
+ FAILED = "failure"
29
+ TIMEOUT = "timeout"
30
+ CANCELLED = "cancelled"
31
+
32
+
33
+ @dataclass
34
+ class ToolDefinition:
35
+ """Tool definition with metadata."""
36
+
37
+ name: str
38
+ func: Callable
39
+ schema: Optional[Dict[str, Any]] = None
40
+ description: Optional[str] = None
41
+ stream: bool = False
42
+ tags: List[str] = None
43
+ timeout: Optional[int] = None
44
+
45
+ def __post_init__(self):
46
+ if self.tags is None:
47
+ self.tags = []
48
+
49
+ # Auto-generate description if not provided
50
+ if not self.description and self.func.__doc__:
51
+ self.description = self.func.__doc__.strip()
52
+
53
+ # Auto-generate schema if not provided
54
+ if not self.schema:
55
+ self.schema = self._generate_schema()
56
+
57
+ def _generate_schema(self) -> Dict[str, Any]:
58
+ """Generate schema from function signature."""
59
+ try:
60
+ sig = inspect.signature(self.func)
61
+ properties = {}
62
+ required = []
63
+
64
+ for param_name, param in sig.parameters.items():
65
+ if param_name == "self": # Skip self parameter
66
+ continue
67
+
68
+ prop = {"type": "string"} # Default type
69
+
70
+ # Try to infer type from annotation
71
+ if param.annotation != param.empty:
72
+ annotation = param.annotation
73
+ if annotation == int:
74
+ prop["type"] = "integer"
75
+ elif annotation == float:
76
+ prop["type"] = "number"
77
+ elif annotation == bool:
78
+ prop["type"] = "boolean"
79
+ elif annotation == list:
80
+ prop["type"] = "array"
81
+ elif annotation == dict:
82
+ prop["type"] = "object"
83
+
84
+ # Handle optional parameters
85
+ if param.default == param.empty:
86
+ required.append(param_name)
87
+ else:
88
+ prop["default"] = param.default
89
+
90
+ properties[param_name] = prop
91
+
92
+ return {
93
+ "type": "object",
94
+ "properties": properties,
95
+ "required": required,
96
+ }
97
+ except Exception:
98
+ return {"type": "object", "properties": {}}
99
+
100
+
101
+ @dataclass
102
+ class ToolExecutionResult:
103
+ """Result of tool execution."""
104
+
105
+ tool_name: str
106
+ session_id: str
107
+ request_id: str
108
+ status: ToolExecutionStatus
109
+ result: Any = None
110
+ error: Optional[str] = None
111
+ execution_time: Optional[float] = None
112
+ metadata: Dict[str, Any] = None
113
+
114
+ def __post_init__(self):
115
+ if self.metadata is None:
116
+ self.metadata = {}
117
+
118
+
119
+ @runtime_checkable
120
+ class IToolExecutor(Protocol):
121
+ """Protocol interface for tool execution."""
122
+
123
+ def execute(
124
+ self, tool_def: ToolDefinition, params: Dict[str, Any], context: Dict[str, Any]
125
+ ) -> ToolExecutionResult:
126
+ """Execute a tool synchronously."""
127
+ ...
128
+
129
+ def execute_async(
130
+ self, tool_def: ToolDefinition, params: Dict[str, Any], context: Dict[str, Any]
131
+ ) -> str:
132
+ """Execute a tool asynchronously, return request ID."""
133
+ ...
134
+
135
+ def stream_execute(
136
+ self, tool_def: ToolDefinition, params: Dict[str, Any], context: Dict[str, Any]
137
+ ) -> Iterator[Any]:
138
+ """Execute a tool with streaming results."""
139
+ ...
140
+
141
+ def cancel_execution(self, request_id: str) -> bool:
142
+ """Cancel tool execution."""
143
+ ...
144
+
145
+ def get_execution_status(self, request_id: str) -> Optional[ToolExecutionResult]:
146
+ """Get execution status."""
147
+ ...
148
+
149
+
150
+ @runtime_checkable
151
+ class IToolManager(Protocol):
152
+ """Protocol interface for tool management."""
153
+
154
+ def register_tool(
155
+ self,
156
+ session_id: str,
157
+ name: str,
158
+ func: Callable,
159
+ schema: Optional[Dict] = None,
160
+ description: Optional[str] = None,
161
+ stream: bool = False,
162
+ tags: Optional[List[str]] = None,
163
+ ) -> None:
164
+ """Register a tool for a session."""
165
+ ...
166
+
167
+ def unregister_tool(self, session_id: str, name: str) -> bool:
168
+ """Unregister a tool from a session."""
169
+ ...
170
+
171
+ def get_tool(self, session_id: str, name: str) -> Optional[ToolDefinition]:
172
+ """Get tool definition."""
173
+ ...
174
+
175
+ def list_tools(self, session_id: str) -> List[ToolDefinition]:
176
+ """List tools for a session."""
177
+ ...
178
+
179
+ def execute_tool(
180
+ self,
181
+ session_id: str,
182
+ tool_name: str,
183
+ params: Dict[str, Any],
184
+ idempotency_key: str = "",
185
+ timeout_seconds: int = 0,
186
+ wait_timeout_seconds: int = 0,
187
+ ) -> Tuple[str, Optional[str], Optional[Any]]:
188
+ """Execute a tool.
189
+
190
+ Returns ``(request_id, terminal_status, result)``:
191
+ ``terminal_status`` is the normalized status name when the
192
+ server-side long-poll observed a terminal state (``result`` holds
193
+ the parsed result for a successful run); both are ``None`` when the
194
+ request is still in flight.
195
+ """
196
+ ...
197
+
198
+ def stream_tool(
199
+ self, session_id: str, tool_name: str, params: Dict[str, Any]
200
+ ) -> Iterator[Any]:
201
+ """Stream tool execution."""
202
+ ...
203
+
204
+ def get_session_tools(self, session_id: str) -> Dict[str, ToolDefinition]:
205
+ """Get all tools for a session."""
206
+ ...
207
+
208
+
209
+ class IToolValidator(ABC):
210
+ """Abstract interface for tool validation."""
211
+
212
+ @abstractmethod
213
+ def validate_tool_definition(self, tool_def: ToolDefinition) -> List[str]:
214
+ """Validate tool definition, return list of errors."""
215
+ pass
216
+
217
+ @abstractmethod
218
+ def validate_tool_parameters(
219
+ self, tool_def: ToolDefinition, params: Dict[str, Any]
220
+ ) -> List[str]:
221
+ """Validate tool parameters, return list of errors."""
222
+ pass
223
+
224
+ @abstractmethod
225
+ def sanitize_parameters(
226
+ self, tool_def: ToolDefinition, params: Dict[str, Any]
227
+ ) -> Dict[str, Any]:
228
+ """Sanitize tool parameters."""
229
+ pass
230
+
231
+
232
+ class DefaultToolValidator(IToolValidator):
233
+ """Default implementation of tool validator."""
234
+
235
+ def validate_tool_definition(self, tool_def: ToolDefinition) -> List[str]:
236
+ """Validate tool definition."""
237
+ errors = []
238
+
239
+ if not tool_def.name:
240
+ errors.append("Tool name is required")
241
+ elif not isinstance(tool_def.name, str):
242
+ errors.append("Tool name must be a string")
243
+ elif not tool_def.name.replace("_", "").replace("-", "").isalnum():
244
+ errors.append("Tool name must be alphanumeric with underscores/hyphens")
245
+
246
+ if not callable(tool_def.func):
247
+ errors.append("Tool function must be callable")
248
+
249
+ if tool_def.schema and not isinstance(tool_def.schema, dict):
250
+ errors.append("Tool schema must be a dictionary")
251
+
252
+ if tool_def.description and len(tool_def.description) > 1000:
253
+ errors.append("Tool description is too long (max 1000 characters)")
254
+
255
+ if tool_def.tags and not isinstance(tool_def.tags, list):
256
+ errors.append("Tool tags must be a list")
257
+
258
+ return errors
259
+
260
+ def validate_tool_parameters(
261
+ self, tool_def: ToolDefinition, params: Dict[str, Any]
262
+ ) -> List[str]:
263
+ """Validate tool parameters against schema."""
264
+ errors = []
265
+
266
+ if not tool_def.schema:
267
+ return errors # No schema to validate against
268
+
269
+ schema = tool_def.schema
270
+ if "properties" not in schema:
271
+ return errors
272
+
273
+ properties = schema["properties"]
274
+ required = schema.get("required", [])
275
+
276
+ # Check required parameters
277
+ for req_param in required:
278
+ if req_param not in params:
279
+ errors.append(f"Required parameter '{req_param}' is missing")
280
+
281
+ # Validate parameter types
282
+ for param_name, param_value in params.items():
283
+ if param_name not in properties:
284
+ errors.append(f"Unknown parameter '{param_name}'")
285
+ continue
286
+
287
+ prop = properties[param_name]
288
+ expected_type = prop.get("type", "string")
289
+
290
+ if not self._validate_type(param_value, expected_type):
291
+ errors.append(
292
+ f"Parameter '{param_name}' has invalid type. "
293
+ f"Expected {expected_type}, got {type(param_value).__name__}"
294
+ )
295
+
296
+ return errors
297
+
298
+ def sanitize_parameters(
299
+ self, tool_def: ToolDefinition, params: Dict[str, Any]
300
+ ) -> Dict[str, Any]:
301
+ """Sanitize tool parameters."""
302
+ if not tool_def.schema or "properties" not in tool_def.schema:
303
+ return params
304
+
305
+ sanitized = {}
306
+ properties = tool_def.schema["properties"]
307
+
308
+ for param_name, param_value in params.items():
309
+ if param_name not in properties:
310
+ continue # Skip unknown parameters
311
+
312
+ prop = properties[param_name]
313
+ expected_type = prop.get("type", "string")
314
+
315
+ # Try to convert to expected type
316
+ try:
317
+ sanitized[param_name] = self._convert_type(param_value, expected_type)
318
+ except (ValueError, TypeError):
319
+ sanitized[param_name] = param_value # Keep original if conversion fails
320
+
321
+ return sanitized
322
+
323
+ def _validate_type(self, value: Any, expected_type: str) -> bool:
324
+ """Validate if value matches expected type."""
325
+ type_map = {
326
+ "string": str,
327
+ "integer": int,
328
+ "number": (int, float),
329
+ "boolean": bool,
330
+ "array": list,
331
+ "object": dict,
332
+ }
333
+
334
+ expected_python_type = type_map.get(expected_type)
335
+ if not expected_python_type:
336
+ return True # Unknown type, assume valid
337
+
338
+ return isinstance(value, expected_python_type)
339
+
340
+ def _convert_type(self, value: Any, expected_type: str) -> Any:
341
+ """Convert value to expected type."""
342
+ if expected_type == "string":
343
+ return str(value)
344
+ elif expected_type == "integer":
345
+ return int(value)
346
+ elif expected_type == "number":
347
+ return float(value)
348
+ elif expected_type == "boolean":
349
+ if isinstance(value, str):
350
+ return value.lower() in ("true", "1", "yes", "on")
351
+ return bool(value)
352
+ elif expected_type == "array":
353
+ if isinstance(value, str):
354
+ import json
355
+
356
+ return json.loads(value)
357
+ return list(value)
358
+ elif expected_type == "object":
359
+ if isinstance(value, str):
360
+ import json
361
+
362
+ return json.loads(value)
363
+ return dict(value)
364
+
365
+ return value
366
+
367
+
368
+ class ToolRegistry:
369
+ """Registry for managing tool definitions with validation."""
370
+
371
+ def __init__(self, validator: Optional[IToolValidator] = None):
372
+ self._tools: Dict[str, Dict[str, ToolDefinition]] = (
373
+ {}
374
+ ) # session_id -> tool_name -> tool_def
375
+ self._validator = validator or DefaultToolValidator()
376
+
377
+ def register_tool(self, session_id: str, tool_def: ToolDefinition) -> None:
378
+ """Register a tool definition."""
379
+ # Validate tool definition
380
+ errors = self._validator.validate_tool_definition(tool_def)
381
+ if errors:
382
+ raise ValueError(f"Invalid tool definition: {'; '.join(errors)}")
383
+
384
+ if session_id not in self._tools:
385
+ self._tools[session_id] = {}
386
+
387
+ self._tools[session_id][tool_def.name] = tool_def
388
+
389
+ def unregister_tool(self, session_id: str, tool_name: str) -> bool:
390
+ """Unregister a tool."""
391
+ if session_id in self._tools and tool_name in self._tools[session_id]:
392
+ del self._tools[session_id][tool_name]
393
+ return True
394
+ return False
395
+
396
+ def get_tool(self, session_id: str, tool_name: str) -> Optional[ToolDefinition]:
397
+ """Get tool definition."""
398
+ return self._tools.get(session_id, {}).get(tool_name)
399
+
400
+ def list_tools(self, session_id: str) -> List[ToolDefinition]:
401
+ """List tools for a session."""
402
+ return list(self._tools.get(session_id, {}).values())
403
+
404
+ def get_session_tools(self, session_id: str) -> Dict[str, ToolDefinition]:
405
+ """Get all tools for a session."""
406
+ return self._tools.get(session_id, {}).copy()
407
+
408
+ def validate_execution_params(
409
+ self, session_id: str, tool_name: str, params: Dict[str, Any]
410
+ ) -> Dict[str, Any]:
411
+ """Validate and sanitize execution parameters."""
412
+ tool_def = self.get_tool(session_id, tool_name)
413
+ if not tool_def:
414
+ raise ValueError(f"Tool '{tool_name}' not found in session '{session_id}'")
415
+
416
+ # Validate parameters
417
+ errors = self._validator.validate_tool_parameters(tool_def, params)
418
+ if errors:
419
+ raise ValueError(f"Invalid parameters: {'; '.join(errors)}")
420
+
421
+ # Sanitize parameters
422
+ return self._validator.sanitize_parameters(tool_def, params)
423
+
424
+ def clear_session_tools(self, session_id: str) -> None:
425
+ """Clear all tools for a session."""
426
+ if session_id in self._tools:
427
+ del self._tools[session_id]
428
+
429
+ def get_tool_stats(self) -> Dict[str, Any]:
430
+ """Get tool registry statistics."""
431
+ total_tools = sum(len(tools) for tools in self._tools.values())
432
+ sessions_with_tools = len([s for s in self._tools.values() if s])
433
+
434
+ return {
435
+ "total_sessions": len(self._tools),
436
+ "sessions_with_tools": sessions_with_tools,
437
+ "total_tools": total_tools,
438
+ "tools_per_session": {
439
+ session_id: len(tools) for session_id, tools in self._tools.items()
440
+ },
441
+ }
File without changes