janito 1.2.0__tar.gz → 1.2.1__tar.gz

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 (60) hide show
  1. {janito-1.2.0 → janito-1.2.1}/PKG-INFO +1 -1
  2. janito-1.2.1/janito/__init__.py +1 -0
  3. {janito-1.2.0 → janito-1.2.1}/janito/agent/agent.py +4 -0
  4. janito-1.2.1/janito/agent/config_utils.py +9 -0
  5. {janito-1.2.0 → janito-1.2.1}/janito/agent/conversation.py +11 -3
  6. {janito-1.2.0 → janito-1.2.1}/janito/agent/tools/__init__.py +2 -4
  7. janito-1.2.1/janito/agent/tools/file_ops.py +67 -0
  8. {janito-1.2.0 → janito-1.2.1}/janito/cli/_print_config.py +11 -11
  9. {janito-1.2.0 → janito-1.2.1}/janito/templates/system_instructions.j2 +2 -2
  10. janito-1.2.1/janito/web/app.py +189 -0
  11. {janito-1.2.0 → janito-1.2.1}/janito.egg-info/PKG-INFO +1 -1
  12. {janito-1.2.0 → janito-1.2.1}/janito.egg-info/SOURCES.txt +2 -4
  13. {janito-1.2.0 → janito-1.2.1}/pyproject.toml +1 -1
  14. janito-1.2.0/janito/__init__.py +0 -1
  15. janito-1.2.0/janito/agent/tools/create_directory.py +0 -19
  16. janito-1.2.0/janito/agent/tools/create_file.py +0 -43
  17. janito-1.2.0/janito/agent/tools/move_file.py +0 -37
  18. janito-1.2.0/janito/agent/tools/remove_file.py +0 -19
  19. janito-1.2.0/janito/web/app.py +0 -132
  20. {janito-1.2.0 → janito-1.2.1}/LICENSE +0 -0
  21. {janito-1.2.0 → janito-1.2.1}/README.md +0 -0
  22. {janito-1.2.0 → janito-1.2.1}/janito/__main__.py +0 -0
  23. {janito-1.2.0 → janito-1.2.1}/janito/agent/__init__.py +0 -0
  24. {janito-1.2.0 → janito-1.2.1}/janito/agent/config.py +0 -0
  25. {janito-1.2.0 → janito-1.2.1}/janito/agent/config_defaults.py +0 -0
  26. {janito-1.2.0 → janito-1.2.1}/janito/agent/queued_tool_handler.py +0 -0
  27. {janito-1.2.0 → janito-1.2.1}/janito/agent/runtime_config.py +0 -0
  28. {janito-1.2.0 → janito-1.2.1}/janito/agent/tool_handler.py +0 -0
  29. {janito-1.2.0 → janito-1.2.1}/janito/agent/tools/ask_user.py +0 -0
  30. {janito-1.2.0 → janito-1.2.1}/janito/agent/tools/bash_exec.py +0 -0
  31. {janito-1.2.0 → janito-1.2.1}/janito/agent/tools/fetch_url.py +0 -0
  32. {janito-1.2.0 → janito-1.2.1}/janito/agent/tools/file_str_replace.py +0 -0
  33. {janito-1.2.0 → janito-1.2.1}/janito/agent/tools/find_files.py +0 -0
  34. {janito-1.2.0 → janito-1.2.1}/janito/agent/tools/gitignore_utils.py +0 -0
  35. {janito-1.2.0 → janito-1.2.1}/janito/agent/tools/rich_live.py +0 -0
  36. {janito-1.2.0 → janito-1.2.1}/janito/agent/tools/rich_utils.py +0 -0
  37. {janito-1.2.0 → janito-1.2.1}/janito/agent/tools/search_text.py +0 -0
  38. {janito-1.2.0 → janito-1.2.1}/janito/agent/tools/view_file.py +0 -0
  39. {janito-1.2.0 → janito-1.2.1}/janito/cli/__init__.py +0 -0
  40. {janito-1.2.0 → janito-1.2.1}/janito/cli/_utils.py +0 -0
  41. {janito-1.2.0 → janito-1.2.1}/janito/cli/arg_parser.py +0 -0
  42. {janito-1.2.0 → janito-1.2.1}/janito/cli/config_commands.py +0 -0
  43. {janito-1.2.0 → janito-1.2.1}/janito/cli/logging_setup.py +0 -0
  44. {janito-1.2.0 → janito-1.2.1}/janito/cli/main.py +0 -0
  45. {janito-1.2.0 → janito-1.2.1}/janito/cli/runner.py +0 -0
  46. {janito-1.2.0 → janito-1.2.1}/janito/cli_chat_shell/__init__.py +0 -0
  47. {janito-1.2.0 → janito-1.2.1}/janito/cli_chat_shell/chat_loop.py +0 -0
  48. {janito-1.2.0 → janito-1.2.1}/janito/cli_chat_shell/commands.py +0 -0
  49. {janito-1.2.0 → janito-1.2.1}/janito/cli_chat_shell/config_shell.py +0 -0
  50. {janito-1.2.0 → janito-1.2.1}/janito/cli_chat_shell/load_prompt.py +0 -0
  51. {janito-1.2.0 → janito-1.2.1}/janito/cli_chat_shell/session_manager.py +0 -0
  52. {janito-1.2.0 → janito-1.2.1}/janito/cli_chat_shell/ui.py +0 -0
  53. {janito-1.2.0 → janito-1.2.1}/janito/render_prompt.py +0 -0
  54. {janito-1.2.0 → janito-1.2.1}/janito/web/__init__.py +0 -0
  55. {janito-1.2.0 → janito-1.2.1}/janito/web/__main__.py +0 -0
  56. {janito-1.2.0 → janito-1.2.1}/janito.egg-info/dependency_links.txt +0 -0
  57. {janito-1.2.0 → janito-1.2.1}/janito.egg-info/entry_points.txt +0 -0
  58. {janito-1.2.0 → janito-1.2.1}/janito.egg-info/requires.txt +0 -0
  59. {janito-1.2.0 → janito-1.2.1}/janito.egg-info/top_level.txt +0 -0
  60. {janito-1.2.0 → janito-1.2.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: janito
3
- Version: 1.2.0
3
+ Version: 1.2.1
4
4
  Summary: An agent framework with built-in tools.
5
5
  Author-email: João Pinto <joao.pinto@gmail.com>
6
6
  License: MIT
@@ -0,0 +1 @@
1
+ __version__ = "1.2.1"
@@ -60,6 +60,10 @@ class Agent:
60
60
  self.client, self.model, self.tool_handler
61
61
  )
