janito 1.8.0__py3-none-any.whl → 1.9.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 (119) hide show
  1. janito/__init__.py +1 -1
  2. janito/agent/config_defaults.py +23 -0
  3. janito/agent/config_utils.py +0 -9
  4. janito/agent/conversation.py +31 -9
  5. janito/agent/conversation_api.py +32 -2
  6. janito/agent/conversation_history.py +53 -0
  7. janito/agent/conversation_tool_calls.py +11 -8
  8. janito/agent/openai_client.py +11 -3
  9. janito/agent/openai_schema_generator.py +9 -6
  10. janito/agent/providers.py +77 -0
  11. janito/agent/rich_message_handler.py +1 -1
  12. janito/agent/templates/profiles/system_prompt_template_base.txt.j2 +8 -8
  13. janito/agent/tool_executor.py +18 -10
  14. janito/agent/tool_use_tracker.py +16 -0
  15. janito/agent/tools/__init__.py +7 -9
  16. janito/agent/tools/create_directory.py +7 -6
  17. janito/agent/tools/create_file.py +29 -54
  18. janito/agent/tools/delete_text_in_file.py +97 -0
  19. janito/agent/tools/fetch_url.py +11 -3
  20. janito/agent/tools/find_files.py +37 -25
  21. janito/agent/tools/get_file_outline/__init__.py +1 -0
  22. janito/agent/tools/{outline_file/__init__.py → get_file_outline/core.py} +12 -15
  23. janito/agent/tools/get_file_outline/python_outline.py +134 -0
  24. janito/agent/tools/{search_outline.py → get_file_outline/search_outline.py} +9 -0
  25. janito/agent/tools/get_lines.py +15 -11
  26. janito/agent/tools/move_file.py +10 -11
  27. janito/agent/tools/remove_directory.py +2 -2
  28. janito/agent/tools/remove_file.py +11 -13
  29. janito/agent/tools/replace_file.py +62 -0
  30. janito/agent/tools/replace_text_in_file.py +3 -3
  31. janito/agent/tools/run_bash_command.py +3 -7
  32. janito/agent/tools/run_powershell_command.py +39 -28
  33. janito/agent/tools/run_python_command.py +3 -5
  34. janito/agent/tools/search_text.py +10 -14
  35. janito/agent/tools/validate_file_syntax/__init__.py +1 -0
  36. janito/agent/tools/validate_file_syntax/core.py +92 -0
  37. janito/agent/tools/validate_file_syntax/css_validator.py +35 -0
  38. janito/agent/tools/validate_file_syntax/html_validator.py +77 -0
  39. janito/agent/tools/validate_file_syntax/js_validator.py +27 -0
  40. janito/agent/tools/validate_file_syntax/json_validator.py +6 -0
  41. janito/agent/tools/validate_file_syntax/markdown_validator.py +66 -0
  42. janito/agent/tools/validate_file_syntax/ps1_validator.py +32 -0
  43. janito/agent/tools/validate_file_syntax/python_validator.py +5 -0
  44. janito/agent/tools/validate_file_syntax/xml_validator.py +11 -0
  45. janito/agent/tools/validate_file_syntax/yaml_validator.py +6 -0
  46. janito/agent/tools_utils/__init__.py +1 -0
  47. janito/agent/tools_utils/dir_walk_utils.py +23 -0
  48. janito/agent/{tools/outline_file → tools_utils}/formatting.py +5 -2
  49. janito/agent/{tools → tools_utils}/gitignore_utils.py +0 -3
  50. janito/agent/tools_utils/utils.py +30 -0
  51. janito/cli/_livereload_log_utils.py +13 -0
  52. janito/cli/arg_parser.py +45 -3
  53. janito/cli/{runner/cli_main.py → cli_main.py} +120 -20
  54. janito/cli/livereload_starter.py +60 -0
  55. janito/cli/main.py +110 -21
  56. janito/cli/one_shot.py +66 -0
  57. janito/cli/termweb_starter.py +2 -2
  58. janito/livereload/app.py +25 -0
  59. janito/rich_utils.py +0 -22
  60. janito/{cli_chat_shell → shell}/commands/__init__.py +18 -11
  61. janito/{cli_chat_shell → shell}/commands/config.py +4 -4
  62. janito/shell/commands/conversation_restart.py +72 -0
  63. janito/shell/commands/edit.py +21 -0
  64. janito/shell/commands/history_view.py +18 -0
  65. janito/shell/commands/livelogs.py +40 -0
  66. janito/{cli_chat_shell → shell}/commands/prompt.py +10 -6
  67. janito/shell/commands/session.py +32 -0
  68. janito/{cli_chat_shell → shell}/commands/session_control.py +2 -7
  69. janito/{cli_chat_shell → shell}/commands/sum.py +6 -6
  70. janito/{cli_chat_shell → shell}/commands/termweb_log.py +10 -10
  71. janito/shell/commands/tools.py +23 -0
  72. janito/{cli_chat_shell → shell}/commands/utility.py +5 -4
  73. janito/{cli_chat_shell → shell}/commands/verbose.py +1 -1
  74. janito/shell/commands.py +40 -0
  75. janito/shell/main.py +321 -0
  76. janito/{cli_chat_shell/shell_command_completer.py → shell/prompt/completer.py} +1 -1
  77. janito/{cli_chat_shell/chat_ui.py → shell/prompt/session_setup.py} +19 -5
  78. janito/{cli_chat_shell/session_manager.py → shell/session/manager.py} +53 -3
  79. janito/{cli_chat_shell/ui.py → shell/ui/interactive.py} +23 -15
  80. janito/termweb/app.py +3 -3
  81. janito/termweb/static/editor.css +146 -0
  82. janito/termweb/static/editor.css.bak +27 -0
  83. janito/termweb/static/editor.html +15 -213
  84. janito/termweb/static/editor.html.bak +16 -215
  85. janito/termweb/static/editor.js +209 -0
  86. janito/termweb/static/editor.js.bak +227 -0
  87. janito/termweb/static/index.html +2 -3
  88. janito/termweb/static/index.html.bak +2 -3
  89. janito/termweb/static/termweb.css.bak +33 -84
  90. janito/termweb/static/termweb.js +15 -34
  91. janito/termweb/static/termweb.js.bak +18 -36
  92. {janito-1.8.0.dist-info → janito-1.9.0.dist-info}/METADATA +6 -3
  93. janito-1.9.0.dist-info/RECORD +151 -0
  94. {janito-1.8.0.dist-info → janito-1.9.0.dist-info}/WHEEL +1 -1
  95. janito/agent/tools/dir_walk_utils.py +0 -16
  96. janito/agent/tools/memory.py +0 -48
  97. janito/agent/tools/outline_file/python_outline.py +0 -71
  98. janito/agent/tools/present_choices_test.py +0 -18
  99. janito/agent/tools/rich_live.py +0 -44
  100. janito/agent/tools/tools_utils.py +0 -56
  101. janito/agent/tools/utils.py +0 -33
  102. janito/agent/tools/validate_file_syntax.py +0 -163
  103. janito/cli_chat_shell/chat_loop.py +0 -163
  104. janito/cli_chat_shell/chat_state.py +0 -38
  105. janito/cli_chat_shell/commands/history_start.py +0 -37
  106. janito/cli_chat_shell/commands/session.py +0 -48
  107. janito-1.8.0.dist-info/RECORD +0 -127
  108. /janito/agent/tools/{outline_file → get_file_outline}/markdown_outline.py +0 -0
  109. /janito/cli/{runner/_termweb_log_utils.py → _termweb_log_utils.py} +0 -0
  110. /janito/cli/{runner/config.py → config_runner.py} +0 -0
  111. /janito/cli/{runner/formatting.py → formatting_runner.py} +0 -0
  112. /janito/{cli/runner → shell}/__init__.py +0 -0
  113. /janito/{cli_chat_shell → shell}/commands/lang.py +0 -0
  114. /janito/{cli_chat_shell → shell/prompt}/load_prompt.py +0 -0
  115. /janito/{cli_chat_shell/config_shell.py → shell/session/config.py} +0 -0
  116. /janito/{cli_chat_shell/__init__.py → shell/session/history.py} +0 -0
  117. {janito-1.8.0.dist-info → janito-1.9.0.dist-info}/entry_points.txt +0 -0
  118. {janito-1.8.0.dist-info → janito-1.9.0.dist-info}/licenses/LICENSE +0 -0
  119. {janito-1.8.0.dist-info → janito-1.9.0.dist-info}/top_level.txt +0 -0
