janito 2.21.0__py3-none-any.whl → 2.22.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 (31) hide show
  1. janito/README.md +0 -0
  2. janito/agent/setup_agent.py +34 -4
  3. janito/agent/templates/profiles/system_prompt_template_Developer_with_Python_Tools.txt.j2 +0 -0
  4. janito/agent/templates/profiles/system_prompt_template_developer.txt.j2 +0 -0
  5. janito/agent/templates/profiles/system_prompt_template_market_analyst.txt.j2 +10 -0
  6. janito/agent/templates/profiles/system_prompt_template_model_conversation_without_tools_or_context.txt.j2 +0 -0
  7. janito/cli/chat_mode/session_profile_select.py +20 -3
  8. janito/cli/chat_mode/shell/commands.bak.zip +0 -0
  9. janito/cli/chat_mode/shell/session.bak.zip +0 -0
  10. janito/cli/cli_commands/list_profiles.py +29 -1
  11. janito/cli/cli_commands/show_system_prompt.py +45 -4
  12. janito/docs/GETTING_STARTED.md +0 -0
  13. janito/drivers/dashscope.bak.zip +0 -0
  14. janito/drivers/openai/README.md +0 -0
  15. janito/drivers/openai_responses.bak.zip +0 -0
  16. janito/llm/README.md +0 -0
  17. janito/mkdocs.yml +0 -0
  18. janito/providers/dashscope.bak.zip +0 -0
  19. janito/providers/ibm/README.md +0 -0
  20. janito/providers/ibm/model_info.py +9 -0
  21. janito/shell.bak.zip +0 -0
  22. janito/tools/DOCSTRING_STANDARD.txt +0 -0
  23. janito/tools/README.md +0 -0
  24. janito/tools/adapters/local/fetch_url.py +96 -8
  25. janito/tools/outline_file.bak.zip +0 -0
  26. {janito-2.21.0.dist-info → janito-2.22.0.dist-info}/METADATA +411 -411
  27. {janito-2.21.0.dist-info → janito-2.22.0.dist-info}/RECORD +13 -12
  28. {janito-2.21.0.dist-info → janito-2.22.0.dist-info}/entry_points.txt +0 -0
  29. {janito-2.21.0.dist-info → janito-2.22.0.dist-info}/licenses/LICENSE +0 -0
  30. {janito-2.21.0.dist-info → janito-2.22.0.dist-info}/top_level.txt +0 -0
  31. {janito-2.21.0.dist-info → janito-2.22.0.dist-info}/WHEEL +0 -0
janito/README.md CHANGED
File without changes
@@ -52,10 +52,40 @@ def _load_template_content(profile, templates_dir):
52
52
  with open(user_template_path, "r", encoding="utf-8") as file:
53
53
  return file.read(), user_template_path
54
54
 
55
- # If nothing matched, raise an informative error
56
- raise FileNotFoundError(
57
- f"[janito] Could not find profile-specific template '{template_filename}' in {template_path} nor in janito.agent.templates.profiles package nor in user profiles directory {user_template_path}."
58
- )
55
+ # If nothing matched, list available profiles and raise an informative error
56
+ from janito.cli.cli_commands.list_profiles import _gather_default_profiles, _gather_user_profiles
57
+
58
+ default_profiles = _gather_default_profiles()
59
+ user_profiles = _gather_user_profiles()
60
+
61
+ available_profiles = []
62
+ if default_profiles:
63
+ available_profiles.extend([(p, "default") for p in default_profiles])
64
+ if user_profiles:
65
+ available_profiles.extend([(p, "user") for p in user_profiles])
66
+
67
+ # Normalize the input profile for better matching suggestions
68
+ normalized_input = re.sub(r"\s+", " ", profile.strip().lower())
69
+
70
+ if available_profiles:
71
+ profile_list = "\n".join([f" - {name} ({source})" for name, source in available_profiles])
72
+
73
+ # Find close matches
74
+ close_matches = []
75
+ for name, source in available_profiles:
76
+ normalized_name = name.lower()
77
+ if normalized_input in normalized_name or normalized_name in normalized_input:
78
+ close_matches.append(name)
79
+
80
+ suggestion = ""
81
+ if close_matches:
82
+ suggestion = f"\nDid you mean: {', '.join(close_matches)}?"
83
+
84
+ error_msg = f"[janito] Could not find profile '{profile}'. Available profiles:\n{profile_list}{suggestion}"
85
+ else:
86
+ error_msg = f"[janito] Could not find profile '{profile}'. No profiles available."
87
+
88
+ raise FileNotFoundError(error_msg)
59
89
  # Replace spaces in profile name with underscores for filename resolution