62
62
 
63
+ @property
64
+ def usage_history(self):
65
+ return self.conversation_handler.usage_history
66
+
63
67
  def chat(self, messages, on_content=None, on_tool_progress=None, verbose_response=False, spinner=False, max_tokens=None):
64
68
  import time
65
69
  from janito.agent.conversation import ProviderError
@@ -0,0 +1,9 @@
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
@@ -16,6 +16,7 @@ class ConversationHandler:
16
16
  self.client = client
17
17
  self.model = model
18
18
  self.tool_handler = tool_handler
19
+ self.usage_history = []
19
20
 
20
21
  def handle_conversation(self, messages, max_rounds=50, on_content=None, on_tool_progress=None, verbose_response=False, spinner=False, max_tokens=None):
21
22
  if not messages:
@@ -95,16 +96,23 @@ class ConversationHandler:
95
96
 
96
97
  # If no tool calls, return the assistant's message and usage info
97
98
  if not choice.message.tool_calls:
99
+ # Store usage info in usage_history, linked to the next assistant message index
100
+ assistant_idx = len([m for m in messages if m.get('role') == 'assistant'])
101
+ self.usage_history.append({"assistant_index": assistant_idx, "usage": usage_info})
98
102
  return {
99
- "content": choice.message.content,
100
- "usage": usage_info
101
- }
103
+ "content": choice.message.content,
104
+ "usage": usage_info,
105
+ "usage_history": self.usage_history
106
+ }
102
107
 
103
108
  tool_responses = []
104
109
  for tool_call in choice.message.tool_calls:
105
110
  result = self.tool_handler.handle_tool_call(tool_call, on_progress=on_tool_progress)
106
111
  tool_responses.append({"tool_call_id": tool_call.id, "content": result})
107
112
 
113
+ # Store usage info in usage_history, linked to the next assistant message index
114
+ assistant_idx = len([m for m in messages if m.get('role') == 'assistant'])
115
+ self.usage_history.append({"assistant_index": assistant_idx, "usage": usage_info})
108
116
  messages.append({"role": "assistant", "content": choice.message.content, "tool_calls": [tc.to_dict() for tc in choice.message.tool_calls]})
109
117
 
110
118
  for tr in tool_responses:
@@ -1,11 +1,9 @@
1
1
  from .ask_user import ask_user
2
- from .create_directory import create_directory
3
- from .create_file import create_file
4
- from .remove_file import remove_file
2
+ from .file_ops import create_directory, create_file, remove_file, move_file
5
3
  from .view_file import view_file
6
4
  from .find_files import find_files
7
5
  from .search_text import search_text
8
6
  from .bash_exec import bash_exec
9
7
  from .fetch_url import fetch_url
10
- from .move_file import move_file
8
+
11
9
  from .file_str_replace import file_str_replace
@@ -0,0 +1,67 @@
1
+ import os
2
+ import shutil
3
+ from janito.agent.tool_handler import ToolHandler
4
+ from janito.agent.tools.rich_utils import print_info, print_success, print_error, format_path
5
+
6
+ @ToolHandler.register_tool
7
+ def create_file(path: str, content: str, overwrite: bool = False) -> str:
8
+ if os.path.exists(path):
9
+ if os.path.isdir(path):
10
+ print_error("❌ Error: is a directory")
11
+ return f"❌ Cannot create file: '{path}' is an existing directory."
12
+ if not overwrite:
13
+ print_error(f"❗ Error: file '{path}' exists and overwrite is False")
14
+ return f"❗ Cannot create file: '{path}' already exists and overwrite is False."
15
+ print_info(f"📝 Creating file: '{format_path(path)}' ... ")
16
+ try:
17
+ with open(path, "w", encoding="utf-8") as f:
18
+ f.write(content)
19
+ print_success("✅ Success")
20
+ return f"✅ Successfully created the file at '{path}'."
21
+ except Exception as e:
22
+ print_error(f"❌ Error: {e}")
23
+ return f"❌ Failed to create the file at '{path}': {e}"
24
+
25
+ @ToolHandler.register_tool
26
+ def remove_file(path: str) -> str:
27
+ print_info(f"🗑️ Removing file: '{format_path(path)}' ... ")
28
+ try:
29
+ os.remove(path)
30
+ print_success("✅ Success")
31
+ return f"✅ Successfully deleted the file at '{path}'."
32
+ except Exception as e:
33
+ print_error(f"❌ Error: {e}")
34
+ return f"❌ Failed to delete the file at '{path}': {e}"
35
+
36
+ @ToolHandler.register_tool
37
+ def move_file(source_path: str, destination_path: str, overwrite: bool = False) -> str:
38
+ print_info(f"🚚 Moving '{format_path(source_path)}' to '{format_path(destination_path)}' ... ")
39
+ try:
40
+ if not os.path.exists(source_path):
41
+ print_error("❌ Error: source does not exist")
42
+ return f"❌ Source path '{source_path}' does not exist."
43
+ if os.path.exists(destination_path):
44
+ if not overwrite:
45
+ print_error("❌ Error: destination exists and overwrite is False")
46
+ return f"❌ Destination path '{destination_path}' already exists. Use overwrite=True to replace it."
47
+ if os.path.isdir(destination_path):
48
+ shutil.rmtree(destination_path)
49
+ else:
50
+ os.remove(destination_path)
51
+ shutil.move(source_path, destination_path)
52
+ print_success("✅ Success")
53
+ return f"✅ Successfully moved '{source_path}' to '{destination_path}'."
54
+ except Exception as e:
55
+ print_error(f"❌ Error: {e}")
56
+ return f"❌ Failed to move '{source_path}' to '{destination_path}': {e}"
57
+
58
+ @ToolHandler.register_tool
59
+ def create_directory(path: str) -> str:
60
+ print_info(f"📁 Creating directory: '{format_path(path)}' ... ")
61
+ try:
62
+ os.makedirs(path, exist_ok=True)
63
+ print_success("✅ Success")
64
+ return f"✅ Directory '{path}' created successfully."
65
+ except Exception as e:
66
+ print_error(f"❌ Error: {e}")
67
+ return f"❌ Error creating directory '{path}': {e}"
@@ -1,22 +1,22 @@
1
1
  import os
2
- from rich import print
2
+ from janito.agent.tools.rich_utils import print_info, print_success, print_error, print_warning, print_magenta
3
3
  from ._utils import home_shorten
4
4
 
5
5
  def print_config_items(items, color_label=None):
6
6
  if not items:
7
7
  return
8
8
  if color_label:
9
- print(color_label)
9
+ print_info(color_label)
10
10
  home = os.path.expanduser("~")