janito/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "1.8.0"
1
+ __version__ = "1.9.0"
@@ -10,4 +10,27 @@ CONFIG_DEFAULTS = {
10
10
  "use_azure_openai": False,
11
11
  "azure_openai_api_version": "2023-05-15",
12
12
  "profile": "base",
13
+ "providers": {
14
+ "openai": {
15
+ "api_key": None,
16
+ "base_url": "https://api.openai.com/v1",
17
+ "default_model": "gpt-3.5-turbo",
18
+ },
19
+ "azureai": {
20
+ "api_key": None,
21
+ "base_url": "https://your-azure-endpoint.openai.azure.com/",
22
+ "api_version": "2023-05-15",
23
+ "default_model": "gpt-35-turbo",
24
+ },
25
+ "openrouterai": {
26
+ "api_key": None,
27
+ "base_url": "https://openrouter.ai/api/v1",
28
+ "default_model": "openrouter/cognitive",
29
+ },
30
+ "fireworksai": {
31
+ "api_key": None,
32
+ "base_url": "https://api.fireworks.ai/inference/v1",
33
+ "default_model": "accounts/fireworks/models/firefunction-v1",
34
+ },
35
+ },
13
36
  }
@@ -1,9 +0,0 @@
1
- def merge_configs(*configs):
2
- """
3
- Merge multiple config-like objects (with .all()) into one dict.
4
- Later configs override earlier ones.
5
- """
6
- merged = {}
7
- for cfg in configs:
8
- merged.update(cfg.all())
9
- return merged
@@ -30,7 +30,7 @@ class ConversationHandler:
30
30
  def handle_conversation(
31
31
  self,
32
32
  messages,
33
- max_rounds=50,
33
+ max_rounds=100,
34
34
  message_handler=None,
35
35
  verbose_response=False,
36
36
  spinner=False,
@@ -39,7 +39,15 @@ class ConversationHandler:
39
39
  stream=False,
40
40
  verbose_stream=False,
41
41
  ):