60
90
  sanitized_profile = re.sub(r"\\s+", "_", profile.strip()) if profile else profile
61
91
  """
@@ -0,0 +1,10 @@
1
+ You are: market analyst specializing in financial markets, business intelligence, and economic research
2
+
3
+ Focus on providing market analysis, financial insights, and business intelligence. When analyzing data:
4
+
5
+ • Identify market trends and patterns
6
+ • Provide quantitative analysis with clear metrics
7
+ • Consider economic indicators and market sentiment
8
+ • Offer actionable insights for business decisions
9
+ • Use appropriate financial terminology and frameworks
10
+ • Reference relevant market data, benchmarks, and industry standards
@@ -111,6 +111,7 @@ def select_profile():
111
111
  choices = [
112
112
  "Developer with Python Tools",
113
113
  "Developer",
114
+ "Market Analyst",
114
115
  "Custom system prompt...",
115
116
  "Raw Model Session (no tools, no context)",
116
117
  ]
@@ -146,9 +147,9 @@ def select_profile():
146
147
  from jinja2 import Template
147
148
  from janito.agent.setup_agent import _prepare_template_context
148
149
 
149
- template_path = Path(
150
- "./janito/agent/templates/profiles/system_prompt_template_Developer.txt.j2"
151
- )
150
+ # Get the absolute path relative to the current script location
151
+ current_dir = Path(__file__).parent
152
+ template_path = current_dir / "../../agent/templates/profiles/system_prompt_template_developer.txt.j2"
152
153
  with open(template_path, "r", encoding="utf-8") as f:
153
154
  template_content = f.read()
154
155
 
@@ -156,4 +157,20 @@ def select_profile():
156
157
  context = _prepare_template_context("developer", "Developer", None)
157
158
  prompt = template.render(**context)
158
159
  return {"profile": "Developer", "profile_system_prompt": prompt}
160
+ elif answer == "Market Analyst":
161
+ # Return the content of the built-in Market Analyst profile prompt
162
+ from pathlib import Path
163
+ from jinja2 import Template
164
+ from janito.agent.setup_agent import _prepare_template_context
165
+
166
+ # Get the absolute path relative to the current script location
167
+ current_dir = Path(__file__).parent
168
+ template_path = current_dir / "../../agent/templates/profiles/system_prompt_template_market_analyst.txt.j2"
169
+ with open(template_path, "r", encoding="utf-8") as f:
170
+ template_content = f.read()
171
+
172
+ template = Template(template_content)
173
+ context = _prepare_template_context("market_analyst", "Market Analyst", None)
174
+ prompt = template.render(**context)
175
+ return {"profile": "Market Analyst", "profile_system_prompt": prompt}
159
176
  return answer
File without changes
File without changes
@@ -19,7 +19,35 @@ def _extract_profile_name(filename: str) -> str:
19
19
  filename = filename[len(_PREFIX) :]
20
20
  if filename.endswith(_SUFFIX):
21
21
  filename = filename[: -len(_SUFFIX)]
22
- return filename.replace("_", " ")
22
+
23
+ # Convert to title case for consistent capitalization, but handle common acronyms
24
+ name = filename.replace("_", " ")
25
+
26
+ # Convert to proper title case with consistent capitalization
27
+ name = filename.replace("_", " ")
28
+
29
+ # Handle special cases and acronyms
30
+ special_cases = {
31
+ "python": "Python",
32
+ "tools": "Tools",
33
+ "model": "Model",
34
+ "context": "Context",
35
+ "developer": "Developer",
36
+ "analyst": "Analyst",
37
+ "conversation": "Conversation",
38
+ "without": "Without"
39
+ }
40
+
41
+ words = name.split()
42
+ capitalized_words = []
43
+ for word in words:
44
+ lower_word = word.lower()
45
+ if lower_word in special_cases:
46
+ capitalized_words.append(special_cases[lower_word])
47
+ else:
48
+ capitalized_words.append(word.capitalize())
49
+
50
+ return " ".join(capitalized_words)
23
51
 
24
52
 
25
53
  def _gather_default_profiles():
@@ -60,7 +60,17 @@ def _load_template(profile, templates_dir):
60
60
  ).open("r", encoding="utf-8") as file:
61
61
  template_content = file.read()
62
62
  except (FileNotFoundError, ModuleNotFoundError, AttributeError):
63
- return template_filename, None
63
+ # Also check user profiles directory
64
+ from pathlib import Path
65
+ import os
66
+ user_profiles_dir = Path(os.path.expanduser("~/.janito/profiles"))
67
+ user_template_path = user_profiles_dir / template_filename
68
+ if user_template_path.exists():
69
+ with open(user_template_path, "r", encoding="utf-8") as file:
70
+ template_content = file.read()
71
+ else:
72
+ template_content = None
73
+ return template_filename, template_content
64
74
  return template_filename, template_content
65
75
 
66
76
 
@@ -117,9 +127,40 @@ def handle_show_system_prompt(args):
117
127
 
118
128
  if not template_content:
119
129
  if profile:
120
- raise FileNotFoundError(
121
- f"[janito] Could not find profile-specific template '{template_filename}' in {templates_dir / template_filename} nor in janito.agent.templates.profiles package."
122
- )
130
+ from janito.cli.cli_commands.list_profiles import _gather_default_profiles, _gather_user_profiles
131
+ import re
132
+
133
+ default_profiles = _gather_default_profiles()
134
+ user_profiles = _gather_user_profiles()
135
+
136
+ available_profiles = []
137
+ if default_profiles:
138
+ available_profiles.extend([(p, "default") for p in default_profiles])
139
+ if user_profiles:
140
+ available_profiles.extend([(p, "user") for p in user_profiles])
141
+
142
+ # Normalize the input profile for better matching suggestions
143
+ normalized_input = re.sub(r"\s+", " ", profile.strip().lower())
144
+
145
+ if available_profiles:
146
+ profile_list = "\n".join([f" - {name} ({source})" for name, source in available_profiles])
147
+
148
+ # Find close matches
149
+ close_matches = []
150
+ for name, source in available_profiles:
151
+ normalized_name = name.lower()
152
+ if normalized_input in normalized_name or normalized_name in normalized_input:
153
+ close_matches.append(name)
154
+
155
+ suggestion = ""
156
+ if close_matches:
157
+ suggestion = f"\nDid you mean: {', '.join(close_matches)}?"
158
+
159
+ error_msg = f"[janito] Could not find profile '{profile}'. Available profiles:\n{profile_list}{suggestion}"
160
+ else:
161
+ error_msg = f"[janito] Could not find profile '{profile}'. No profiles available."
162
+
163
+ raise FileNotFoundError(error_msg)
123
164
  else:
124
165
  print(
125
166
  f"[janito] Could not find {template_filename} in {templates_dir / template_filename} nor in janito.agent.templates.profiles package."
File without changes
File without changes
File without changes
File without changes
janito/llm/README.md CHANGED
File without changes
janito/mkdocs.yml CHANGED
File without changes
File without changes
File without changes
@@ -3,6 +3,15 @@
3
3
  from janito.llm.model import LLMModelInfo
4
4
 
5
5
  MODEL_SPECS = {
6
+ "openai/gpt-oss-120b": LLMModelInfo(
7
+ name="openai/gpt-oss-120b",
8
+ context=128000,
9
+ max_input=128000,
10
+ max_response=4096,
11
+ max_cot=4096,
12
+ thinking_supported=True,
13
+ category="IBM WatsonX",
14
+ ),
6
15
  "ibm/granite-3-8b-instruct": LLMModelInfo(
7
16
  name="ibm/granite-3-8b-instruct",
8
17
  context=128000,
janito/shell.bak.zip CHANGED
File without changes
File without changes
janito/tools/README.md CHANGED
File without changes
@@ -1,4 +1,8 @@
1
1
  import requests
2
+ import time
3
+ import os
4
+ import json
5
+ from pathlib import Path
2
6
  from bs4 import BeautifulSoup
3
7
  from janito.tools.adapters.local.adapter import register_local_tool
4
8
  from janito.tools.tool_base import ToolBase, ToolPermissions
@@ -18,6 +22,7 @@ class FetchUrlTool(ToolBase):
18
22
  max_length (int, optional): Maximum number of characters to return. Defaults to 5000.
19
23
  max_lines (int, optional): Maximum number of lines to return. Defaults to 200.
20
24
  context_chars (int, optional): Characters of context around search matches. Defaults to 400.
25
+ timeout (int, optional): Timeout in seconds for the HTTP request. Defaults to 10.
21
26
  Returns:
22
27
  str: Extracted text content from the web page, or a warning message. Example:
23
28
  - "<main text content...>"
@@ -28,15 +33,101 @@ class FetchUrlTool(ToolBase):
28
33
  permissions = ToolPermissions(read=True)
29
34
  tool_name = "fetch_url"
30
35
 
31
- def _fetch_url_content(self, url: str) -> str:
36
+ def __init__(self):
37
+ super().__init__()
38
+ self.cache_dir = Path.home() / ".janito" / "cache" / "fetch_url"
39
+ self.cache_dir.mkdir(parents=True, exist_ok=True)
40
+ self.cache_file = self.cache_dir / "error_cache.json"
41
+ self._load_cache()
42
+
43
+ def _load_cache(self):
44
+ """Load error cache from disk."""
45
+ if self.cache_file.exists():
46
+ try:
47
+ with open(self.cache_file, 'r', encoding='utf-8') as f:
48
+ self.error_cache = json.load(f)
49
+ except (json.JSONDecodeError, IOError):
50
+ self.error_cache = {}
51
+ else:
52
+ self.error_cache = {}
53
+
54
+ def _save_cache(self):
55
+ """Save error cache to disk."""
56
+ try:
57
+ with open(self.cache_file, 'w', encoding='utf-8') as f:
58
+ json.dump(self.error_cache, f, indent=2)
59
+ except IOError:
60
+ pass # Silently fail if we can't write cache
61
+
62
+ def _get_cached_error(self, url: str) -> tuple[str, bool]:
63
+ """
64
+ Check if we have a cached error for this URL.
65
+ Returns (error_message, is_cached) tuple.
66
+ """
67
+ if url not in self.error_cache:
68
+ return None, False
69
+
70
+ entry = self.error_cache[url]
71
+ current_time = time.time()
72
+
73
+ # Different expiration times for different status codes
74
+ if entry['status_code'] == 403:
75
+ # Cache 403 errors for 24 hours (more permanent)
76
+ expiration_time = 24 * 3600
77
+ elif entry['status_code'] == 404:
78
+ # Cache 404 errors for 1 hour (more temporary)
79
+ expiration_time = 3600
80
+ else:
81
+ # Cache other 4xx errors for 30 minutes
82
+ expiration_time = 1800
83
+
84
+ if current_time - entry['timestamp'] > expiration_time:
85
+ # Cache expired, remove it
86
+ del self.error_cache[url]
87
+ self._save_cache()
88
+ return None, False
89
+
90
+ return entry['message'], True
91
+
92
+ def _cache_error(self, url: str, status_code: int, message: str):
93
+ """Cache an HTTP error response."""
94
+ self.error_cache[url] = {
95
+ 'status_code': status_code,
96
+ 'message': message,
97
+ 'timestamp': time.time()
98
+ }
99
+ self._save_cache()
100
+
101
+ def _fetch_url_content(self, url: str, timeout: int = 10) -> str:
32
102
  """Fetch URL content and handle HTTP errors."""
103
+ # Check cache first for known errors
104
+ cached_error, is_cached = self._get_cached_error(url)
105
+ if cached_error:
106
+ self.report_warning(
107
+ tr(
108
+ "ℹ️ Using cached HTTP error for URL: {url}",
109
+ url=url,
110
+ ),
111
+ ReportAction.READ,
112
+ )
113
+ return cached_error
114
+
33
115
  try:
34
- response = requests.get(url, timeout=10)
116
+ response = requests.get(url, timeout=timeout)
35
117
  response.raise_for_status()
36
118
  return response.text
37
119
  except requests.exceptions.HTTPError as http_err:
38
120
  status_code = http_err.response.status_code if http_err.response else None
39
121
  if status_code and 400 <= status_code < 500:
122
+ error_message = tr(
123
+ "Warning: HTTP {status_code} error for URL: {url}",
124
+ status_code=status_code,
125
+ url=url,
126
+ )
127
+ # Cache 403 and 404 errors
128
+ if status_code in [403, 404]:
129
+ self._cache_error(url, status_code, error_message)
130
+
40
131
  self.report_error(
41
132
  tr(
42
133
  "❗ HTTP {status_code} error for URL: {url}",
@@ -45,11 +136,7 @@ class FetchUrlTool(ToolBase):
45
136
  ),
46
137
  ReportAction.READ,
47
138
  )
48
- return tr(
49
- "Warning: HTTP {status_code} error for URL: {url}",
50
- status_code=status_code,
51
- url=url,
52
- )
139
+ return error_message
53
140
  else:
54
141
  self.report_error(
55
142
  tr(
@@ -123,6 +210,7 @@ class FetchUrlTool(ToolBase):
123
210
  max_length: int = 5000,
124
211
  max_lines: int = 200,
125
212
  context_chars: int = 400,
213
+ timeout: int = 10,
126
214
  ) -> str:
127
215
  if not url.strip():
128
216
  self.report_warning(tr("ℹ️ Empty URL provided."), ReportAction.READ)
@@ -131,7 +219,7 @@ class FetchUrlTool(ToolBase):
131
219
  self.report_action(tr("🌐 Fetch URL '{url}' ...", url=url), ReportAction.READ)
132
220
 
133
221
  # Fetch URL content
134
- html_content = self._fetch_url_content(url)
222
+ html_content = self._fetch_url_content(url, timeout=timeout)
135
223
  if html_content.startswith("Warning:"):
136
224
  return html_content
137
225
 
File without changes