janito 2.3.0__py3-none-any.whl → 2.4.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 (150) hide show
  1. janito/__init__.py +6 -6
  2. janito/_version.py +57 -0
  3. janito/agent/setup_agent.py +92 -18
  4. janito/agent/templates/profiles/system_prompt_template_developer.txt.j2 +44 -0
  5. janito/cli/chat_mode/bindings.py +21 -2
  6. janito/cli/chat_mode/chat_entry.py +2 -3
  7. janito/cli/chat_mode/prompt_style.py +5 -0
  8. janito/cli/chat_mode/session.py +80 -94
  9. janito/cli/chat_mode/session_profile_select.py +80 -0
  10. janito/cli/chat_mode/shell/autocomplete.py +21 -21
  11. janito/cli/chat_mode/shell/commands/__init__.py +13 -7
  12. janito/cli/chat_mode/shell/commands/_priv_check.py +5 -0
  13. janito/cli/chat_mode/shell/commands/clear.py +12 -12
  14. janito/cli/chat_mode/shell/commands/conversation_restart.py +30 -0
  15. janito/cli/chat_mode/shell/commands/execute.py +42 -0
  16. janito/cli/chat_mode/shell/commands/help.py +6 -3
  17. janito/cli/chat_mode/shell/commands/model.py +28 -0
  18. janito/cli/chat_mode/shell/commands/multi.py +51 -51
  19. janito/cli/chat_mode/shell/commands/read.py +37 -0
  20. janito/cli/chat_mode/shell/commands/tools.py +45 -18
  21. janito/cli/chat_mode/shell/commands/write.py +37 -0
  22. janito/cli/chat_mode/shell/commands.bak.zip +0 -0
  23. janito/cli/chat_mode/shell/input_history.py +62 -62
  24. janito/cli/chat_mode/shell/session.bak.zip +0 -0
  25. janito/cli/chat_mode/toolbar.py +44 -27
  26. janito/cli/cli_commands/list_models.py +35 -35
  27. janito/cli/cli_commands/list_providers.py +9 -9
  28. janito/cli/cli_commands/list_tools.py +86 -53
  29. janito/cli/cli_commands/model_selection.py +50 -50
  30. janito/cli/cli_commands/set_api_key.py +19 -19
  31. janito/cli/cli_commands/show_config.py +51 -51
  32. janito/cli/cli_commands/show_system_prompt.py +105 -62
  33. janito/cli/config.py +5 -6
  34. janito/cli/core/__init__.py +4 -4
  35. janito/cli/core/event_logger.py +59 -59
  36. janito/cli/core/runner.py +25 -18
  37. janito/cli/core/setters.py +10 -1
  38. janito/cli/core/unsetters.py +54 -54
  39. janito/cli/main_cli.py +28 -5
  40. janito/cli/prompt_core.py +18 -2
  41. janito/cli/prompt_setup.py +56 -0
  42. janito/cli/single_shot_mode/__init__.py +6 -6
  43. janito/cli/single_shot_mode/handler.py +14 -73
  44. janito/cli/verbose_output.py +1 -1
  45. janito/config.py +5 -5
  46. janito/config_manager.py +13 -0
  47. janito/drivers/anthropic/driver.py +113 -113
  48. janito/drivers/dashscope.bak.zip +0 -0
  49. janito/drivers/openai/README.md +20 -0
  50. janito/drivers/openai_responses.bak.zip +0 -0
  51. janito/event_bus/event.py +2 -2
  52. janito/formatting_token.py +54 -54
  53. janito/i18n/__init__.py +35 -35
  54. janito/i18n/messages.py +23 -23
  55. janito/i18n/pt.py +46 -47
  56. janito/llm/README.md +23 -0
  57. janito/llm/__init__.py +5 -5
  58. janito/llm/agent.py +507 -443
  59. janito/llm/driver.py +8 -0
  60. janito/llm/driver_config_builder.py +34 -34
  61. janito/llm/driver_input.py +12 -12
  62. janito/llm/message_parts.py +60 -60
  63. janito/llm/model.py +38 -38
  64. janito/llm/provider.py +196 -196
  65. janito/provider_registry.py +8 -6
  66. janito/providers/anthropic/model_info.py +22 -22
  67. janito/providers/anthropic/provider.py +2 -0
  68. janito/providers/azure_openai/provider.py +3 -0
  69. janito/providers/dashscope.bak.zip +0 -0
  70. janito/providers/deepseek/__init__.py +1 -1
  71. janito/providers/deepseek/model_info.py +16 -16
  72. janito/providers/deepseek/provider.py +94 -91
  73. janito/providers/google/provider.py +3 -0
  74. janito/providers/mistralai/provider.py +3 -0
  75. janito/providers/openai/provider.py +4 -0
  76. janito/providers/registry.py +26 -26
  77. janito/shell.bak.zip +0 -0
  78. janito/tools/DOCSTRING_STANDARD.txt +33 -0
  79. janito/tools/README.md +3 -0
  80. janito/tools/__init__.py +20 -6
  81. janito/tools/adapters/__init__.py +1 -1
  82. janito/tools/adapters/local/__init__.py +65 -62
  83. janito/tools/adapters/local/adapter.py +18 -35
  84. janito/tools/adapters/local/ask_user.py +101 -102
  85. janito/tools/adapters/local/copy_file.py +84 -84
  86. janito/tools/adapters/local/create_directory.py +69 -69
  87. janito/tools/adapters/local/create_file.py +82 -82
  88. janito/tools/adapters/local/delete_text_in_file.py +2 -2
  89. janito/tools/adapters/local/fetch_url.py +97 -97
  90. janito/tools/adapters/local/find_files.py +139 -138
  91. janito/tools/adapters/local/get_file_outline/__init__.py +1 -1
  92. janito/tools/adapters/local/get_file_outline/core.py +117 -117
  93. janito/tools/adapters/local/get_file_outline/java_outline.py +40 -40
  94. janito/tools/adapters/local/get_file_outline/markdown_outline.py +14 -14
  95. janito/tools/adapters/local/get_file_outline/python_outline.py +303 -303
  96. janito/tools/adapters/local/get_file_outline/python_outline_v2.py +156 -156
  97. janito/tools/adapters/local/get_file_outline/search_outline.py +33 -33
  98. janito/tools/adapters/local/move_file.py +2 -2
  99. janito/tools/adapters/local/open_html_in_browser.py +2 -1
  100. janito/tools/adapters/local/open_url.py +2 -2
  101. janito/tools/adapters/local/python_code_run.py +166 -166
  102. janito/tools/adapters/local/python_command_run.py +164 -164
  103. janito/tools/adapters/local/python_file_run.py +163 -163
  104. janito/tools/adapters/local/remove_directory.py +2 -2
  105. janito/tools/adapters/local/remove_file.py +2 -2
  106. janito/tools/adapters/local/replace_text_in_file.py +2 -2
  107. janito/tools/adapters/local/run_bash_command.py +176 -176
  108. janito/tools/adapters/local/run_powershell_command.py +219 -219
  109. janito/tools/adapters/local/search_text/__init__.py +1 -1
  110. janito/tools/adapters/local/search_text/core.py +201 -201
  111. janito/tools/adapters/local/search_text/pattern_utils.py +73 -73
  112. janito/tools/adapters/local/search_text/traverse_directory.py +145 -145
  113. janito/tools/adapters/local/validate_file_syntax/__init__.py +1 -1
  114. janito/tools/adapters/local/validate_file_syntax/core.py +106 -106
  115. janito/tools/adapters/local/validate_file_syntax/css_validator.py +35 -35
  116. janito/tools/adapters/local/validate_file_syntax/html_validator.py +93 -93
  117. janito/tools/adapters/local/validate_file_syntax/js_validator.py +27 -27
  118. janito/tools/adapters/local/validate_file_syntax/json_validator.py +6 -6
  119. janito/tools/adapters/local/validate_file_syntax/markdown_validator.py +109 -109
  120. janito/tools/adapters/local/validate_file_syntax/ps1_validator.py +32 -32
  121. janito/tools/adapters/local/validate_file_syntax/python_validator.py +5 -5
  122. janito/tools/adapters/local/validate_file_syntax/xml_validator.py +11 -11
  123. janito/tools/adapters/local/validate_file_syntax/yaml_validator.py +6 -6
  124. janito/tools/adapters/local/view_file.py +168 -167
  125. janito/tools/inspect_registry.py +17 -17
  126. janito/tools/outline_file.bak.zip +0 -0
  127. janito/tools/permissions.py +45 -0
  128. janito/tools/permissions_parse.py +12 -0
  129. janito/tools/tool_base.py +118 -105
  130. janito/tools/tool_events.py +58 -58
  131. janito/tools/tool_run_exception.py +12 -12
  132. janito/tools/tool_use_tracker.py +81 -81
  133. janito/tools/tool_utils.py +43 -45
  134. janito/tools/tools_adapter.py +25 -20
  135. janito/tools/tools_schema.py +104 -104
  136. {janito-2.3.0.dist-info → janito-2.4.0.dist-info}/METADATA +425 -388
  137. janito-2.4.0.dist-info/RECORD +195 -0
  138. janito/agent/templates/profiles/system_prompt_template_base_pt.txt.j2 +0 -13
  139. janito/agent/templates/profiles/system_prompt_template_main.txt.j2 +0 -37
  140. janito/cli/chat_mode/shell/commands/edit.py +0 -25
  141. janito/cli/chat_mode/shell/commands/exec.py +0 -27
  142. janito/cli/chat_mode/shell/commands/termweb_log.py +0 -92
  143. janito/cli/termweb_starter.py +0 -122
  144. janito/termweb/app.py +0 -95
  145. janito/version.py +0 -4
  146. janito-2.3.0.dist-info/RECORD +0 -181
  147. {janito-2.3.0.dist-info → janito-2.4.0.dist-info}/WHEEL +0 -0
  148. {janito-2.3.0.dist-info → janito-2.4.0.dist-info}/entry_points.txt +0 -0
  149. {janito-2.3.0.dist-info → janito-2.4.0.dist-info}/licenses/LICENSE +0 -0
  150. {janito-2.3.0.dist-info → janito-2.4.0.dist-info}/top_level.txt +0 -0