42
- if not messages:
42
+ from janito.agent.conversation_history import ConversationHistory
43
+
44
+ # Accept either ConversationHistory or a list for backward compatibility
45
+ if isinstance(messages, ConversationHistory):
46
+ history = messages
47
+ else:
48
+ history = ConversationHistory(messages)
49
+
50
+ if len(history) == 0:
43
51
  raise ValueError("No prompt provided in messages")
44
52
 
45
53
  resolved_max_tokens = max_tokens
@@ -66,7 +74,7 @@ class ConversationHandler:
66
74
  return get_openai_stream_response(
67
75
  self.client,
68
76
  self.model,
69
- messages,
77
+ history.get_messages(),
70
78
  resolved_max_tokens,
71
79
  verbose_stream=runtime_config.get("verbose_stream", False),
72
80
  message_handler=message_handler,
@@ -78,7 +86,10 @@ class ConversationHandler:
78
86
  # Non-streaming mode
79
87
  def api_call():
80
88
  return get_openai_response(
81
- self.client, self.model, messages, resolved_max_tokens
89
+ self.client,
90
+ self.model,
91
+ history.get_messages(),
92
+ resolved_max_tokens,
82
93
  )
83
94
 
84
95
  if spinner:
@@ -87,7 +98,6 @@ class ConversationHandler:
87
98
  )
88
99
  else:
89
100
  response = retry_api_call(api_call)
90
- print("[DEBUG] OpenAI API raw response:", repr(response))
91
101
  except NoToolSupportError:
92
102
  print(
93
103
  "⚠️ Endpoint does not support tool use. Proceeding in vanilla mode (tools disabled)."
@@ -142,10 +152,19 @@ class ConversationHandler:
142
152
  if message_handler is not None and choice.message.content:
143
153
  message_handler.handle_message(event)
144
154
  if not choice.message.tool_calls:
145
- agent_idx = len([m for m in messages if m.get("role") == "agent"])
155
+ agent_idx = len(
156
+ [m for m in history.get_messages() if m.get("role") == "agent"]
157
+ )
146
158
  self.usage_history.append(
147
159
  {"agent_index": agent_idx, "usage": usage_info}
148
160
  )
161
+ # Add assistant response to history
162
+ history.add_message(
163
+ {
164
+ "role": "assistant",
165
+ "content": choice.message.content,
166
+ }
167
+ )
149
168
  return {
150
169
  "content": choice.message.content,
151
170
  "usage": usage_info,
@@ -155,9 +174,12 @@ class ConversationHandler:
155
174
  tool_responses = handle_tool_calls(
156
175
  choice.message.tool_calls, message_handler=message_handler
157
176
  )
158
- agent_idx = len([m for m in messages if m.get("role") == "agent"])
177
+ agent_idx = len(
178
+ [m for m in history.get_messages() if m.get("role") == "agent"]
179
+ )
159
180
  self.usage_history.append({"agent_index": agent_idx, "usage": usage_info})
160
- messages.append(
181
+ # Add assistant response with tool calls
182
+ history.add_message(
161
183
  {
162
184
  "role": "assistant",
163
185
  "content": choice.message.content,
@@ -165,7 +187,7 @@ class ConversationHandler:
165
187
  }
166
188
  )
167
189
  for tool_response in tool_responses:
168
- messages.append(
190
+ history.add_message(
169
191
  {
170
192
  "role": "tool",
171
193
  "tool_call_id": tool_response["tool_call_id"],
@@ -10,18 +10,36 @@ from janito.agent.tool_registry import get_tool_schemas
10
10
  from janito.agent.conversation_exceptions import NoToolSupportError
11
11
 
12
12
 
13
+ def _sanitize_utf8_surrogates(obj):
14
+ """
15
+ Recursively sanitize a dict/list/string by replacing surrogate codepoints with the unicode replacement character.
16
+ """
17
+ if isinstance(obj, str):
18
+ # Encode with surrogatepass, then decode with 'utf-8', replacing errors
19
+ return obj.encode("utf-8", "replace").decode("utf-8", "replace")
20
+ elif isinstance(obj, dict):
21
+ return {k: _sanitize_utf8_surrogates(v) for k, v in obj.items()}
22
+ elif isinstance(obj, list):
23
+ return [_sanitize_utf8_surrogates(x) for x in obj]
24
+ else:
25
+ return obj
26
+
27
+
13
28
  def get_openai_response(
14
29
  client, model, messages, max_tokens, tools=None, tool_choice=None, temperature=None
15
30
  ):
16
31
  """Non-streaming OpenAI API call."""
32
+ messages = _sanitize_utf8_surrogates(messages)
33
+ from janito.agent.conversation_exceptions import ProviderError
34
+
17
35
  if runtime_config.get("vanilla_mode", False):
18
- return client.chat.completions.create(
36
+ response = client.chat.completions.create(
19
37
  model=model,
20
38
  messages=messages,
21
39
  max_tokens=max_tokens,
22
40
  )
23
41
  else:
24
- return client.chat.completions.create(
42
+ response = client.chat.completions.create(
25
43
  model=model,
26
44
  messages=messages,
27
45
  tools=tools or get_tool_schemas(),
@@ -29,6 +47,17 @@ def get_openai_response(
29
47
  temperature=temperature if temperature is not None else 0.2,
30
48
  max_tokens=max_tokens,
31
49
  )
50
+ # Explicitly check for missing or empty choices (API/LLM error)
51
+ if (
52
+ not hasattr(response, "choices")
53
+ or response.choices is None
54
+ or len(response.choices) == 0
55
+ ):
56
+ raise ProviderError(
57
+ "No choices in response; possible API or LLM error.",
58
+ {"code": 502, "raw_response": str(response)},
59
+ )
60
+ return response
32
61
 
33
62
 
34
63
  def get_openai_stream_response(
@@ -43,6 +72,7 @@ def get_openai_stream_response(
43
72
  message_handler=None,
44
73
  ):
45
74
  """Streaming OpenAI API call."""
75
+ messages = _sanitize_utf8_surrogates(messages)
46
76
  openai_args = dict(
47
77
  model=model,
48
78
  messages=messages,
@@ -0,0 +1,53 @@
1
+ from typing import List, Dict, Optional
2
+ import json
3
+
4
+
5
+ class ConversationHistory:
6
+ """
7
+ Manages the message history for a conversation, supporting OpenAI-style roles.
8
+ Intended to be used by ConversationHandler and chat loop for all history operations.
9
+ """
10
+
11
+ def __init__(self, messages: Optional[List[Dict]] = None):
12
+ self._messages = messages.copy() if messages else []
13
+
14
+ def add_message(self, message: Dict):
15
+ """Append a message dict to the history."""
16
+ self._messages.append(message)
17
+
18
+ def get_messages(self, role: Optional[str] = None) -> List[Dict]:
19
+ """
20
+ Return all messages, or only those matching a given role/type (e.g., 'assistant', 'user', 'tool').
21
+ If role is None, returns all messages.
22
+ """
23
+ if role is None:
24
+ return self._messages.copy()
25
+ return [msg for msg in self._messages if msg.get("role") == role]
26
+
27
+ def clear(self):
28
+ """Remove all messages from history."""
29
+ self._messages.clear()
30
+
31
+ def set_system_message(self, content: str):
32
+ """
33
+ Replace the first system prompt message, or insert if not present.
34
+ """
35
+ system_idx = next(
36
+ (i for i, m in enumerate(self._messages) if m.get("role") == "system"), None
37
+ )
38
+ system_msg = {"role": "system", "content": content}
39
+ if system_idx is not None:
40
+ self._messages[system_idx] = system_msg
41
+ else:
42
+ self._messages.insert(0, system_msg)
43
+
44
+ def to_json_file(self, path: str):
45
+ """Save the conversation history as a JSON file to the given path."""
46
+ with open(path, "w", encoding="utf-8") as f:
47
+ json.dump(self.get_messages(), f, indent=2, ensure_ascii=False)
48
+
49
+ def __len__(self):
50
+ return len(self._messages)
51
+
52
+ def __getitem__(self, idx):
53
+ return self._messages[idx]
@@ -2,6 +2,7 @@
2
2
  Helpers for handling tool calls in conversation.
3
3
  """
4
4
 
5
+ import json
5
6
  from janito.agent.tool_executor import ToolExecutor
6
7
  from janito.agent import tool_registry
7
8
  from .conversation_exceptions import MaxRoundsExceededError
@@ -19,18 +20,20 @@ def handle_tool_calls(tool_calls, message_handler=None):
19
20
  )
20
21
  tool_entry = tool_registry._tool_registry[tool_call.function.name]
21
22
  try:
22
- result = ToolExecutor(message_handler=message_handler).execute(
23
- tool_entry, tool_call
24
- )
25
- tool_responses.append({"tool_call_id": tool_call.id, "content": result})
26
- except TypeError as e:
27
- # Return the error as a tool result, asking to retry with correct params
28
- error_msg = str(e)
23
+ arguments = json.loads(tool_call.function.arguments)
24
+ except (TypeError, AttributeError, json.JSONDecodeError) as e:
25
+ error_msg = f"Invalid/malformed function parameters: {e}. Please retry with valid JSON arguments."
29
26
  tool_responses.append(
30
27
  {
31
28
  "tool_call_id": tool_call.id,
32
- "content": f"Tool execution error: {error_msg}. Please retry with the correct parameters.",
29
+ "content": error_msg,
33
30
  }
34
31
  )
32
+ tool_calls_made += 1
33
+ continue
34
+ result = ToolExecutor(message_handler=message_handler).execute(
35
+ tool_entry, tool_call, arguments
36
+ )
37
+ tool_responses.append({"tool_call_id": tool_call.id, "content": result})
35
38
  tool_calls_made += 1
36
39
  return tool_responses
@@ -64,18 +64,18 @@ class Agent:
64
64
 
65
65
  def chat(
66
66
  self,
67
- messages,
67
+ messages=None,
68
68
  message_handler=None,
69
69
  spinner=False,
70
70
  max_tokens=None,
71
- max_rounds=50,
71
+ max_rounds=100,
72
72
  stream=False,
73
73
  ):
74
74
  """
75
75
  Start a chat conversation with the agent.
76
76
 
77
77
  Args:
78
- messages: List of message dicts.
78
+ messages: ConversationHistory instance or None.
79
79
  message_handler: Optional handler for streaming or event messages.
80
80
  spinner: Show spinner during request.
81
81
  max_tokens: Max tokens for completion.
@@ -86,6 +86,14 @@ class Agent:
86
86
  If stream=True: generator yielding content chunks or events.
87
87
  """
88
88
  from janito.agent.runtime_config import runtime_config
89
+ from janito.agent.conversation_history import ConversationHistory
90
+
91
+ if messages is None:
92
+ messages = ConversationHistory()
93
+ elif not isinstance(messages, ConversationHistory):
94
+ raise TypeError(
95
+ "Agent.chat expects a ConversationHistory instance or None."
96
+ )
89
97
 
90
98
  max_retries = 5
91
99
  for attempt in range(1, max_retries + 1):
@@ -124,12 +124,15 @@ def generate_openai_function_schema(func, tool_name: str, tool_class=None):
124
124
  )
125
125
  properties = OrderedDict()
126
126
  required = []
127
- # Inject tool_call_reason as the first required parameter
128
- properties["tool_call_reason"] = {
129
- "type": "string",
130
- "description": "The reason or context for why this tool is being called. This is required for traceability.",
131
- }
132
- required.append("tool_call_reason")
127
+ # Inject tool_call_reason as the first required parameter, unless --ntt is set
128
+ from janito.agent.runtime_config import runtime_config
129
+
130
+ if not runtime_config.get("no_tools_tracking", False):
131
+ properties["tool_call_reason"] = {
132
+ "type": "string",
133
+ "description": "The reason or context for why this tool is being called. This is required for traceability.",
134
+ }
135
+ required.append("tool_call_reason")
133
136
  for name, param in sig.parameters.items():
134
137
  if name == "self":
135
138
  continue
@@ -0,0 +1,77 @@
1
+ """Providers module: defines LLM provider interfaces and implementations."""
2
+
3
+ from abc import ABC, abstractmethod
4
+
5
+
6
+ class Provider(ABC):
7
+ """Abstract base class for LLM providers."""
8
+
9
+ def __init__(self, config: dict):
10
+ self.config = config
11
+
12
+ @abstractmethod
13
+ def create_client(self):
14
+ """Instantiate and return the provider-specific client."""
15
+ pass
16
+
17
+ @abstractmethod
18
+ def get_default_model(self) -> str:
19
+ """Return the default model for this provider."""
20
+ pass
21
+
22
+
23
+ class OpenAIProvider(Provider):
24
+ def create_client(self):
25
+ from openai import OpenAI
26
+
27
+ return OpenAI(
28
+ base_url=self.config.get("base_url", "https://api.openai.com/v1"),
29
+ api_key=self.config["api_key"],
30
+ )
31
+
32
+ def get_default_model(self) -> str:
33
+ return self.config.get("default_model", "gpt-3.5-turbo")
34
+
35
+
36
+ class AzureAIProvider(Provider):
37
+ def create_client(self):
38
+ from openai import AzureOpenAI
39
+
40
+ return AzureOpenAI(
41
+ api_key=self.config["api_key"],
42
+ azure_endpoint=self.config["base_url"],
43
+ api_version=self.config.get("api_version", "2023-05-15"),
44
+ )
45
+
46
+ def get_default_model(self) -> str:
47
+ return self.config.get("default_model", "gpt-35-turbo")
48
+
49
+
50
+ class OpenrouterAIProvider(Provider):
51
+ def create_client(self):
52
+ from openai import OpenAI
53
+
54
+ return OpenAI(
55
+ base_url=self.config.get("base_url", "https://openrouter.ai/api/v1"),
56
+ api_key=self.config["api_key"],
57
+ )
58
+
59
+ def get_default_model(self) -> str:
60
+ return self.config.get("default_model", "openrouter/cognitive")
61
+
62
+
63
+ class FireworksAIProvider(Provider):
64
+ def create_client(self):
65
+ from openai import OpenAI
66
+
67
+ return OpenAI(
68
+ base_url=self.config.get(
69
+ "base_url", "https://api.fireworks.ai/inference/v1"
70
+ ),
71
+ api_key=self.config["api_key"],
72
+ )
73
+
74
+ def get_default_model(self) -> str:
75
+ return self.config.get(
76
+ "default_model", "accounts/fireworks/models/firefunction-v1"
77
+ )
@@ -38,7 +38,7 @@ class RichMessageHandler(MessageHandlerProtocol):
38
38
  if msg_type == "content":
39
39
  self.console.print(Markdown(message))
40
40
  elif msg_type == "info":
41
- self.console.print(message, style="cyan", end="")
41
+ self.console.print(f" {message}", style="cyan", end="")
42
42
  elif msg_type == "success":
43
43
  self.console.print(message, style="bold green", end="\n")
44
44
  elif msg_type == "error":
@@ -1,14 +1,14 @@
1
- You are acting as: {{ role }}
1
+ You are: {{ role }}
2
2
 
3
- You have access for development purposes to the following environment:
3
+ You will be developing and testing in the following environment:
4
4
  Platform: {{ platform }}
5
5
  Python version: {{ python_version }}
6
6
  Shell/Environment: {{ shell_info }}
7
7
 
8
- Answer according to the following guidelines:
9
- - Start by searching for text/files in the project.
8
+ Respond according to the following guidelines:
9
+ - Before answering check for files in the current project that might be related to the question
10
10
  - Before using your namespace functions, provide a concise explanation.
11
- - Prefer making localized edits using string replacements. If the required change is extensive, replace the entire file instead.
12
- - When locating code lines to change, consider the previous lines content for indentation precise replacement.
13
- - After edting files, validate them.
14
-
11
+ - When you are likely to need the entire file content, get the full content by omitting range numbers
12
+ - Do not provide the code in the messages, use the namespace functions to provide the cocde changes.
13
+ - Prefer making localized edits using string replacements. If the required change is extensive, replace the entire file instead, provide full content without placeholders.
14
+ - When locating code lines to change, check the surrounding lines content for indentation precise replacement.
@@ -3,7 +3,6 @@
3
3
  ToolExecutor: Responsible for executing tools, validating arguments, handling errors, and reporting progress.
4
4
  """
5
5
 
6
- import json
7
6
  from janito.i18n import tr
8
7
  import inspect
9
8
  from janito.agent.tool_base import ToolBase
@@ -14,15 +13,18 @@ class ToolExecutor:
14
13
  def __init__(self, message_handler=None):
15
14
  self.message_handler = message_handler
16
15
 
17
- def execute(self, tool_entry, tool_call):
16
+ def execute(self, tool_entry, tool_call, arguments):
18
17
  import uuid
19
18
 
20
19
  call_id = getattr(tool_call, "id", None) or str(uuid.uuid4())
21
20
  func = tool_entry["function"]
22
- args = json.loads(tool_call.function.arguments)
23
- tool_call_reason = args.pop(
24
- "tool_call_reason", None
25
- ) # Extract and remove 'tool_call_reason' if present
21
+ args = arguments
22
+ if runtime_config.get("no_tools_tracking", False):
23
+ tool_call_reason = None
24
+ else:
25
+ tool_call_reason = args.pop(
26
+ "tool_call_reason", None
27
+ ) # Extract and remove 'tool_call_reason' if present
26
28
  # Record tool usage
27
29
  try:
28
30
  from janito.agent.tool_use_tracker import ToolUseTracker
@@ -61,7 +63,7 @@ class ToolExecutor:
61
63
  "call_id": call_id,
62
64
  "arguments": args,
63
65
  }
64
- if tool_call_reason:
66
+ if tool_call_reason and not runtime_config.get("no_tools_tracking", False):
65
67
  event["tool_call_reason"] = tool_call_reason
66
68
  self.message_handler.handle_message(event)
67
69
  # Argument validation
@@ -77,7 +79,9 @@ class ToolExecutor:
77
79
  "call_id": call_id,
78
80
  "error": error_msg,
79
81
  }
80
- if tool_call_reason:
82
+ if tool_call_reason and not runtime_config.get(
83
+ "no_tools_tracking", False
84
+ ):
81
85
  error_event["tool_call_reason"] = tool_call_reason
82
86
  self.message_handler.handle_message(error_event)
83
87
  raise TypeError(error_msg)
@@ -91,7 +95,9 @@ class ToolExecutor:
91
95
  "call_id": call_id,
92
96
  "result": result,
93
97
  }
94
- if tool_call_reason:
98
+ if tool_call_reason and not runtime_config.get(
99
+ "no_tools_tracking", False
100
+ ):
95
101
  result_event["tool_call_reason"] = tool_call_reason
96
102
  self.message_handler.handle_message(result_event)
97
103
  return result
@@ -103,7 +109,9 @@ class ToolExecutor:
103
109
  "call_id": call_id,
104
110
  "error": str(e),
105
111
  }
106
- if tool_call_reason:
112
+ if tool_call_reason and not runtime_config.get(
113
+ "no_tools_tracking", False
114
+ ):
107
115
  error_event["tool_call_reason"] = tool_call_reason
108
116
  self.message_handler.handle_message(error_event)
109
117
  raise
@@ -41,6 +41,22 @@ class ToolUseTracker:
41
41
  return True
42
42
  return False
43
43
 
44
+ def last_operation_is_full_read_or_replace(self, file_path: str) -> bool:
45
+ ops = self.get_operations_on_file(file_path)
46
+ if not ops:
47
+ return False
48
+ last = ops[-1]
49
+ if last["tool"] == "replace_file":
50
+ return True
51
+ if last["tool"] == "get_lines":
52
+ params = last["params"]
53
+ if params.get("from_line") is None and params.get("to_line") is None:
54
+ return True
55
+ return False
56
+
57
+ def clear_history(self):
58
+ self._history.clear()
59
+
44
60
  @classmethod
45
61
  def instance(cls):
46
62
  return cls()
@@ -1,17 +1,17 @@
1
1
  from . import ask_user
2
2
  from . import create_directory
3
3
  from . import create_file
4
+ from . import replace_file
4
5
  from . import fetch_url
5
6
  from . import find_files
6
7
  from . import get_lines
7
- from . import outline_file
8
- from . import gitignore_utils
8
+ from .get_file_outline import core # noqa: F401,F811
9
9
  from . import move_file
10
- from . import validate_file_syntax
10
+ from .validate_file_syntax import core # noqa: F401,F811
11
11
  from . import remove_directory
12
12
  from . import remove_file
13
13
  from . import replace_text_in_file
14
- from . import rich_live
14
+ from . import delete_text_in_file
15
15
  from . import run_bash_command
16
16
  from . import run_powershell_command
17
17
  from . import run_python_command
@@ -24,21 +24,19 @@ __all__ = [
24
24
  "create_file",
25
25
  "fetch_url",
26
26
  "find_files",
27
- "outline_file",
27
+ "GetFileOutlineTool",
28
28
  "get_lines",
29
- "gitignore_utils",
30
29
  "move_file",
31
30
  "validate_file_syntax",
32
31
  "remove_directory",
33
32
  "remove_file",
33
+ "replace_file",
34
34
  "replace_text_in_file",
35
- "rich_live",
35
+ "delete_text_in_file",
36
36
  "run_bash_command",
37
37
  "run_powershell_command",
38
38
  "run_python_command",
39
39
  "search_files",
40
- "tools_utils",
41
- "memory",
42
40
  "present_choices",
43
41
  "search_text",
44
42
  ]
@@ -1,5 +1,7 @@
1
1
  from janito.agent.tool_registry import register_tool
2
- from janito.agent.tools.utils import expand_path, display_path
2
+
3
+ # from janito.agent.tools_utils.expand_path import expand_path
4
+ from janito.agent.tools_utils.utils import display_path
3
5
  from janito.agent.tool_base import ToolBase
4
6
  from janito.i18n import tr
5
7
  import os
@@ -18,10 +20,11 @@ class CreateDirectoryTool(ToolBase):
18
20
  """
19
21
 
20
22
  def run(self, file_path: str) -> str:
21
- file_path = expand_path(file_path)
23
+ # file_path = expand_path(file_path)
24
+ # Using file_path as is
22
25
  disp_path = display_path(file_path)
23
26
  self.report_info(
24
- tr("📁 Creating directory: '{disp_path}' ...", disp_path=disp_path)
27
+ tr("📁 Creating directory '{disp_path}' ...", disp_path=disp_path)
25
28
  )
26
29
  try:
27
30
  if os.path.exists(file_path):
@@ -47,9 +50,7 @@ class CreateDirectoryTool(ToolBase):
47
50
  disp_path=disp_path,
48
51
  )
49
52
  os.makedirs(file_path, exist_ok=True)
50
- self.report_success(
51
- tr("✅ Directory created at '{disp_path}'", disp_path=disp_path)
52
- )
53
+ self.report_success(tr("✅ Directory created"))
53
54
  return tr(
54
55
  "✅ Successfully created the directory at '{disp_path}'.",
55
56
  disp_path=disp_path,