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.
- janito/__init__.py +1 -1
- janito/agent/config_defaults.py +23 -0
- janito/agent/config_utils.py +0 -9
- janito/agent/conversation.py +31 -9
- janito/agent/conversation_api.py +32 -2
- janito/agent/conversation_history.py +53 -0
- janito/agent/conversation_tool_calls.py +11 -8
- janito/agent/openai_client.py +11 -3
- janito/agent/openai_schema_generator.py +9 -6
- janito/agent/providers.py +77 -0
- janito/agent/rich_message_handler.py +1 -1
- janito/agent/templates/profiles/system_prompt_template_base.txt.j2 +8 -8
- janito/agent/tool_executor.py +18 -10
- janito/agent/tool_use_tracker.py +16 -0
- janito/agent/tools/__init__.py +7 -9
- janito/agent/tools/create_directory.py +7 -6
- janito/agent/tools/create_file.py +29 -54
- janito/agent/tools/delete_text_in_file.py +97 -0
- janito/agent/tools/fetch_url.py +11 -3
- janito/agent/tools/find_files.py +37 -25
- janito/agent/tools/get_file_outline/__init__.py +1 -0
- janito/agent/tools/{outline_file/__init__.py → get_file_outline/core.py} +12 -15
- janito/agent/tools/get_file_outline/python_outline.py +134 -0
- janito/agent/tools/{search_outline.py → get_file_outline/search_outline.py} +9 -0
- janito/agent/tools/get_lines.py +15 -11
- janito/agent/tools/move_file.py +10 -11
- janito/agent/tools/remove_directory.py +2 -2
- janito/agent/tools/remove_file.py +11 -13
- janito/agent/tools/replace_file.py +62 -0
- janito/agent/tools/replace_text_in_file.py +3 -3
- janito/agent/tools/run_bash_command.py +3 -7
- janito/agent/tools/run_powershell_command.py +39 -28
- janito/agent/tools/run_python_command.py +3 -5
- janito/agent/tools/search_text.py +10 -14
- janito/agent/tools/validate_file_syntax/__init__.py +1 -0
- janito/agent/tools/validate_file_syntax/core.py +92 -0
- janito/agent/tools/validate_file_syntax/css_validator.py +35 -0
- janito/agent/tools/validate_file_syntax/html_validator.py +77 -0
- janito/agent/tools/validate_file_syntax/js_validator.py +27 -0
- janito/agent/tools/validate_file_syntax/json_validator.py +6 -0
- janito/agent/tools/validate_file_syntax/markdown_validator.py +66 -0
- janito/agent/tools/validate_file_syntax/ps1_validator.py +32 -0
- janito/agent/tools/validate_file_syntax/python_validator.py +5 -0
- janito/agent/tools/validate_file_syntax/xml_validator.py +11 -0
- janito/agent/tools/validate_file_syntax/yaml_validator.py +6 -0
- janito/agent/tools_utils/__init__.py +1 -0
- janito/agent/tools_utils/dir_walk_utils.py +23 -0
- janito/agent/{tools/outline_file → tools_utils}/formatting.py +5 -2
- janito/agent/{tools → tools_utils}/gitignore_utils.py +0 -3
- janito/agent/tools_utils/utils.py +30 -0
- janito/cli/_livereload_log_utils.py +13 -0
- janito/cli/arg_parser.py +45 -3
- janito/cli/{runner/cli_main.py → cli_main.py} +120 -20
- janito/cli/livereload_starter.py +60 -0
- janito/cli/main.py +110 -21
- janito/cli/one_shot.py +66 -0
- janito/cli/termweb_starter.py +2 -2
- janito/livereload/app.py +25 -0
- janito/rich_utils.py +0 -22
- janito/{cli_chat_shell → shell}/commands/__init__.py +18 -11
- janito/{cli_chat_shell → shell}/commands/config.py +4 -4
- janito/shell/commands/conversation_restart.py +72 -0
- janito/shell/commands/edit.py +21 -0
- janito/shell/commands/history_view.py +18 -0
- janito/shell/commands/livelogs.py +40 -0
- janito/{cli_chat_shell → shell}/commands/prompt.py +10 -6
- janito/shell/commands/session.py +32 -0
- janito/{cli_chat_shell → shell}/commands/session_control.py +2 -7
- janito/{cli_chat_shell → shell}/commands/sum.py +6 -6
- janito/{cli_chat_shell → shell}/commands/termweb_log.py +10 -10
- janito/shell/commands/tools.py +23 -0
- janito/{cli_chat_shell → shell}/commands/utility.py +5 -4
- janito/{cli_chat_shell → shell}/commands/verbose.py +1 -1
- janito/shell/commands.py +40 -0
- janito/shell/main.py +321 -0
- janito/{cli_chat_shell/shell_command_completer.py → shell/prompt/completer.py} +1 -1
- janito/{cli_chat_shell/chat_ui.py → shell/prompt/session_setup.py} +19 -5
- janito/{cli_chat_shell/session_manager.py → shell/session/manager.py} +53 -3
- janito/{cli_chat_shell/ui.py → shell/ui/interactive.py} +23 -15
- janito/termweb/app.py +3 -3
- janito/termweb/static/editor.css +146 -0
- janito/termweb/static/editor.css.bak +27 -0
- janito/termweb/static/editor.html +15 -213
- janito/termweb/static/editor.html.bak +16 -215
- janito/termweb/static/editor.js +209 -0
- janito/termweb/static/editor.js.bak +227 -0
- janito/termweb/static/index.html +2 -3
- janito/termweb/static/index.html.bak +2 -3
- janito/termweb/static/termweb.css.bak +33 -84
- janito/termweb/static/termweb.js +15 -34
- janito/termweb/static/termweb.js.bak +18 -36
- {janito-1.8.0.dist-info → janito-1.9.0.dist-info}/METADATA +6 -3
- janito-1.9.0.dist-info/RECORD +151 -0
- {janito-1.8.0.dist-info → janito-1.9.0.dist-info}/WHEEL +1 -1
- janito/agent/tools/dir_walk_utils.py +0 -16
- janito/agent/tools/memory.py +0 -48
- janito/agent/tools/outline_file/python_outline.py +0 -71
- janito/agent/tools/present_choices_test.py +0 -18
- janito/agent/tools/rich_live.py +0 -44
- janito/agent/tools/tools_utils.py +0 -56
- janito/agent/tools/utils.py +0 -33
- janito/agent/tools/validate_file_syntax.py +0 -163
- janito/cli_chat_shell/chat_loop.py +0 -163
- janito/cli_chat_shell/chat_state.py +0 -38
- janito/cli_chat_shell/commands/history_start.py +0 -37
- janito/cli_chat_shell/commands/session.py +0 -48
- janito-1.8.0.dist-info/RECORD +0 -127
- /janito/agent/tools/{outline_file → get_file_outline}/markdown_outline.py +0 -0
- /janito/cli/{runner/_termweb_log_utils.py → _termweb_log_utils.py} +0 -0
- /janito/cli/{runner/config.py → config_runner.py} +0 -0
- /janito/cli/{runner/formatting.py → formatting_runner.py} +0 -0
- /janito/{cli/runner → shell}/__init__.py +0 -0
- /janito/{cli_chat_shell → shell}/commands/lang.py +0 -0
- /janito/{cli_chat_shell → shell/prompt}/load_prompt.py +0 -0
- /janito/{cli_chat_shell/config_shell.py → shell/session/config.py} +0 -0
- /janito/{cli_chat_shell/__init__.py → shell/session/history.py} +0 -0
- {janito-1.8.0.dist-info → janito-1.9.0.dist-info}/entry_points.txt +0 -0
- {janito-1.8.0.dist-info → janito-1.9.0.dist-info}/licenses/LICENSE +0 -0
- {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.
|
1
|
+
__version__ = "1.9.0"
|
janito/agent/config_defaults.py
CHANGED
@@ -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
|
}
|
janito/agent/config_utils.py
CHANGED
janito/agent/conversation.py
CHANGED
@@ -30,7 +30,7 @@ class ConversationHandler:
|
|
30
30
|
def handle_conversation(
|
31
31
|
self,
|
32
32
|
messages,
|
33
|
-
max_rounds=
|
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
|
-
|
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
|
-
|
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,
|
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(
|
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(
|
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
|
-
|
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
|
-
|
190
|
+
history.add_message(
|
169
191
|
{
|
170
192
|
"role": "tool",
|
171
193
|
"tool_call_id": tool_response["tool_call_id"],
|
janito/agent/conversation_api.py
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
23
|
-
|
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":
|
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
|
janito/agent/openai_client.py
CHANGED
@@ -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=
|
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:
|
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
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
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
|
1
|
+
You are: {{ role }}
|
2
2
|
|
3
|
-
You
|
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
|
-
|
9
|
-
-
|
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
|
-
-
|
12
|
-
-
|
13
|
-
-
|
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.
|
janito/agent/tool_executor.py
CHANGED
@@ -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 =
|
23
|
-
|
24
|
-
|
25
|
-
|
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
|
janito/agent/tool_use_tracker.py
CHANGED
@@ -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()
|
janito/agent/tools/__init__.py
CHANGED
@@ -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
|
8
|
-
from . import gitignore_utils
|
8
|
+
from .get_file_outline import core # noqa: F401,F811
|
9
9
|
from . import move_file
|
10
|
-
from . import
|
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
|
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
|
-
"
|
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
|
-
"
|
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
|
-
|
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
|
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,
|