janito/tools/tool_base.py CHANGED
@@ -1,105 +1,118 @@
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
- class ToolBase:
6
- """
7
- Base class for all tools in the janito project.
8
- Extend this class to implement specific tool functionality.
9
- """
10
- provides_execution = False # Indicates if the tool provides execution capability (default: False)
11
-
12
- def __init__(self, name=None, event_bus=None):
13
- self.name = name or self.__class__.__name__
14
- self._event_bus = event_bus or default_event_bus
15
-
16
- @property
17
- def event_bus(self):
18
- return self._event_bus
19
-
20
- @event_bus.setter
21
- def event_bus(self, bus):
22
- self._event_bus = bus or default_event_bus
23
-
24
- def report_action(self, message: str, action: ReportAction, context: dict = None):
25
- """
26
- Report that a tool action is starting. This should be the first reporting call for every tool action.
27
- """
28
- self._event_bus.publish(
29
- ReportEvent(
30
- subtype=ReportSubtype.ACTION_INFO,
31
- message=" " + message,
32
- action=action,
33
- tool=self.name,
34
- context=context,
35
- )
36
- )
37
-
38
- def report_info(self, message: str, context: dict = None):
39
- self._event_bus.publish(
40
- ReportEvent(
41
- subtype=ReportSubtype.ACTION_INFO,
42
- message=message,
43
- action=None,
44
- tool=self.name,
45
- context=context,
46
- )
47
- )
48
-
49
- def report_error(self, message: str, context: dict = None):
50
- self._event_bus.publish(
51
- ReportEvent(
52
- subtype=ReportSubtype.ERROR,
53
- message=message,
54
- action=None,
55
- tool=self.name,
56
- context=context,
57
- )
58
- )
59
-
60
- def report_success(self, message: str, context: dict = None):
61
- self._event_bus.publish(
62
- ReportEvent(
63
- subtype=ReportSubtype.SUCCESS,
64
- message=message,
65
- action=None,
66
- tool=self.name,
67
- context=context,
68
- )
69
- )
70
-
71
- def report_warning(self, message: str, context: dict = None):
72
- self._event_bus.publish(
73
- ReportEvent(
74
- subtype=ReportSubtype.WARNING,
75
- message=message,
76
- action=None,
77
- tool=self.name,
78
- context=context,
79
- )
80
- )
81
-
82
- def report_stdout(self, message: str, context: dict = None):
83
- self._event_bus.publish(
84
- ReportEvent(
85
- subtype=ReportSubtype.STDOUT,
86
- message=message,
87
- action=None,
88
- tool=self.name,
89
- context=context,
90
- )
91
- )
92
-
93
- def report_stderr(self, message: str, context: dict = None):
94
- self._event_bus.publish(
95
- ReportEvent(
96
- subtype=ReportSubtype.STDERR,
97
- message=message,
98
- action=None,
99
- tool=self.name,
100
- context=context,
101
- )
102
- )
103
-
104
- def run(self, *args, **kwargs):
105
- 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
+ class ToolPermissions(namedtuple('ToolPermissions', ['read', 'write', 'execute'])):
8
+ __slots__ = ()
9
+ def __new__(cls, read=False, write=False, execute=False):
10
+ return super().__new__(cls, read, write, execute)
11
+
12
+ def __repr__(self):
13
+ return f"ToolPermissions(read={self.read}, write={self.write}, execute={self.execute})"
14
+
15
+
16
+ class ToolBase:
17
+ """
18
+ Base class for all tools in the janito project.
19
+ Extend this class to implement specific tool functionality.
20
+ """
21
+ permissions: 'ToolPermissions' = None # Required: must be set by subclasses
22
+
23
+ def __init__(self, name=None, event_bus=None):
24
+ if self.permissions is None or not isinstance(self.permissions, ToolPermissions):
25
+ raise ValueError(f"Tool '{self.__class__.__name__}' must define a 'permissions' attribute of type ToolPermissions.")
26
+ self.name = name or self.__class__.__name__
27
+ self._event_bus = event_bus or default_event_bus
28
+
29
+ @property
30
+ def event_bus(self):
31
+ return self._event_bus
32
+
33
+ @event_bus.setter
34
+ def event_bus(self, bus):
35
+ self._event_bus = bus or default_event_bus
36
+
37
+ def report_action(self, message: str, action: ReportAction, context: dict = None):
38
+ """
39
+ Report that a tool action is starting. This should be the first reporting call for every tool action.
40
+ """
41
+ self._event_bus.publish(
42
+ ReportEvent(
43
+ subtype=ReportSubtype.ACTION_INFO,
44
+ message=" " + message,
45
+ action=action,
46
+ tool=self.name,
47
+ context=context,
48
+ )
49
+ )
50
+
51
+ def report_info(self, message: str, context: dict = None):
52
+ self._event_bus.publish(
53
+ ReportEvent(
54
+ subtype=ReportSubtype.ACTION_INFO,
55
+ message=message,
56
+ action=None,
57
+ tool=self.name,
58
+ context=context,
59
+ )
60
+ )
61
+
62
+ def report_error(self, message: str, context: dict = None):
63
+ self._event_bus.publish(
64
+ ReportEvent(
65
+ subtype=ReportSubtype.ERROR,
66
+ message=message,
67
+ action=None,
68
+ tool=self.name,
69
+ context=context,
70
+ )
71
+ )
72
+
73
+ def report_success(self, message: str, context: dict = None):
74
+ self._event_bus.publish(
75
+ ReportEvent(
76
+ subtype=ReportSubtype.SUCCESS,
77
+ message=message,
78
+ action=None,
79
+ tool=self.name,
80
+ context=context,
81
+ )
82
+ )
83
+
84
+ def report_warning(self, message: str, context: dict = None):
85
+ self._event_bus.publish(
86
+ ReportEvent(
87
+ subtype=ReportSubtype.WARNING,
88
+ message=message,
89
+ action=None,
90
+ tool=self.name,
91
+ context=context,
92
+ )
93
+ )
94
+
95
+ def report_stdout(self, message: str, context: dict = None):
96
+ self._event_bus.publish(
97
+ ReportEvent(
98
+ subtype=ReportSubtype.STDOUT,
99
+ message=message,
100
+ action=None,
101
+ tool=self.name,
102
+ context=context,
103
+ )
104
+ )
105
+
106
+ def report_stderr(self, message: str, context: dict = None):
107
+ self._event_bus.publish(
108
+ ReportEvent(
109
+ subtype=ReportSubtype.STDERR,
110
+ message=message,
111
+ action=None,
112
+ tool=self.name,
113
+ context=context,
114
+ )
115
+ )
116
+
117
+ def run(self, *args, **kwargs):
118
+ raise NotImplementedError("Subclasses must implement the run method.")
@@ -1,58 +1,58 @@
1
- import attr
2
- from typing import Any, ClassVar
3
- from janito.event_bus.event import Event
4
-
5
-
6
- @attr.s(auto_attribs=True, kw_only=True)
7
- class ToolEvent(Event):
8
- """
9
- Base class for events related to tool calls (external or internal tools).
10
- Includes tool name and request ID for correlation.
11
- """
12
-
13
- category: ClassVar[str] = "tool"
14
- tool_name: str
15
- request_id: str
16
-
17
-
18
- @attr.s(auto_attribs=True, kw_only=True)
19
- class ToolCallStarted(ToolEvent):
20
- """
21
- Event indicating that a tool call has started.
22
- Contains the arguments passed to the tool.
23
- """
24
-
25
- arguments: Any
26
-
27
-
28
- @attr.s(auto_attribs=True, kw_only=True)
29
- class ToolCallFinished(ToolEvent):
30
- """
31
- Event indicating that a tool call has finished.
32
- Contains the result returned by the tool.
33
- """
34
-
35
- result: Any
36
-
37
-
38
- @attr.s(auto_attribs=True, kw_only=True)
39
- class ToolRunError(ToolEvent):
40
- """
41
- Event indicating that an error occurred during tool execution (for event bus, not exception handling).
42
- """
43
-
44
- error: str
45
- exception: Exception = None
46
- arguments: Any = None
47
-
48
-
49
- @attr.s(auto_attribs=True, kw_only=True)
50
- class ToolCallError(ToolEvent):
51
- """
52
- Event indicating that the tool could not be called (e.g., tool not found, invalid arguments, or invocation failure).
53
- This is distinct from ToolRunError, which is for errors during execution after the tool has started running.
54
- """
55
-
56
- error: str
57
- exception: Exception = None
58
- arguments: Any = None
1
+ import attr
2
+ from typing import Any, ClassVar
3
+ from janito.event_bus.event import Event
4
+
5
+
6
+ @attr.s(auto_attribs=True, kw_only=True)
7
+ class ToolEvent(Event):
8
+ """
9
+ Base class for events related to tool calls (external or internal tools).
10
+ Includes tool name and request ID for correlation.
11
+ """
12
+
13
+ category: ClassVar[str] = "tool"
14
+ tool_name: str
15
+ request_id: str
16
+
17
+
18
+ @attr.s(auto_attribs=True, kw_only=True)
19
+ class ToolCallStarted(ToolEvent):
20
+ """
21
+ Event indicating that a tool call has started.
22
+ Contains the arguments passed to the tool.
23
+ """
24
+
25
+ arguments: Any
26
+
27
+
28
+ @attr.s(auto_attribs=True, kw_only=True)
29
+ class ToolCallFinished(ToolEvent):
30
+ """
31
+ Event indicating that a tool call has finished.
32
+ Contains the result returned by the tool.
33
+ """
34
+
35
+ result: Any
36
+
37
+
38
+ @attr.s(auto_attribs=True, kw_only=True)
39
+ class ToolRunError(ToolEvent):
40
+ """
41
+ Event indicating that an error occurred during tool execution (for event bus, not exception handling).
42
+ """
43
+
44
+ error: str
45
+ exception: Exception = None
46
+ arguments: Any = None
47
+
48
+
49
+ @attr.s(auto_attribs=True, kw_only=True)
50
+ class ToolCallError(ToolEvent):
51
+ """
52
+ Event indicating that the tool could not be called (e.g., tool not found, invalid arguments, or invocation failure).
53
+ This is distinct from ToolRunError, which is for errors during execution after the tool has started running.
54
+ """
55
+
56
+ error: str
57
+ exception: Exception = None
58
+ arguments: Any = None
@@ -1,12 +1,12 @@
1
- class ToolRunException(Exception):
2
- """
3
- Exception raised when a tool runs but fails due to an internal error or runtime exception.
4
- This is distinct from ToolRunError event, which is for event bus notification.
5
- """
6
-
7
- def __init__(self, tool_name, error, arguments=None, exception=None):
8
- self.tool_name = tool_name
9
- self.error = error
10
- self.arguments = arguments
11
- self.original_exception = exception
12
- super().__init__(f"ToolRunException: {tool_name}: {error}")
1
+ class ToolRunException(Exception):
2
+ """
3
+ Exception raised when a tool runs but fails due to an internal error or runtime exception.
4
+ This is distinct from ToolRunError event, which is for event bus notification.
5
+ """
6
+
7
+ def __init__(self, tool_name, error, arguments=None, exception=None):
8
+ self.tool_name = tool_name
9
+ self.error = error
10
+ self.arguments = arguments
11
+ self.original_exception = exception
12
+ super().__init__(f"ToolRunException: {tool_name}: {error}")
@@ -1,81 +1,81 @@
1
- import threading
2
- import os
3
- from typing import Any, Dict, List
4
-
5
-
6
- def normalize_path(path: str) -> str:
7
- if not isinstance(path, str):
8
- return path
9
- return os.path.normcase(os.path.abspath(path))
10
-
11
-
12
- class ToolUseTracker:
13
- _instance = None
14
- _lock = threading.Lock()
15
-
16
- def __new__(cls):
17
- if not cls._instance:
18
- with cls._lock:
19
- if not cls._instance:
20
- cls._instance = super().__new__(cls)
21
- cls._instance._history = []
22
- return cls._instance
23
-
24
- def record(self, tool_name: str, params: Dict[str, Any], result: Any = None):
25
- # Normalize file_path in params if present
26
- norm_params = params.copy()
27
- if "file_path" in norm_params:
28
- norm_params["file_path"] = normalize_path(norm_params["file_path"])
29
- self._history.append(
30
- {"tool": tool_name, "params": norm_params, "result": result}
31
- )
32
-
33
- def get_history(self) -> List[Dict[str, Any]]:
34
- return list(self._history)
35
-
36
- def get_operations_on_file(self, file_path: str) -> List[Dict[str, Any]]:
37
- norm_file_path = normalize_path(file_path)
38
- ops = []
39
- for entry in self._history:
40
- params = entry["params"]
41
- # Normalize any string param values for comparison
42
- for v in params.values():
43
- if isinstance(v, str) and normalize_path(v) == norm_file_path:
44
- ops.append(entry)
45
- break
46
- return ops
47
-
48
- def file_fully_read(self, file_path: str) -> bool:
49
- norm_file_path = normalize_path(file_path)
50
- for entry in self._history:
51
- if entry["tool"] == "view_file":
52
- params = entry["params"]
53
- if (
54
- "file_path" in params
55
- and normalize_path(params["file_path"]) == norm_file_path
56
- ):
57
- # If both from_line and to_line are None, full file was read
58
- if (
59
- params.get("from_line") is None
60
- and params.get("to_line") is None
61
- ):
62
- return True
63
- return False
64
-
65
- def last_operation_is_full_read_or_replace(self, file_path: str) -> bool:
66
- ops = self.get_operations_on_file(file_path)
67
- if not ops:
68
- return False
69
- last = ops[-1]
70
- if last["tool"] == "view_file":
71
- params = last["params"]
72
- if params.get("from_line") is None and params.get("to_line") is None:
73
- return True
74
- return False
75
-
76
- def clear_history(self):
77
- self._history.clear()
78
-
79
- @classmethod
80
- def instance(cls):
81
- return cls()
1
+ import threading
2
+ import os
3
+ from typing import Any, Dict, List
4
+
5
+
6
+ def normalize_path(path: str) -> str:
7
+ if not isinstance(path, str):
8
+ return path
9
+ return os.path.normcase(os.path.abspath(path))
10
+
11
+
12
+ class ToolUseTracker:
13
+ _instance = None
14
+ _lock = threading.Lock()
15
+
16
+ def __new__(cls):
17
+ if not cls._instance:
18
+ with cls._lock:
19
+ if not cls._instance:
20
+ cls._instance = super().__new__(cls)
21
+ cls._instance._history = []
22
+ return cls._instance
23
+
24
+ def record(self, tool_name: str, params: Dict[str, Any], result: Any = None):
25
+ # Normalize file_path in params if present
26
+ norm_params = params.copy()
27
+ if "file_path" in norm_params:
28
+ norm_params["file_path"] = normalize_path(norm_params["file_path"])
29
+ self._history.append(
30
+ {"tool": tool_name, "params": norm_params, "result": result}
31
+ )
32
+
33
+ def get_history(self) -> List[Dict[str, Any]]:
34
+ return list(self._history)
35
+
36
+ def get_operations_on_file(self, file_path: str) -> List[Dict[str, Any]]:
37
+ norm_file_path = normalize_path(file_path)
38
+ ops = []
39
+ for entry in self._history:
40
+ params = entry["params"]
41
+ # Normalize any string param values for comparison
42
+ for v in params.values():
43
+ if isinstance(v, str) and normalize_path(v) == norm_file_path:
44
+ ops.append(entry)
45
+ break
46
+ return ops
47
+
48
+ def file_fully_read(self, file_path: str) -> bool:
49
+ norm_file_path = normalize_path(file_path)
50
+ for entry in self._history:
51
+ if entry["tool"] == "view_file":
52
+ params = entry["params"]
53
+ if (
54
+ "file_path" in params
55
+ and normalize_path(params["file_path"]) == norm_file_path
56
+ ):
57
+ # If both from_line and to_line are None, full file was read
58
+ if (
59
+ params.get("from_line") is None
60
+ and params.get("to_line") is None
61
+ ):
62
+ return True
63
+ return False
64
+
65
+ def last_operation_is_full_read_or_replace(self, file_path: str) -> bool:
66
+ ops = self.get_operations_on_file(file_path)
67
+ if not ops:
68
+ return False
69
+ last = ops[-1]
70
+ if last["tool"] == "view_file":
71
+ params = last["params"]
72
+ if params.get("from_line") is None and params.get("to_line") is None:
73
+ return True
74
+ return False
75
+
76
+ def clear_history(self):
77
+ self._history.clear()
78
+
79
+ @classmethod
80
+ def instance(cls):
81
+ return cls()
@@ -1,45 +1,43 @@
1
- """
2
- Utility functions for the janito project.
3
- Add your shared helper functions here.
4
- """
5
-
6
- import os
7
- import urllib.parse
8
-
9
-
10
- def example_utility_function(x):
11
- """A simple example utility function."""
12
- return f"Processed: {x}"
13
-
14
-
15
- def display_path(path):
16
- """
17
- Returns a display-friendly path. Injects an ANSI hyperlink to a local web file viewer using a hardcoded port.
18
- Args:
19
- path (str): Path to display.
20
- Returns:
21
- str: Display path, as an ANSI hyperlink.
22
- """
23
- from janito.cli.config import get_termweb_port
24
-
25
- port = get_termweb_port()
26
- if os.path.isabs(path):
27
- cwd = os.path.abspath(os.getcwd())
28
- abs_path = os.path.abspath(path)
29
- # Check if the absolute path is within the current working directory
30
- if abs_path.startswith(cwd + os.sep):
31
- disp = os.path.relpath(abs_path, cwd)
32
- else:
33
- disp = path
34
- else:
35
- disp = os.path.relpath(path)
36
- url = f"http://localhost:{port}/?path={urllib.parse.quote(path)}"
37
- # Use Rich markup for hyperlinks
38
- return f"[link={url}]{disp}[/link]"
39
-
40
-
41
- def pluralize(word: str, count: int) -> str:
42
- """Return the pluralized form of word if count != 1, unless word already ends with 's'."""
43
- if count == 1 or word.endswith("s"):
44
- return word
45
- return word + "s"
1
+ """
2
+ Utility functions for the janito project.
3
+ Add your shared helper functions here.
4
+ """
5
+
6
+ import os
7
+ import urllib.parse
8
+
9
+
10
+ def example_utility_function(x):
11
+ """A simple example utility function."""
12
+ return f"Processed: {x}"
13
+
14
+
15
+ def display_path(path):
16
+ """
17
+ Returns a display-friendly path. Injects an ANSI hyperlink to a local web file viewer using a hardcoded port.
18
+ Args:
19
+ path (str): Path to display.
20
+ Returns:
21
+ str: Display path, as an ANSI hyperlink.
22
+ """
23
+
24
+ port = 8088
25
+ if os.path.isabs(path):
26
+ cwd = os.path.abspath(os.getcwd())
27
+ abs_path = os.path.abspath(path)
28
+ # Check if the absolute path is within the current working directory
29
+ if abs_path.startswith(cwd + os.sep):
30
+ disp = os.path.relpath(abs_path, cwd)
31
+ else:
32
+ disp = path
33
+ else:
34
+ disp = os.path.relpath(path)
35
+ # URL injection removed; just return display path
36
+ return disp
37
+
38
+
39
+ def pluralize(word: str, count: int) -> str:
40
+ """Return the pluralized form of word if count != 1, unless word already ends with 's'."""
41
+ if count == 1 or word.endswith("s"):
42
+ return word
43
+ return word + "s"