11
11
  for key, value in items.items():
12
12
  if key == "system_prompt" and isinstance(value, str):
13
13
  if value.startswith(home):
14
14
  print(f"{key} = {home_shorten(value)}")
15
15
  else:
16
- print(f"{key} = {value}")
16
+ print_info(f"{key} = {value}")
17
17
  else:
18
- print(f"{key} = {value}")
19
- print()
18
+ print_info(f"{key} = {value}")
19
+ print_info("")
20
20
 
21
21
  def print_full_config(local_config, global_config, unified_config, config_defaults, console=None):
22
22
  """
@@ -28,9 +28,9 @@ def print_full_config(local_config, global_config, unified_config, config_defaul
28
28
  local_keys = set(local_config.all().keys())
29
29
  global_keys = set(global_config.all().keys())
30
30
  all_keys = set(config_defaults.keys()) | global_keys | local_keys
31
- out = print if console is None else console.print
31
+ out = print_info if console is None else console.print
32
32
  if not (local_keys or global_keys):
33
- out("No configuration found.")
33
+ print_warning("No configuration found.")
34
34
  else:
35
35
  for key in sorted(local_keys):
36
36
  if key == "api_key":
@@ -57,12 +57,12 @@ def print_full_config(local_config, global_config, unified_config, config_defaul
57
57
  shown_keys = set(local_items.keys()) | set(global_items.keys())
58
58
  default_items = {k: v for k, v in config_defaults.items() if k not in shown_keys and k != 'api_key'}
59
59
  if default_items:
60
- out("[green]🟢 Defaults (not set in config files)[/green]")
60
+ print_magenta("[green]🟢 Defaults (not set in config files)[/green]")
61
61
  from pathlib import Path
62
62
  template_path = Path(__file__).parent.parent / "templates" / "system_instructions.j2"
63
63
  for key, value in default_items.items():
64
64
  if key == "system_prompt" and value is None:
65
- out(f"{key} = (default template path: {home_shorten(str(template_path))})")
65
+ print_info(f"{key} = (default template path: {home_shorten(str(template_path))})")
66
66
  else:
67
- out(f"{key} = {value}")
68
- out("")
67
+ print_info(f"{key} = {value}")
68
+ print_info("")
@@ -18,7 +18,7 @@ If in doubt during editing, use the `ask_user` function to get additional inform
18
18
 
19
19
  When you need to make changes to a file, consider the following:
20
20
 
21
- - Use `file_str_replace` when you want to update or fix specific text fragments within a file without altering the rest of its content. It is preferred over full file replacement when:
21
+ - Use `file_str_replace` when you want to update or fix specific text fragments within a file without altering the rest of its content. It is preferred over full file replacement when:
22
22
  - Only small, targeted changes are needed.
23
23
  - You want to avoid the risk of accidentally overwriting unrelated content.
24
24
  - The file is large, and rewriting the entire file would be inefficient.
@@ -34,5 +34,5 @@ After performing changes:
34
34
 
35
35
  - Review the README content if there are user-exposed or public API changes.
36
36
  - Use `git commit` to save the changes. Unless requested otherwise, review `git diff` when composing the commit message.
37
- - Review `docs/structure.md` considering discovered, created, or modified files.
37
+ - Update `docs/structure.md` considering discovered, created, or modified files.
38
38
  </finishing>
@@ -0,0 +1,189 @@
1
+ from flask import Flask, request, render_template, Response, send_from_directory, session, jsonify
2
+ from queue import Queue
3
+ import json
4
+ from janito.agent.queued_tool_handler import QueuedToolHandler
5
+ from janito.agent.agent import Agent
6
+ from janito.agent.config import get_api_key
7
+ from janito.render_prompt import render_system_prompt
8
+ import os
9
+ import threading
10
+
11
+ from janito.agent.runtime_config import unified_config
12
+
13
+ # Render system prompt from config
14
+ role = unified_config.get("role", "software engineer")
15
+ system_prompt_override = unified_config.get("system_prompt")
16
+ if system_prompt_override:
17
+ system_prompt = system_prompt_override
18
+ else:
19
+ system_prompt = render_system_prompt(role)
20
+
21
+ app = Flask(
22
+ __name__,
23
+ static_url_path='/static',
24
+ static_folder=os.path.join(os.path.dirname(__file__), 'static')
25
+ )
26
+
27
+ # Secret key for session management
28
+ app.secret_key = 'replace_with_a_secure_random_secret_key'
29
+
30
+ # Path for persistent conversation storage
31
+ conversation_file = os.path.expanduser('~/.janito/last_conversation_web.json')
32
+
33
+ # Initially no conversation loaded
34
+ conversation = None
35
+
36
+
37
+ # Global event queue for streaming
38
+ stream_queue = Queue()
39
+
40
+ # Create a QueuedToolHandler with the queue
41
+ queued_handler = QueuedToolHandler(stream_queue)
42
+
43
+ # Instantiate the Agent with config-driven parameters
44
+ agent = Agent(
45
+ api_key=unified_config.get("api_key"),
46
+ model=unified_config.get("model"),
47
+ base_url=unified_config.get("base_url"),
48
+ tool_handler=queued_handler
49
+ )
50
+
51
+ @app.route('/get_config')
52
+ def get_config():
53
+ # Expose full config for the web app: defaults, effective, runtime (mask api_key)
54
+ from janito.agent.runtime_config import unified_config
55
+ from janito.agent.config_defaults import CONFIG_DEFAULTS
56
+ # Start with defaults
57
+ config = dict(CONFIG_DEFAULTS)
58
+ # Overlay effective config
59
+ config.update(unified_config.effective_cfg.all())
60
+ # Overlay runtime config (highest priority)
61
+ config.update(unified_config.runtime_cfg.all())
62
+ api_key = config.get("api_key")
63
+ if api_key:
64
+ config["api_key"] = api_key[:4] + '...' + api_key[-4:] if len(api_key) > 8 else '***'
65
+ return jsonify(config)
66
+
67
+ @app.route('/set_config', methods=['POST'])
68
+ def set_config():
69
+ from janito.agent.runtime_config import runtime_config
70
+ from janito.agent.config import CONFIG_OPTIONS
71
+ from janito.agent.config_defaults import CONFIG_DEFAULTS
72
+ data = request.get_json()
73
+ key = data.get('key')
74
+ value = data.get('value')
75
+ if key not in CONFIG_OPTIONS:
76
+ return jsonify({'status': 'error', 'message': f'Invalid config key: {key}'}), 400
77
+ # Type coercion based on defaults
78
+ default = CONFIG_DEFAULTS.get(key)
79
+ if default is not None and value is not None:
80
+ try:
81
+ if isinstance(default, bool):
82
+ value = bool(value)
83
+ elif isinstance(default, int):
84
+ value = int(value)
85
+ elif isinstance(default, float):
86
+ value = float(value)
87
+ # else: leave as string or None
88
+ except Exception as e:
89
+ return jsonify({'status': 'error', 'message': f'Invalid value type for {key}: {e}'}), 400
90
+ runtime_config.set(key, value)
91
+ # Mask api_key in response
92
+ resp_value = value
93
+ if key == 'api_key' and value:
94
+ resp_value = value[:4] + '...' + value[-4:] if len(value) > 8 else '***'
95
+ return jsonify({'status': 'ok', 'key': key, 'value': resp_value})
96
+
97
+
98
+ @app.route('/favicon.ico')
99
+ def favicon():
100
+ return send_from_directory(
101
+ os.path.join(app.root_path, 'static'),
102
+ 'favicon.ico',
103
+ mimetype='image/vnd.microsoft.icon'
104
+ )
105
+
106
+
107
+ @app.route('/')
108
+ def index():
109
+ return render_template('index.html')
110
+
111
+ @app.route('/load_conversation')
112
+ def load_conversation():
113
+ global conversation
114
+ try:
115
+ with open(conversation_file, 'r') as f:
116
+ conversation = json.load(f)
117
+ except (FileNotFoundError, json.JSONDecodeError):
118
+ conversation = []
119
+ return jsonify({'status': 'ok', 'conversation': conversation})
120
+
121
+ @app.route('/new_conversation', methods=['POST'])
122
+ def new_conversation():
123
+ global conversation
124
+ conversation = []
125
+ return jsonify({'status': 'ok'})
126
+
127
+ @app.route('/execute_stream', methods=['POST'])
128
+ def execute_stream():
129
+ data = request.get_json()
130
+ user_input = data.get('input', '')
131
+
132
+ global conversation
133
+ if conversation is None:
134
+ # If no conversation loaded, start a new one
135
+ conversation = []
136
+
137
+ # Always start with the system prompt as the first message
138
+ if not conversation or conversation[0]['role'] != 'system':
139
+ conversation.insert(0, {"role": "system", "content": system_prompt})
140
+
141
+ # Append the new user message
142
+ conversation.append({"role": "user", "content": user_input})
143
+
144
+ def run_agent():
145
+ try:
146
+ response = agent.chat(
147
+ conversation,
148
+ on_content=lambda data: stream_queue.put({"type": "content", "content": data.get("content")})
149
+ )
150
+ if response and 'content' in response:
151
+ conversation.append({"role": "assistant", "content": response['content']})
152
+ try:
153
+ os.makedirs(os.path.dirname(conversation_file), exist_ok=True)
154
+ with open(conversation_file, 'w') as f:
155
+ json.dump(conversation, f, indent=2)
156
+ except Exception as e:
157
+ print(f"Error saving conversation: {e}")
158
+ except Exception as e:
159
+ import traceback
160
+ tb = traceback.format_exc()
161
+ stream_queue.put({"type": "error", "error": str(e), "traceback": tb})
162
+ finally:
163
+ stream_queue.put(None)
164
+
165
+ threading.Thread(target=run_agent, daemon=True).start()
166
+
167
+ def generate():
168
+ while True:
169
+ content = stream_queue.get()
170
+ if content is None:
171
+ break
172
+ if isinstance(content, tuple) and content[0] == 'tool_progress':
173
+ message = json.dumps({"type": "tool_progress", "data": content[1]})
174
+ else:
175
+ message = json.dumps(content)
176
+ yield f"data: {message}\n\n"
177
+ import sys
178
+ sys.stdout.flush()
179
+
180
+ return Response(
181
+ generate(),
182
+ mimetype='text/event-stream',
183
+ headers={
184
+ 'Cache-Control': 'no-cache',
185
+ 'X-Accel-Buffering': 'no',
186
+ 'Connection': 'keep-alive',
187
+ 'Transfer-Encoding': 'chunked'
188
+ }
189
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: janito
3
- Version: 1.2.0
3
+ Version: 1.2.1
4
4
  Summary: An agent framework with built-in tools.
5
5
  Author-email: João Pinto <joao.pinto@gmail.com>
6
6
  License: MIT
@@ -14,6 +14,7 @@ janito/agent/__init__.py
14
14
  janito/agent/agent.py
15
15
  janito/agent/config.py
16
16
  janito/agent/config_defaults.py
17
+ janito/agent/config_utils.py
17
18
  janito/agent/conversation.py
18
19
  janito/agent/queued_tool_handler.py
19
20
  janito/agent/runtime_config.py
@@ -21,14 +22,11 @@ janito/agent/tool_handler.py
21
22
  janito/agent/tools/__init__.py
22
23
  janito/agent/tools/ask_user.py
23
24
  janito/agent/tools/bash_exec.py
24
- janito/agent/tools/create_directory.py
25
- janito/agent/tools/create_file.py
26
25
  janito/agent/tools/fetch_url.py
26
+ janito/agent/tools/file_ops.py
27
27
  janito/agent/tools/file_str_replace.py
28
28
  janito/agent/tools/find_files.py
29
29
  janito/agent/tools/gitignore_utils.py
30
- janito/agent/tools/move_file.py
31
- janito/agent/tools/remove_file.py
32
30
  janito/agent/tools/rich_live.py
33
31
  janito/agent/tools/rich_utils.py
34
32
  janito/agent/tools/search_text.py
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "janito"
3
- version = "1.2.0"
3
+ version = "1.2.1"
4
4
  description = "An agent framework with built-in tools."
5
5
  authors = [
6
6
  { name = "João Pinto", email = "joao.pinto@gmail.com" }
@@ -1 +0,0 @@
1
- __version__ = "1.2.0"
@@ -1,19 +0,0 @@
1
- import os
2
- from janito.agent.tool_handler import ToolHandler
3
- from janito.agent.tools.rich_utils import print_info, print_success, print_error, format_path
4
-
5
- @ToolHandler.register_tool
6
- def create_directory(path: str) -> str:
7
- """
8
- Create a directory at the specified path.
9
-
10
- path: The path of the directory to create
11
- """
12
- print_info(f"📁 Creating directory: '{format_path(path)}' ... ")
13
- try:
14
- os.makedirs(path, exist_ok=True)
15
- print_success("✅ Success")
16
- return f"✅ Directory '{path}' created successfully."
17
- except Exception as e:
18
- print_error(f"❌ Error: {e}")
19
- return f"❌ Error creating directory '{path}': {e}"
@@ -1,43 +0,0 @@
1
- import os
2
- from janito.agent.tool_handler import ToolHandler
3
- from janito.agent.tools.rich_utils import print_info, print_success, print_error, format_path
4
-
5
- @ToolHandler.register_tool
6
- def create_file(path: str, content: str, overwrite: bool = False) -> str:
7
- """
8
- Create a file with the specified content.
9
-
10
- path: The path of the file to create
11
- content: The content to write into the file
12
- overwrite: Whether to overwrite the file if it exists (default: False)
13
- """
14
- old_lines = None
15
- if os.path.exists(path):
16
- if os.path.isdir(path):
17
- print_error("❌ Error: is a directory")
18
- return f"❌ Cannot create file: '{path}' is an existing directory."
19
- if overwrite:
20
- try:
21
- with open(path, "r", encoding="utf-8") as f:
22
- old_lines = sum(1 for _ in f)
23
- except Exception:
24
- old_lines = 'unknown'
25
- else:
26
- print_error(f"❗ Error: file '{path}' exists and overwrite is False")
27
- return f"❗ Cannot create file: '{path}' already exists and overwrite is False."
28
-
29
- new_lines = content.count('\n') + 1 if content else 0
30
-
31
- if old_lines is not None:
32
- print_info(f"♻️ Replacing file: '{format_path(path)}' (line count: {old_lines} -> {new_lines}) ... ")
33
- else:
34
- print_info(f"📝 Creating file: '{format_path(path)}' (lines: {new_lines}) ... ")
35
-
36
- try:
37
- with open(path, "w", encoding="utf-8") as f:
38
- f.write(content)
39
- print_success("✅ Success")
40
- return f"✅ Successfully created the file at '{path}'."
41
- except Exception as e:
42
- print_error(f"❌ Error: {e}")
43
- return f"❌ Failed to create the file at '{path}': {e}"
@@ -1,37 +0,0 @@
1
- import shutil
2
- import os
3
- from janito.agent.tool_handler import ToolHandler
4
- from janito.agent.tools.rich_utils import print_info, print_success, print_error, format_path
5
-
6
-
7
- @ToolHandler.register_tool
8
- def move_file(source_path: str, destination_path: str, overwrite: bool = False) -> str:
9
- """
10
- Move a file or directory from source_path to destination_path.
11
-
12
- source_path: The path of the file or directory to move
13
- destination_path: The target path
14
- overwrite: Whether to overwrite the destination if it exists (default: False)
15
- """
16
- print_info(f"🚚 Moving '{format_path(source_path)}' to '{format_path(destination_path)}' ... ")
17
- try:
18
- if not os.path.exists(source_path):
19
- print_error("❌ Error: source does not exist")
20
- return f"❌ Source path '{source_path}' does not exist."
21
-
22
- if os.path.exists(destination_path):
23
- if not overwrite:
24
- print_error("❌ Error: destination exists and overwrite is False")
25
- return f"❌ Destination path '{destination_path}' already exists. Use overwrite=True to replace it."
26
- # Remove destination if overwrite is True
27
- if os.path.isdir(destination_path):
28
- shutil.rmtree(destination_path)
29
- else:
30
- os.remove(destination_path)
31
-
32
- shutil.move(source_path, destination_path)
33
- print_success("✅ Success")
34
- return f"✅ Successfully moved '{source_path}' to '{destination_path}'."
35
- except Exception as e:
36
- print_error(f"❌ Error: {e}")
37
- return f"❌ Failed to move '{source_path}' to '{destination_path}': {e}"
@@ -1,19 +0,0 @@
1
- import os
2
- from janito.agent.tool_handler import ToolHandler
3
- from janito.agent.tools.rich_utils import print_info, print_success, print_error, format_path
4
-
5
- @ToolHandler.register_tool
6
- def remove_file(path: str) -> str:
7
- """
8
- Remove a specified file.
9
-
10
- path: The path of the file to remove
11
- """
12
- print_info(f"🗑️ Removing file: '{format_path(path)}' ... ")
13
- try:
14
- os.remove(path)
15
- print_success("✅ Success")
16
- return f"✅ Successfully deleted the file at '{path}'."
17
- except Exception as e:
18
- print_error(f"❌ Error: {e}")
19
- return f"❌ Failed to delete the file at '{path}': {e}"
@@ -1,132 +0,0 @@
1
- from flask import Flask, request, render_template, Response, send_from_directory, session, jsonify
2
- from queue import Queue
3
- import json
4
- from janito.agent.queued_tool_handler import QueuedToolHandler
5
- from janito.agent.agent import Agent
6
- from janito.agent.config import get_api_key
7
- from janito.render_prompt import render_system_prompt
8
- import os
9
- import threading
10
-
11
- # Render system prompt once
12
- system_prompt = render_system_prompt("software engineer")
13
-
14
- app = Flask(
15
- __name__,
16
- static_url_path='/static',
17
- static_folder=os.path.join(os.path.dirname(__file__), 'static')
18
- )
19
-
20
- # Secret key for session management
21
- app.secret_key = 'replace_with_a_secure_random_secret_key'
22
-
23
- # Path for persistent conversation storage
24
- conversation_file = os.path.expanduser('~/.janito/last_conversation_web.json')
25
-
26
- # Initially no conversation loaded
27
- conversation = None
28
-
29
-
30
- # Global event queue for streaming
31
- stream_queue = Queue()
32
-
33
- # Create a QueuedToolHandler with the queue
34
- queued_handler = QueuedToolHandler(stream_queue)
35
-
36
- # Instantiate the Agent with the custom tool handler
37
- agent = Agent(
38
- api_key=get_api_key(),
39
- tool_handler=queued_handler
40
- )
41
-
42
- @app.route('/get_model_name')
43
- def get_model_name():
44
- return jsonify({"model": agent.model})
45
-
46
-
47
- @app.route('/favicon.ico')
48
- def favicon():
49
- return send_from_directory(
50
- os.path.join(app.root_path, 'static'),
51
- 'favicon.ico',
52
- mimetype='image/vnd.microsoft.icon'
53
- )
54
-
55
-
56
- @app.route('/')
57
- def index():
58
- return render_template('index.html')
59
-
60
- @app.route('/load_conversation')
61
- def load_conversation():
62
- global conversation
63
- try:
64
- with open(conversation_file, 'r') as f:
65
- conversation = json.load(f)
66
- except (FileNotFoundError, json.JSONDecodeError):
67
- conversation = []
68
- return jsonify({'status': 'ok', 'conversation': conversation})
69
-
70
- @app.route('/new_conversation', methods=['POST'])
71
- def new_conversation():
72
- global conversation
73
- conversation = []
74
- return jsonify({'status': 'ok'})
75
-
76
- @app.route('/execute_stream', methods=['POST'])
77
- def execute_stream():
78
- data = request.get_json()
79
- user_input = data.get('input', '')
80
-
81
- global conversation
82
- if conversation is None:
83
- # If no conversation loaded, start a new one
84
- conversation = []
85
-
86
- # Always start with the system prompt as the first message
87
- if not conversation or conversation[0]['role'] != 'system':
88
- conversation.insert(0, {"role": "system", "content": system_prompt})
89
-
90
- # Append the new user message
91
- conversation.append({"role": "user", "content": user_input})
92
-
93
- def run_agent():
94
- response = agent.chat(
95
- conversation,
96
- on_content=lambda data: stream_queue.put({"type": "content", "content": data.get("content")})
97
- )
98
- if response and 'content' in response:
99
- conversation.append({"role": "assistant", "content": response['content']})
100
- try:
101
- os.makedirs(os.path.dirname(conversation_file), exist_ok=True)
102
- with open(conversation_file, 'w') as f:
103
- json.dump(conversation, f, indent=2)
104
- except Exception as e:
105
- print(f"Error saving conversation: {e}")
106
- stream_queue.put(None)
107
-
108
- threading.Thread(target=run_agent, daemon=True).start()
109
-
110
- def generate():
111
- while True:
112
- content = stream_queue.get()
113
- if content is None:
114
- break
115
- if isinstance(content, tuple) and content[0] == 'tool_progress':
116
- message = json.dumps({"type": "tool_progress", "data": content[1]})
117
- else:
118
- message = json.dumps(content)
119
- yield f"data: {message}\n\n"
120
- import sys
121
- sys.stdout.flush()
122
-
123
- return Response(
124
- generate(),
125
- mimetype='text/event-stream',
126
- headers={
127
- 'Cache-Control': 'no-cache',
128
- 'X-Accel-Buffering': 'no',
129
- 'Connection': 'keep-alive',
130
- 'Transfer-Encoding': 'chunked'
131
- }
132
- )
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes