janito 3.3.0__py3-none-any.whl → 3.5.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 (170) hide show
  1. janito/README.md +3 -0
  2. janito/cli/chat_mode/bindings.py +50 -0
  3. janito/cli/chat_mode/session.py +12 -1
  4. janito/cli/chat_mode/shell/commands/multi.py +5 -0
  5. janito/cli/chat_mode/shell/commands/security/allowed_sites.py +47 -33
  6. janito/cli/cli_commands/list_plugins.py +13 -8
  7. janito/cli/core/model_guesser.py +40 -24
  8. janito/cli/prompt_core.py +47 -9
  9. janito/cli/rich_terminal_reporter.py +2 -2
  10. janito/hello.txt +0 -0
  11. janito/i18n/it.py +46 -46
  12. janito/llm/agent.py +32 -16
  13. janito/llm/cancellation_manager.py +63 -0
  14. janito/llm/driver.py +8 -0
  15. janito/llm/enter_cancellation.py +107 -0
  16. janito/{plugin_system_backup_20250825_070018 → plugin_system}/core_loader.py +76 -3
  17. janito/{plugin_system_backup_20250825_070018 → plugin_system}/core_loader_fixed.py +79 -3
  18. janito/plugins/__init__.py +29 -21
  19. janito/{plugins_backup_20250825_070018 → plugins}/auto_loader.py +12 -11
  20. janito/plugins/builtin.py +1 -1
  21. janito/plugins/core/filemanager/tools/copy_file.py +1 -25
  22. janito/plugins/core/filemanager/tools/create_directory.py +1 -28
  23. janito/plugins/core/filemanager/tools/create_file.py +3 -27
  24. janito/plugins/core/filemanager/tools/delete_text_in_file.py +0 -1
  25. janito/plugins/core/imagedisplay/plugin.py +1 -1
  26. janito/plugins/core_adapter.py +131 -0
  27. janito/plugins/discovery.py +3 -3
  28. janito/{plugins_backup_20250825_070018 → plugins}/discovery_core.py +14 -9
  29. janito/plugins/example_plugin.py +1 -1
  30. janito/plugins/manager.py +1 -1
  31. janito/{plugins_backup_20250825_070018 → plugins}/tools/core_tools_plugin.py +9 -10
  32. janito/{plugins_backup_20250825_070018 → plugins}/tools/create_file.py +2 -2
  33. janito/{plugins_backup_20250825_070018 → plugins}/tools/delete_text_in_file.py +0 -1
  34. janito/providers/__init__.py +1 -0
  35. janito/providers/together/__init__.py +1 -0
  36. janito/providers/together/model_info.py +69 -0
  37. janito/providers/together/provider.py +108 -0
  38. janito/tools/base.py +1 -31
  39. janito/tools/cli_initializer.py +1 -1
  40. janito/tools/initialize.py +1 -1
  41. janito/tools/loop_protection_decorator.py +114 -117
  42. janito/tools/tool_base.py +114 -142
  43. janito/tools/tools_schema.py +6 -12
  44. janito-3.5.0.dist-info/METADATA +229 -0
  45. {janito-3.3.0.dist-info → janito-3.5.0.dist-info}/RECORD +98 -162
  46. janito/plugins/__main__.py +0 -85
  47. janito/plugins/base.py +0 -57
  48. janito/plugins/core_loader.py +0 -144
  49. janito/plugins_backup_20250825_070018/__init__.py +0 -36
  50. janito/plugins_backup_20250825_070018/builtin.py +0 -102
  51. janito/plugins_backup_20250825_070018/config.py +0 -84
  52. janito/plugins_backup_20250825_070018/core/__init__.py +0 -7
  53. janito/plugins_backup_20250825_070018/core/codeanalyzer/__init__.py +0 -43
  54. janito/plugins_backup_20250825_070018/core/codeanalyzer/tools/get_file_outline/__init__.py +0 -1
  55. janito/plugins_backup_20250825_070018/core/codeanalyzer/tools/get_file_outline/core.py +0 -122
  56. janito/plugins_backup_20250825_070018/core/codeanalyzer/tools/search_text/__init__.py +0 -1
  57. janito/plugins_backup_20250825_070018/core/codeanalyzer/tools/search_text/core.py +0 -205
  58. janito/plugins_backup_20250825_070018/core/filemanager/__init__.py +0 -124
  59. janito/plugins_backup_20250825_070018/core/filemanager/tools/copy_file.py +0 -87
  60. janito/plugins_backup_20250825_070018/core/filemanager/tools/create_directory.py +0 -70
  61. janito/plugins_backup_20250825_070018/core/filemanager/tools/create_file.py +0 -87
  62. janito/plugins_backup_20250825_070018/core/filemanager/tools/delete_text_in_file.py +0 -135
  63. janito/plugins_backup_20250825_070018/core/filemanager/tools/find_files.py +0 -143
  64. janito/plugins_backup_20250825_070018/core/filemanager/tools/move_file.py +0 -131
  65. janito/plugins_backup_20250825_070018/core/filemanager/tools/read_files.py +0 -58
  66. janito/plugins_backup_20250825_070018/core/filemanager/tools/remove_directory.py +0 -55
  67. janito/plugins_backup_20250825_070018/core/filemanager/tools/remove_file.py +0 -58
  68. janito/plugins_backup_20250825_070018/core/filemanager/tools/replace_text_in_file.py +0 -270
  69. janito/plugins_backup_20250825_070018/core/filemanager/tools/validate_file_syntax/__init__.py +0 -1
  70. janito/plugins_backup_20250825_070018/core/filemanager/tools/validate_file_syntax/core.py +0 -114
  71. janito/plugins_backup_20250825_070018/core/filemanager/tools/view_file.py +0 -172
  72. janito/plugins_backup_20250825_070018/core/imagedisplay/__init__.py +0 -14
  73. janito/plugins_backup_20250825_070018/core/imagedisplay/plugin.py +0 -51
  74. janito/plugins_backup_20250825_070018/core/imagedisplay/tools/__init__.py +0 -1
  75. janito/plugins_backup_20250825_070018/core/imagedisplay/tools/show_image.py +0 -83
  76. janito/plugins_backup_20250825_070018/core/imagedisplay/tools/show_image_grid.py +0 -84
  77. janito/plugins_backup_20250825_070018/core/system/__init__.py +0 -23
  78. janito/plugins_backup_20250825_070018/core/system/tools/run_bash_command.py +0 -183
  79. janito/plugins_backup_20250825_070018/core/system/tools/run_powershell_command.py +0 -218
  80. janito/plugins_backup_20250825_070018/core_adapter.py +0 -55
  81. janito/plugins_backup_20250825_070018/dev/__init__.py +0 -7
  82. janito/plugins_backup_20250825_070018/dev/pythondev/__init__.py +0 -37
  83. janito/plugins_backup_20250825_070018/dev/pythondev/tools/python_code_run.py +0 -172
  84. janito/plugins_backup_20250825_070018/dev/pythondev/tools/python_command_run.py +0 -171
  85. janito/plugins_backup_20250825_070018/dev/pythondev/tools/python_file_run.py +0 -172
  86. janito/plugins_backup_20250825_070018/dev/visualization/__init__.py +0 -23
  87. janito/plugins_backup_20250825_070018/dev/visualization/tools/read_chart.py +0 -259
  88. janito/plugins_backup_20250825_070018/discovery.py +0 -289
  89. janito/plugins_backup_20250825_070018/example_plugin.py +0 -108
  90. janito/plugins_backup_20250825_070018/manager.py +0 -243
  91. janito/plugins_backup_20250825_070018/tools/get_file_outline/java_outline.py +0 -47
  92. janito/plugins_backup_20250825_070018/tools/get_file_outline/markdown_outline.py +0 -14
  93. janito/plugins_backup_20250825_070018/tools/get_file_outline/python_outline.py +0 -303
  94. janito/plugins_backup_20250825_070018/tools/get_file_outline/search_outline.py +0 -36
  95. janito/plugins_backup_20250825_070018/tools/search_text/match_lines.py +0 -67
  96. janito/plugins_backup_20250825_070018/tools/search_text/pattern_utils.py +0 -73
  97. janito/plugins_backup_20250825_070018/tools/search_text/traverse_directory.py +0 -145
  98. janito/plugins_backup_20250825_070018/tools/validate_file_syntax/css_validator.py +0 -35
  99. janito/plugins_backup_20250825_070018/tools/validate_file_syntax/html_validator.py +0 -100
  100. janito/plugins_backup_20250825_070018/tools/validate_file_syntax/jinja2_validator.py +0 -50
  101. janito/plugins_backup_20250825_070018/tools/validate_file_syntax/js_validator.py +0 -27
  102. janito/plugins_backup_20250825_070018/tools/validate_file_syntax/json_validator.py +0 -6
  103. janito/plugins_backup_20250825_070018/tools/validate_file_syntax/markdown_validator.py +0 -109
  104. janito/plugins_backup_20250825_070018/tools/validate_file_syntax/ps1_validator.py +0 -32
  105. janito/plugins_backup_20250825_070018/tools/validate_file_syntax/python_validator.py +0 -5
  106. janito/plugins_backup_20250825_070018/tools/validate_file_syntax/xml_validator.py +0 -11
  107. janito/plugins_backup_20250825_070018/tools/validate_file_syntax/yaml_validator.py +0 -6
  108. janito/plugins_backup_20250825_070018/ui/__init__.py +0 -7
  109. janito/plugins_backup_20250825_070018/ui/userinterface/__init__.py +0 -16
  110. janito/plugins_backup_20250825_070018/ui/userinterface/tools/ask_user.py +0 -110
  111. janito/plugins_backup_20250825_070018/web/__init__.py +0 -7
  112. janito/plugins_backup_20250825_070018/web/webtools/__init__.py +0 -33
  113. janito/plugins_backup_20250825_070018/web/webtools/tools/fetch_url.py +0 -458
  114. janito/plugins_backup_20250825_070018/web/webtools/tools/open_html_in_browser.py +0 -51
  115. janito/plugins_backup_20250825_070018/web/webtools/tools/open_url.py +0 -37
  116. janito/tools/function_adapter.py +0 -176
  117. janito-3.3.0.dist-info/METADATA +0 -83
  118. /janito/{plugin_system_backup_20250825_070018 → plugin_system}/__init__.py +0 -0
  119. /janito/{plugin_system_backup_20250825_070018 → plugin_system}/base.py +0 -0
  120. /janito/{plugins_backup_20250825_070018 → plugins}/auto_loader_fixed.py +0 -0
  121. /janito/{plugins_backup_20250825_070018 → plugins}/tools/__init__.py +0 -0
  122. /janito/{plugins_backup_20250825_070018 → plugins}/tools/ask_user.py +0 -0
  123. /janito/{plugins_backup_20250825_070018 → plugins}/tools/copy_file.py +0 -0
  124. /janito/{plugins_backup_20250825_070018 → plugins}/tools/create_directory.py +0 -0
  125. /janito/{plugins_backup_20250825_070018 → plugins}/tools/decorators.py +0 -0
  126. /janito/{plugins_backup_20250825_070018 → plugins}/tools/fetch_url.py +0 -0
  127. /janito/{plugins_backup_20250825_070018 → plugins}/tools/find_files.py +0 -0
  128. /janito/{plugins_backup_20250825_070018 → plugins}/tools/get_file_outline/__init__.py +0 -0
  129. /janito/{plugins_backup_20250825_070018 → plugins}/tools/get_file_outline/core.py +0 -0
  130. /janito/{plugins_backup_20250825_070018/core/codeanalyzer → plugins}/tools/get_file_outline/java_outline.py +0 -0
  131. /janito/{plugins_backup_20250825_070018/core/codeanalyzer → plugins}/tools/get_file_outline/markdown_outline.py +0 -0
  132. /janito/{plugins_backup_20250825_070018/core/codeanalyzer → plugins}/tools/get_file_outline/python_outline.py +0 -0
  133. /janito/{plugins_backup_20250825_070018/core/codeanalyzer → plugins}/tools/get_file_outline/search_outline.py +0 -0
  134. /janito/{plugins_backup_20250825_070018 → plugins}/tools/move_file.py +0 -0
  135. /janito/{plugins_backup_20250825_070018 → plugins}/tools/open_html_in_browser.py +0 -0
  136. /janito/{plugins_backup_20250825_070018 → plugins}/tools/open_url.py +0 -0
  137. /janito/{plugins_backup_20250825_070018 → plugins}/tools/python_code_run.py +0 -0
  138. /janito/{plugins_backup_20250825_070018 → plugins}/tools/python_command_run.py +0 -0
  139. /janito/{plugins_backup_20250825_070018 → plugins}/tools/python_file_run.py +0 -0
  140. /janito/{plugins_backup_20250825_070018 → plugins}/tools/read_chart.py +0 -0
  141. /janito/{plugins_backup_20250825_070018 → plugins}/tools/read_files.py +0 -0
  142. /janito/{plugins_backup_20250825_070018 → plugins}/tools/remove_directory.py +0 -0
  143. /janito/{plugins_backup_20250825_070018 → plugins}/tools/remove_file.py +0 -0
  144. /janito/{plugins_backup_20250825_070018 → plugins}/tools/replace_text_in_file.py +0 -0
  145. /janito/{plugins_backup_20250825_070018 → plugins}/tools/run_bash_command.py +0 -0
  146. /janito/{plugins_backup_20250825_070018 → plugins}/tools/run_powershell_command.py +0 -0
  147. /janito/{plugins_backup_20250825_070018 → plugins}/tools/search_text/__init__.py +0 -0
  148. /janito/{plugins_backup_20250825_070018 → plugins}/tools/search_text/core.py +0 -0
  149. /janito/{plugins_backup_20250825_070018/core/codeanalyzer → plugins}/tools/search_text/match_lines.py +0 -0
  150. /janito/{plugins_backup_20250825_070018/core/codeanalyzer → plugins}/tools/search_text/pattern_utils.py +0 -0
  151. /janito/{plugins_backup_20250825_070018/core/codeanalyzer → plugins}/tools/search_text/traverse_directory.py +0 -0
  152. /janito/{plugins_backup_20250825_070018 → plugins}/tools/show_image.py +0 -0
  153. /janito/{plugins_backup_20250825_070018 → plugins}/tools/show_image_grid.py +0 -0
  154. /janito/{plugins_backup_20250825_070018 → plugins}/tools/validate_file_syntax/__init__.py +0 -0
  155. /janito/{plugins_backup_20250825_070018 → plugins}/tools/validate_file_syntax/core.py +0 -0
  156. /janito/{plugins_backup_20250825_070018/core/filemanager → plugins}/tools/validate_file_syntax/css_validator.py +0 -0
  157. /janito/{plugins_backup_20250825_070018/core/filemanager → plugins}/tools/validate_file_syntax/html_validator.py +0 -0
  158. /janito/{plugins_backup_20250825_070018/core/filemanager → plugins}/tools/validate_file_syntax/jinja2_validator.py +0 -0
  159. /janito/{plugins_backup_20250825_070018/core/filemanager → plugins}/tools/validate_file_syntax/js_validator.py +0 -0
  160. /janito/{plugins_backup_20250825_070018/core/filemanager → plugins}/tools/validate_file_syntax/json_validator.py +0 -0
  161. /janito/{plugins_backup_20250825_070018/core/filemanager → plugins}/tools/validate_file_syntax/markdown_validator.py +0 -0
  162. /janito/{plugins_backup_20250825_070018/core/filemanager → plugins}/tools/validate_file_syntax/ps1_validator.py +0 -0
  163. /janito/{plugins_backup_20250825_070018/core/filemanager → plugins}/tools/validate_file_syntax/python_validator.py +0 -0
  164. /janito/{plugins_backup_20250825_070018/core/filemanager → plugins}/tools/validate_file_syntax/xml_validator.py +0 -0
  165. /janito/{plugins_backup_20250825_070018/core/filemanager → plugins}/tools/validate_file_syntax/yaml_validator.py +0 -0
  166. /janito/{plugins_backup_20250825_070018 → plugins}/tools/view_file.py +0 -0
  167. {janito-3.3.0.dist-info → janito-3.5.0.dist-info}/WHEEL +0 -0
  168. {janito-3.3.0.dist-info → janito-3.5.0.dist-info}/entry_points.txt +0 -0
  169. {janito-3.3.0.dist-info → janito-3.5.0.dist-info}/licenses/LICENSE +0 -0
  170. {janito-3.3.0.dist-info → janito-3.5.0.dist-info}/top_level.txt +0 -0
@@ -1,143 +1,140 @@
1
1
  import functools
2
2
  import time
3
3
  import threading
4
- from typing import Callable, Any
5
- from janito.tools.loop_protection import LoopProtection
6
- from janito.tools.tool_use_tracker import normalize_path
7
-
4
+ from typing import Any, Tuple
8
5
 
9
6
  # Global tracking for decorator-based loop protection
10
7
  _decorator_call_tracker = {}
11
8
  _decorator_call_tracker_lock = threading.Lock()
12
9
 
13
10
 
11
+ def _normalize_key_value(key_field: str, key_value: Any) -> Any:
12
+ """Normalize key values, especially paths, so different representations map to the same key."""
13
+ if key_value is None:
14
+ return None
15
+
16
+ try:
17
+ if isinstance(key_field, str) and "path" in key_field.lower():
18
+ from janito.tools.tool_use_tracker import (
19
+ normalize_path as _norm, # reuse existing normalization
20
+ )
21
+
22
+ if isinstance(key_value, str):
23
+ return _norm(key_value)
24
+ if isinstance(key_value, (list, tuple)):
25
+ return tuple(_norm(v) if isinstance(v, str) else v for v in key_value)
26
+ except Exception:
27
+ # Best-effort normalization – fall back to original value
28
+ pass
29
+
30
+ return key_value
31
+
32
+
33
+ def _get_param_value(func, args, kwargs, key_field: str):
34
+ """Extract the watched parameter value from args/kwargs using function signature."""
35
+ if key_field in kwargs:
36
+ return kwargs[key_field]
37
+
38
+ # Handle positional arguments by mapping to parameter names
39
+ if len(args) > 1: # args[0] is self
40
+ import inspect
41
+
42
+ try:
43
+ sig = inspect.signature(func)
44
+ param_names = list(sig.parameters.keys())
45
+ if key_field in param_names:
46
+ idx = param_names.index(key_field)
47
+ if idx < len(args):
48
+ return args[idx]
49
+ except Exception:
50
+ return None
51
+
52
+ return None
53
+
54
+
55
+ def _determine_operation_name(func, args, kwargs, key_field: str) -> str:
56
+ """Build the operation name for rate limiting, optionally including a normalized key value."""
57
+ if key_field:
58
+ raw_value = _get_param_value(func, args, kwargs, key_field)
59
+ if raw_value is not None:
60
+ norm_value = _normalize_key_value(key_field, raw_value)
61
+ return f"{func.__name__}_{norm_value}"
62
+ return func.__name__
63
+
64
+
65
+ def _check_and_record(
66
+ op_name: str,
67
+ current_time: float,
68
+ time_window: float,
69
+ max_calls: int,
70
+ tool_instance: Any,
71
+ ) -> Tuple[bool, str]:
72
+ """Check loop limits for op_name and record the call. Returns (exceeded, message)."""
73
+ with _decorator_call_tracker_lock:
74
+ # Clean old timestamps
75
+ if op_name in _decorator_call_tracker:
76
+ _decorator_call_tracker[op_name] = [
77
+ ts
78
+ for ts in _decorator_call_tracker[op_name]
79
+ if current_time - ts <= time_window
80
+ ]
81
+
82
+ # Check limit
83
+ if (
84
+ op_name in _decorator_call_tracker
85
+ and len(_decorator_call_tracker[op_name]) >= max_calls
86
+ ):
87
+ if all(
88
+ current_time - ts <= time_window
89
+ for ts in _decorator_call_tracker[op_name]
90
+ ):
91
+ msg = (
92
+ f"Loop protection: Too many {op_name} operations in a short time period "
93
+ f"({max_calls} calls in {time_window}s). Please try a different approach or wait before retrying."
94
+ )
95
+ if hasattr(tool_instance, "report_error"):
96
+ try:
97
+ tool_instance.report_error(msg)
98
+ except Exception:
99
+ pass
100
+ return True, msg
101
+
102
+ # Record this call
103
+ if op_name not in _decorator_call_tracker:
104
+ _decorator_call_tracker[op_name] = []
105
+ _decorator_call_tracker[op_name].append(current_time)
106
+
107
+ return False, ""
108
+
109
+
14
110
  def protect_against_loops(
15
111
  max_calls: int = 5, time_window: float = 10.0, key_field: str = None
16
112
  ):
17
113
  """
18
114
  Decorator that adds loop protection to tool run methods.
19
115
 
20
- This decorator monitors tool executions and prevents excessive calls within
21
- a configurable time window. It helps prevent infinite loops or excessive
22
- resource consumption when tools are called repeatedly.
23
-
24
- When the configured limits are exceeded, the decorator raises a RuntimeError
25
- with a descriptive message. This exception will propagate up the call stack
26
- unless caught by a try/except block in the calling code.
27
-
28
- The decorator works by:
29
- 1. Tracking the number of calls to the decorated function
30
- 2. Checking if the calls exceed the configured limits
31
- 3. Raising a RuntimeError if a potential loop is detected
32
- 4. Allowing the method to proceed normally if the operation is safe
33
-
34
- Args:
35
- max_calls (int): Maximum number of calls allowed within the time window.
36
- Defaults to 5 calls.
37
- time_window (float): Time window in seconds for detecting excessive calls.
38
- Defaults to 10.0 seconds.
39
- key_field (str, optional): The parameter name to use for key matching instead of function name.
40
- If provided, the decorator will track calls based on the value of this
41
- parameter rather than the function name. Useful for tools that operate
42
- on specific files or resources.
43
-
44
- Example:
45
- >>> @protect_against_loops(max_calls=3, time_window=5.0)
46
- >>> def run(self, path: str) -> str:
47
- >>> # Implementation here
48
- >>> pass
49
-
50
- >>> @protect_against_loops(max_calls=10, time_window=30.0)
51
- >>> def run(self, file_paths: list) -> str:
52
- >>> # Implementation here
53
- >>> pass
54
-
55
- >>> @protect_against_loops(max_calls=5, time_window=10.0, key_field='path')
56
- >>> def run(self, path: str) -> str:
57
- >>> # This will track calls per unique path value
58
- >>> pass
59
-
60
- Note:
61
- When loop protection is triggered, a RuntimeError will be raised with a
62
- descriptive message. This exception will propagate up the call stack
63
- unless caught by a try/except block in the calling code.
116
+ Tracks calls within a sliding time window and prevents excessive repeated operations.
117
+ When key_field is provided, the limit is applied per unique normalized value of that parameter
118
+ (e.g., per-path protection for file tools).
64
119
  """
65
120
 
66
121
  def decorator(func):
67
122
  @functools.wraps(func)
68
123
  def wrapper(*args, **kwargs):
69
- # Get the tool instance (self)
124
+ # Methods should always have self; if not, execute directly.
70
125
  if not args:
71
- # This shouldn't happen in normal usage as methods need self
72
126
  return func(*args, **kwargs)
73
127
 
74
- # Determine the operation key
75
- if key_field:
76
- # Use the key_field parameter value as the operation key
77
- key_value = None
78
- if key_field in kwargs:
79
- key_value = kwargs[key_field]
80
- elif len(args) > 1:
81
- # Handle positional arguments - need to map parameter names
82
- import inspect
83
-
84
- try:
85
- sig = inspect.signature(func)
86
- param_names = list(sig.parameters.keys())
87
- if key_field in param_names:
88
- field_index = param_names.index(key_field)
89
- if field_index < len(args):
90
- key_value = args[field_index]
91
- except (ValueError, TypeError):
92
- pass
93
-
94
- if key_value is not None:
95
- op_name = f"{func.__name__}_{key_value}"
96
- else:
97
- op_name = func.__name__
98
- else:
99
- # Use the function name as the operation name
100
- op_name = func.__name__
101
-
102
- # Check call limits
103
- current_time = time.time()
104
-
105
- with _decorator_call_tracker_lock:
106
- # Clean up old entries outside the time window
107
- if op_name in _decorator_call_tracker:
108
- _decorator_call_tracker[op_name] = [
109
- timestamp
110
- for timestamp in _decorator_call_tracker[op_name]
111
- if current_time - timestamp <= time_window
112
- ]
113
-
114
- # Check if we're exceeding the limit
115
- if op_name in _decorator_call_tracker:
116
- if len(_decorator_call_tracker[op_name]) >= max_calls:
117
- # Check if all recent calls are within the time window
118
- if all(
119
- current_time - timestamp <= time_window
120
- for timestamp in _decorator_call_tracker[op_name]
121
- ):
122
- # Return loop protection message as string instead of raising exception
123
- error_msg = f"Loop protection: Too many {op_name} operations in a short time period ({max_calls} calls in {time_window}s). Please try a different approach or wait before retrying."
124
-
125
- # Try to report the error through the tool's reporting mechanism
126
- tool_instance = args[0] if args else None
127
- if hasattr(tool_instance, "report_error"):
128
- try:
129
- tool_instance.report_error(error_msg)
130
- except Exception:
131
- pass # If reporting fails, we still return the message
132
-
133
- return error_msg
134
-
135
- # Record this call
136
- if op_name not in _decorator_call_tracker:
137
- _decorator_call_tracker[op_name] = []
138
- _decorator_call_tracker[op_name].append(current_time)
139
-
140
- # Proceed with the original function
128
+ op_name = _determine_operation_name(func, args, kwargs, key_field)
129
+ exceeded, msg = _check_and_record(
130
+ op_name=op_name,
131
+ current_time=time.time(),
132
+ time_window=time_window,
133
+ max_calls=max_calls,
134
+ tool_instance=args[0],
135
+ )
136
+ if exceeded:
137
+ return msg
141
138
  return func(*args, **kwargs)
142
139
 
143
140
  return wrapper
janito/tools/tool_base.py CHANGED
@@ -1,142 +1,114 @@
1
- from janito.report_events import ReportEvent, ReportSubtype, ReportAction
2
- from janito.event_bus.bus import event_bus as default_event_bus
3
-
4
-
5
- from collections import namedtuple
6
-
7
-
8
- class ToolPermissions(namedtuple("ToolPermissions", ["read", "write", "execute"])):
9
- __slots__ = ()
10
-
11
- def __new__(cls, read=False, write=False, execute=False):
12
- return super().__new__(cls, read, write, execute)
13
-
14
- def __repr__(self):
15
- return f"ToolPermissions(read={self.read}, write={self.write}, execute={self.execute})"
16
-
17
-
18
- class ToolBase:
19
- """
20
- Base class for all tools in the janito project.
21
- Extend this class to implement specific tool functionality.
22
-
23
- Parameters:
24
- path (str): Target file path for file operations
25
- content (str): File content to write or process
26
- overwrite (bool): Whether to overwrite existing files (default: False)
27
- sources (str): Source file(s) to copy from
28
- target (str): Destination path for copy operations
29
- recursive (bool): Whether to process directories recursively
30
- from_line (int): Starting line number for file reading
31
- to_line (int): Ending line number for file reading
32
- search_text (str): Text to search for in files
33
- replacement_text (str): Text to replace search matches with
34
- use_regex (bool): Whether to treat search as regex pattern
35
- case_sensitive (bool): Whether search should be case sensitive
36
- max_depth (int): Maximum directory depth to search
37
- include_gitignored (bool): Whether to include .gitignored files
38
- timeout (int): Timeout in seconds for operations
39
- require_confirmation (bool): Whether to require user confirmation
40
- data (dict): Chart data for visualization tools
41
- title (str): Chart title
42
- width (int): Chart width in pixels
43
- height (int): Chart height in pixels
44
- query (str): Search query for text search
45
- paths (str): Directory or file paths to search in
46
- src_path (str): Source path for move operations
47
- dest_path (str): Destination path for move operations
48
- code (str): Python code to execute
49
- pattern (str): File pattern to match (e.g., '*.py')
50
- """
51
-
52
- permissions: "ToolPermissions" = None # Required: must be set by subclasses
53
-
54
- def __init__(self, name=None, event_bus=None):
55
- if self.permissions is None or not isinstance(
56
- self.permissions, ToolPermissions
57
- ):
58
- raise ValueError(
59
- f"Tool '{self.__class__.__name__}' must define a 'permissions' attribute of type ToolPermissions."
60
- )
61
- self.name = name or self.__class__.__name__
62
- self._event_bus = event_bus or default_event_bus
63
-
64
- @property
65
- def event_bus(self):
66
- return self._event_bus
67
-
68
- @event_bus.setter
69
- def event_bus(self, bus):
70
- self._event_bus = bus or default_event_bus
71
-
72
- def report_action(self, message: str, action: ReportAction, context: dict = None):
73
- """
74
- Report that a tool action is starting. This should be the first reporting call for every tool action.
75
- """
76
- self._event_bus.publish(
77
- ReportEvent(
78
- subtype=ReportSubtype.ACTION_INFO,
79
- message=" " + message,
80
- action=action,
81
- tool=self.name,
82
- context=context,
83
- )
84
- )
85
-
86
- def report_error(self, message: str, context: dict = None):
87
- self._event_bus.publish(
88
- ReportEvent(
89
- subtype=ReportSubtype.ERROR,
90
- message=message,
91
- action=None,
92
- tool=self.name,
93
- context=context,
94
- )
95
- )
96
-
97
- def report_success(self, message: str, context: dict = None):
98
- self._event_bus.publish(
99
- ReportEvent(
100
- subtype=ReportSubtype.SUCCESS,
101
- message=message,
102
- action=None,
103
- tool=self.name,
104
- context=context,
105
- )
106
- )
107
-
108
- def report_warning(self, message: str, context: dict = None):
109
- self._event_bus.publish(
110
- ReportEvent(
111
- subtype=ReportSubtype.WARNING,
112
- message=message,
113
- action=None,
114
- tool=self.name,
115
- context=context,
116
- )
117
- )
118
-
119
- def report_stdout(self, message: str, context: dict = None):
120
- self._event_bus.publish(
121
- ReportEvent(
122
- subtype=ReportSubtype.STDOUT,
123
- message=message,
124
- action=None,
125
- tool=self.name,
126
- context=context,
127
- )
128
- )
129
-
130
- def report_stderr(self, message: str, context: dict = None):
131
- self._event_bus.publish(
132
- ReportEvent(
133
- subtype=ReportSubtype.STDERR,
134
- message=message,
135
- action=None,
136
- tool=self.name,
137
- context=context,
138
- )
139
- )
140
-
141
- def run(self, *args, **kwargs):
142
- raise NotImplementedError("Subclasses must implement the run method.")
1
+ from janito.report_events import ReportEvent, ReportSubtype, ReportAction
2
+ from janito.event_bus.bus import event_bus as default_event_bus
3
+
4
+
5
+ from collections import namedtuple
6
+
7
+
8
+ class ToolPermissions(namedtuple("ToolPermissions", ["read", "write", "execute"])):
9
+ __slots__ = ()
10
+
11
+ def __new__(cls, read=False, write=False, execute=False):
12
+ return super().__new__(cls, read, write, execute)
13
+
14
+ def __repr__(self):
15
+ return f"ToolPermissions(read={self.read}, write={self.write}, execute={self.execute})"
16
+
17
+
18
+ class ToolBase:
19
+ """
20
+ Base class for all tools in the janito project.
21
+ Extend this class to implement specific tool functionality.
22
+ """
23
+
24
+ permissions: "ToolPermissions" = None # Required: must be set by subclasses
25
+
26
+ def __init__(self, name=None, event_bus=None):
27
+ if self.permissions is None or not isinstance(
28
+ self.permissions, ToolPermissions
29
+ ):
30
+ raise ValueError(
31
+ f"Tool '{self.__class__.__name__}' must define a 'permissions' attribute of type ToolPermissions."
32
+ )
33
+ self.name = name or self.__class__.__name__
34
+ self._event_bus = event_bus or default_event_bus
35
+
36
+ @property
37
+ def event_bus(self):
38
+ return self._event_bus
39
+
40
+ @event_bus.setter
41
+ def event_bus(self, bus):
42
+ self._event_bus = bus or default_event_bus
43
+
44
+ def report_action(self, message: str, action: ReportAction, context: dict = None):
45
+ """
46
+ Report that a tool action is starting. This should be the first reporting call for every tool action.
47
+ """
48
+ self._event_bus.publish(
49
+ ReportEvent(
50
+ subtype=ReportSubtype.ACTION_INFO,
51
+ message=" " + message,
52
+ action=action,
53
+ tool=self.name,
54
+ context=context,
55
+ )
56
+ )
57
+
58
+ def report_error(self, message: str, context: dict = None):
59
+ self._event_bus.publish(
60
+ ReportEvent(
61
+ subtype=ReportSubtype.ERROR,
62
+ message=message,
63
+ action=None,
64
+ tool=self.name,
65
+ context=context,
66
+ )
67
+ )
68
+
69
+ def report_success(self, message: str, context: dict = None):
70
+ self._event_bus.publish(
71
+ ReportEvent(
72
+ subtype=ReportSubtype.SUCCESS,
73
+ message=message,
74
+ action=None,
75
+ tool=self.name,
76
+ context=context,
77
+ )
78
+ )
79
+
80
+ def report_warning(self, message: str, context: dict = None):
81
+ self._event_bus.publish(
82
+ ReportEvent(
83
+ subtype=ReportSubtype.WARNING,
84
+ message=message,
85
+ action=None,
86
+ tool=self.name,
87
+ context=context,
88
+ )
89
+ )
90
+
91
+ def report_stdout(self, message: str, context: dict = None):
92
+ self._event_bus.publish(
93
+ ReportEvent(
94
+ subtype=ReportSubtype.STDOUT,
95
+ message=message,
96
+ action=None,
97
+ tool=self.name,
98
+ context=context,
99
+ )
100
+ )
101
+
102
+ def report_stderr(self, message: str, context: dict = None):
103
+ self._event_bus.publish(
104
+ ReportEvent(
105
+ subtype=ReportSubtype.STDERR,
106
+ message=message,
107
+ action=None,
108
+ tool=self.name,
109
+ context=context,
110
+ )
111
+ )
112
+
113
+ def run(self, *args, **kwargs):
114
+ raise NotImplementedError("Subclasses must implement the run method.")
@@ -7,8 +7,6 @@ class ToolSchemaBase:
7
7
  def parse_param_section(self, lines, param_section_headers):
8
8
  param_descs = {}
9
9
  in_params = False
10
- current_param = None
11
-
12
10
  for line in lines:
13
11
  stripped_line = line.strip()
14
12
  if any(
@@ -18,21 +16,17 @@ class ToolSchemaBase:
18
16
  in_params = True
19
17
  continue
20
18
  if in_params:
21
- # Check for parameter definition: "param_name (type): description"
22
19
  m = re.match(
23
- r"([a-zA-Z_][a-zA-Z0-9_]*)\s*(?:\([^)]+\))?\s*:\s*(.+)",
20
+ r"([a-zA-Z_][a-zA-Z0-9_]*)\s*(?:\(([^)]+)\))?\s*[:\-]?\s*(.+)",
24
21
  stripped_line,
25
22
  )
26
23
  if m:
27
- param, desc = m.groups()
24
+ param, _, desc = m.groups()
28
25
  param_descs[param] = desc.strip()
29
- current_param = param
30
- elif current_param and stripped_line and not (
31
- stripped_line.lower().startswith("returns:") or
32
- stripped_line.lower() == "returns"
33
- ):
34
- # Continuation of current parameter description
35
- param_descs[current_param] += " " + stripped_line.strip()
26
+ elif stripped_line and stripped_line[0] != "-":
27
+ if param_descs:
28
+ last = list(param_descs)[-1]
29
+ param_descs[last] += " " + stripped_line
36
30
  if (
37
31
  stripped_line.lower().startswith("returns:")
38
32
  or stripped_line.lower() == "returns"