janito 2.3.1__py3-none-any.whl → 2.5.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 (107) hide show
  1. janito/__init__.py +1 -1
  2. janito/_version.py +57 -0
  3. janito/agent/setup_agent.py +95 -21
  4. janito/agent/templates/profiles/system_prompt_template_assistant.txt.j2 +1 -0
  5. janito/agent/templates/profiles/system_prompt_template_developer.txt.j2 +44 -0
  6. janito/cli/chat_mode/bindings.py +21 -2
  7. janito/cli/chat_mode/chat_entry.py +2 -3
  8. janito/cli/chat_mode/prompt_style.py +5 -0
  9. janito/cli/chat_mode/script_runner.py +153 -0
  10. janito/cli/chat_mode/session.py +128 -122
  11. janito/cli/chat_mode/session_profile_select.py +80 -0
  12. janito/cli/chat_mode/shell/commands/__init__.py +19 -9
  13. janito/cli/chat_mode/shell/commands/_priv_check.py +5 -0
  14. janito/cli/chat_mode/shell/commands/bang.py +36 -0
  15. janito/cli/chat_mode/shell/commands/conversation_restart.py +31 -24
  16. janito/cli/chat_mode/shell/commands/execute.py +42 -0
  17. janito/cli/chat_mode/shell/commands/help.py +7 -4
  18. janito/cli/chat_mode/shell/commands/model.py +28 -0
  19. janito/cli/chat_mode/shell/commands/prompt.py +0 -8
  20. janito/cli/chat_mode/shell/commands/read.py +37 -0
  21. janito/cli/chat_mode/shell/commands/tools.py +45 -18
  22. janito/cli/chat_mode/shell/commands/write.py +37 -0
  23. janito/cli/chat_mode/shell/commands.bak.zip +0 -0
  24. janito/cli/chat_mode/shell/input_history.py +1 -1
  25. janito/cli/chat_mode/shell/session/manager.py +0 -68
  26. janito/cli/chat_mode/shell/session.bak.zip +0 -0
  27. janito/cli/chat_mode/toolbar.py +44 -27
  28. janito/cli/cli_commands/list_tools.py +44 -11
  29. janito/cli/cli_commands/model_utils.py +95 -95
  30. janito/cli/cli_commands/show_system_prompt.py +57 -14
  31. janito/cli/config.py +5 -6
  32. janito/cli/core/getters.py +33 -33
  33. janito/cli/core/runner.py +27 -20
  34. janito/cli/core/setters.py +10 -1
  35. janito/cli/main_cli.py +40 -10
  36. janito/cli/prompt_core.py +18 -2
  37. janito/cli/prompt_setup.py +56 -0
  38. janito/cli/rich_terminal_reporter.py +21 -6
  39. janito/cli/single_shot_mode/handler.py +24 -77
  40. janito/cli/verbose_output.py +1 -1
  41. janito/config_manager.py +125 -112
  42. janito/drivers/dashscope.bak.zip +0 -0
  43. janito/drivers/driver_registry.py +0 -2
  44. janito/drivers/openai/README.md +20 -0
  45. janito/drivers/openai_responses.bak.zip +0 -0
  46. janito/event_bus/event.py +2 -2
  47. janito/formatting_token.py +7 -6
  48. janito/i18n/pt.py +0 -1
  49. janito/llm/README.md +23 -0
  50. janito/llm/agent.py +80 -16
  51. janito/llm/auth.py +63 -63
  52. janito/llm/driver.py +8 -0
  53. janito/provider_registry.py +178 -176
  54. janito/providers/__init__.py +0 -2
  55. janito/providers/azure_openai/model_info.py +16 -16
  56. janito/providers/dashscope.bak.zip +0 -0
  57. janito/providers/provider_static_info.py +0 -3
  58. janito/providers/registry.py +26 -26
  59. janito/shell.bak.zip +0 -0
  60. janito/tools/DOCSTRING_STANDARD.txt +33 -0
  61. janito/tools/README.md +3 -0
  62. janito/tools/__init__.py +20 -6
  63. janito/tools/adapters/local/__init__.py +65 -62
  64. janito/tools/adapters/local/adapter.py +18 -35
  65. janito/tools/adapters/local/ask_user.py +3 -4
  66. janito/tools/adapters/local/copy_file.py +2 -2
  67. janito/tools/adapters/local/create_directory.py +2 -2
  68. janito/tools/adapters/local/create_file.py +2 -2
  69. janito/tools/adapters/local/delete_text_in_file.py +2 -2
  70. janito/tools/adapters/local/fetch_url.py +2 -2
  71. janito/tools/adapters/local/find_files.py +2 -1
  72. janito/tools/adapters/local/get_file_outline/core.py +2 -2
  73. janito/tools/adapters/local/get_file_outline/search_outline.py +2 -2
  74. janito/tools/adapters/local/move_file.py +2 -2
  75. janito/tools/adapters/local/open_html_in_browser.py +2 -1
  76. janito/tools/adapters/local/open_url.py +2 -2
  77. janito/tools/adapters/local/python_code_run.py +3 -3
  78. janito/tools/adapters/local/python_command_run.py +3 -3
  79. janito/tools/adapters/local/python_file_run.py +3 -3
  80. janito/tools/adapters/local/remove_directory.py +2 -2
  81. janito/tools/adapters/local/remove_file.py +2 -2
  82. janito/tools/adapters/local/replace_text_in_file.py +2 -2
  83. janito/tools/adapters/local/run_bash_command.py +3 -3
  84. janito/tools/adapters/local/run_powershell_command.py +3 -3
  85. janito/tools/adapters/local/search_text/core.py +2 -2
  86. janito/tools/adapters/local/validate_file_syntax/core.py +3 -3
  87. janito/tools/adapters/local/view_file.py +2 -1
  88. janito/tools/outline_file.bak.zip +0 -0
  89. janito/tools/permissions.py +45 -0
  90. janito/tools/permissions_parse.py +12 -0
  91. janito/tools/tool_base.py +14 -11
  92. janito/tools/tool_utils.py +4 -6
  93. janito/tools/tools_adapter.py +25 -20
  94. {janito-2.3.1.dist-info → janito-2.5.0.dist-info}/METADATA +46 -24
  95. {janito-2.3.1.dist-info → janito-2.5.0.dist-info}/RECORD +99 -82
  96. janito/agent/templates/profiles/system_prompt_template_base_pt.txt.j2 +0 -13
  97. janito/agent/templates/profiles/system_prompt_template_main.txt.j2 +0 -37
  98. janito/cli/chat_mode/shell/commands/edit.py +0 -25
  99. janito/cli/chat_mode/shell/commands/exec.py +0 -27
  100. janito/cli/chat_mode/shell/commands/termweb_log.py +0 -92
  101. janito/cli/termweb_starter.py +0 -122
  102. janito/termweb/app.py +0 -95
  103. janito/version.py +0 -4
  104. {janito-2.3.1.dist-info → janito-2.5.0.dist-info}/WHEEL +0 -0
  105. {janito-2.3.1.dist-info → janito-2.5.0.dist-info}/entry_points.txt +0 -0
  106. {janito-2.3.1.dist-info → janito-2.5.0.dist-info}/licenses/LICENSE +0 -0
  107. {janito-2.3.1.dist-info → janito-2.5.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,56 @@
1
+ """
2
+ Shared utilities to set up an agent together with a GenericPromptHandler that
3
+ both single–shot and chat modes can reuse. Having one central place avoids the
4
+ code duplication that previously existed in `chat_mode.session.ChatSession` and
5
+ `single_shot_mode.handler.PromptHandler`.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from janito.agent.setup_agent import create_configured_agent
10
+ from janito.cli.prompt_core import (
11
+ PromptHandler as GenericPromptHandler,
12
+ )
13
+ from typing import Any, Optional
14
+
15
+
16
+ def setup_agent_and_prompt_handler(
17
+ *,
18
+ args: Any,
19
+ provider_instance: Any,
20
+ llm_driver_config: Any,
21
+ role: Optional[str] = None,
22
+ verbose_tools: bool = False,
23
+ verbose_agent: bool = False,
24
+
25
+ allowed_permissions: Optional[list[str]] = None,
26
+ profile: Optional[str] = None,
27
+ profile_system_prompt: Optional[str] = None,
28
+ conversation_history: Any = None,
29
+ ):
30
+ """Create a configured *agent* as well as a *GenericPromptHandler* bound to
31
+ that agent and return them as a tuple.
32
+
33
+ This helper consolidates the repetitive boiler-plate that was scattered
34
+ across *single-shot* and *chat* modes – both of which need an agent plus a
35
+ prompt handler that points to that agent.
36
+ """
37
+ agent = create_configured_agent(
38
+ provider_instance=provider_instance,
39
+ llm_driver_config=llm_driver_config,
40
+ role=role,
41
+ verbose_tools=verbose_tools,
42
+ verbose_agent=verbose_agent,
43
+
44
+ allowed_permissions=allowed_permissions,
45
+ profile=profile,
46
+ profile_system_prompt=profile_system_prompt,
47
+ )
48
+
49
+ prompt_handler = GenericPromptHandler(
50
+ args=args,
51
+ conversation_history=conversation_history,
52
+ provider_instance=provider_instance,
53
+ )
54
+ prompt_handler.agent = agent
55
+
56
+ return agent, prompt_handler
@@ -10,6 +10,8 @@ from janito.event_bus.bus import event_bus
10
10
  from janito.llm import message_parts
11
11
 
12
12
 
13
+ import sys
14
+
13
15
  class RichTerminalReporter(EventHandlerBase):
14
16
  """
15
17
  Handles UI rendering for janito events using Rich.
@@ -63,8 +65,15 @@ class RichTerminalReporter(EventHandlerBase):
63
65
  self.console.print(Markdown(part.content))
64
66
  self.console.file.flush()
65
67
 
68
+ def delete_current_line(self):
69
+ """
70
+ Clears the entire current line in the terminal and returns the cursor to column 1.
71
+ """
72
+ sys.stdout.write('\033[2K\r')
73
+ sys.stdout.flush()
74
+
66
75
  def on_RequestFinished(self, event):
67
- self.console.print("") # Print end of line after waiting message
76
+ self.delete_current_line()
68
77
  self._waiting_printed = False
69
78
  response = getattr(event, "response", None)
70
79
  error = getattr(event, "error", None)
@@ -99,14 +108,14 @@ class RichTerminalReporter(EventHandlerBase):
99
108
  if not msg or not subtype:
100
109
  return
101
110
  if subtype == ReportSubtype.ACTION_INFO:
102
- if getattr(event, "action", None) in (
111
+ # Use orange for modification actions, cyan otherwise
112
+ modification_actions = (
103
113
  getattr(ReportAction, "UPDATE", None),
104
114
  getattr(ReportAction, "WRITE", None),
105
115
  getattr(ReportAction, "DELETE", None),
106
- ):
107
- self.console.print(f"[magenta]{msg}[/magenta]", end="")
108
- else:
109
- self.console.print(msg, end="")
116
+ )
117
+ style = "orange1" if getattr(event, "action", None) in modification_actions else "cyan"
118
+ self.console.print(Text(msg, style=style), end="")
110
119
  self.console.file.flush()
111
120
  elif subtype in (
112
121
  ReportSubtype.SUCCESS,
@@ -115,6 +124,12 @@ class RichTerminalReporter(EventHandlerBase):
115
124
  ):
116
125
  self.console.print(msg)
117
126
  self.console.file.flush()
127
+ elif subtype == ReportSubtype.STDOUT:
128
+ self.console.print(Text(msg, style="on dark_green"))
129
+ self.console.file.flush()
130
+ elif subtype == ReportSubtype.STDERR:
131
+ self.console.print(Text(msg, style="on red"))
132
+ self.console.file.flush()
118
133
  else:
119
134
  self.console.print(msg)
120
135
  self.console.file.flush()
@@ -2,48 +2,31 @@
2
2
  PromptHandler: Handles prompt submission and response formatting for janito CLI (one-shot prompt execution).
3
3
  """
4
4
 
5
- import time
6
- from janito.version import __version__ as VERSION
7
- from janito.cli.prompt_core import PromptHandler as GenericPromptHandler
8
- from janito.cli.verbose_output import (
9
- print_verbose_header,
10
- print_performance,
11
- handle_exception,
12
- )
5
+ from __future__ import annotations
6
+
7
+ from janito.cli.prompt_setup import setup_agent_and_prompt_handler
13
8
  import janito.tools # Ensure all tools are registered
14
9
  from janito.cli.console import shared_console
15
10
 
16
11
 
17
12
  class PromptHandler:
18
- def __init__(self, args, provider_instance, llm_driver_config, role=None, exec_enabled=False):
13
+ def __init__(self, args, provider_instance, llm_driver_config, role=None, allowed_permissions=None):
19
14
  self.args = args
20
15
  self.provider_instance = provider_instance
21
16
  self.llm_driver_config = llm_driver_config
22
17
  self.role = role
23
- self.exec_enabled = exec_enabled
24
- from janito.agent.setup_agent import create_configured_agent
25
-
26
- # DEBUG: Print exec_enabled propagation
27
- self.agent = create_configured_agent(
18
+ # Instantiate agent together with prompt handler using the shared helper
19
+ self.agent, self.generic_handler = setup_agent_and_prompt_handler(
20
+ args=args,
28
21
  provider_instance=provider_instance,
29
22
  llm_driver_config=llm_driver_config,
30
23
  role=role,
31
24
  verbose_tools=getattr(args, "verbose_tools", False),
32
25
  verbose_agent=getattr(args, "verbose_agent", False),
33
- exec_enabled=exec_enabled,
34
- )
35
- # Setup conversation/history if needed
36
- # Dynamically enable/disable execution tools in the registry
37
- try:
38
- registry = __import__('janito.tools', fromlist=['get_local_tools_adapter']).get_local_tools_adapter()
39
- if hasattr(registry, 'set_execution_tools_enabled'):
40
- registry.set_execution_tools_enabled(exec_enabled)
41
- except Exception as e:
42
- shared_console.print(f"[yellow]Warning: Could not update execution tools dynamically in single-shot mode: {e}[/yellow]")
43
- self.generic_handler = GenericPromptHandler(
44
- args, [], provider_instance=provider_instance
26
+
27
+ allowed_permissions=allowed_permissions,
28
+ profile=getattr(args, "profile", None),
45
29
  )
46
- self.generic_handler.agent = self.agent
47
30
 
48
31
  def handle(self) -> None:
49
32
  import traceback
@@ -58,13 +41,18 @@ class PromptHandler:
58
41
  shared_console.print(
59
42
  "[yellow]Warning: Some characters in your input were not valid UTF-8 and have been replaced.[/yellow]"
60
43
  )
44
+ import time
61
45
  try:
46
+ start_time = time.time()
62
47
  self.generic_handler.handle_prompt(
63
48
  sanitized,
64
49
  args=self.args,
65
50
  print_header=True,
66
51
  raw=getattr(self.args, "raw", False),
67
52
  )
53
+ end_time = time.time()
54
+ elapsed = end_time - start_time
55
+ self._post_prompt_actions(elapsed=elapsed)
68
56
  if hasattr(self.args, "verbose_agent") and self.args.verbose_agent:
69
57
  print("[debug] handle_prompt() completed without exception.")
70
58
  except Exception as e:
@@ -72,59 +60,18 @@ class PromptHandler:
72
60
  f"[error] Exception occurred in handle_prompt: {type(e).__name__}: {e}"
73
61
  )
74
62
  traceback.print_exc()
75
- self._post_prompt_actions()
76
-
77
- def _post_prompt_actions(self):
78
- final_event = getattr(self.agent, "last_event", None)
79
- if final_event is not None:
80
- self._print_exit_reason_and_parts(final_event)
81
- # --- BEGIN: Print token info in rich rule if --verbose is set ---
82
- if hasattr(self.args, "verbose") and self.args.verbose:
83
- from janito.perf_singleton import performance_collector
84
63
 
85
- token_info = performance_collector.get_last_request_usage()
86
- from rich.rule import Rule
87
- from rich import print as rich_print
88
- from janito.cli.utils import format_tokens
89
-
90
- if token_info:
91
- if isinstance(token_info, dict):
92
- token_str = " | ".join(
93
- f"{k}: {format_tokens(v) if isinstance(v, int) else v}"
94
- for k, v in token_info.items()
95
- )
96
- else:
97
- token_str = str(token_info)
98
- rich_print(Rule(f"[bold cyan]Token Usage[/bold cyan] {token_str}"))
99
- else:
100
- rich_print(Rule("[cyan]No token usage info available.[/cyan]"))
101
- else:
102
- shared_console.print("[yellow]No output produced by the model.[/yellow]")
64
+ def _post_prompt_actions(self, elapsed=None):
65
+ # Align with chat mode: only print token usage summary
66
+ import sys
67
+ from janito.formatting_token import print_token_message_summary
68
+ from janito.perf_singleton import performance_collector
69
+ usage = performance_collector.get_last_request_usage()
70
+ # If running in stdin mode, do not print token usage
71
+ if sys.stdin.isatty():
72
+ print_token_message_summary(shared_console, msg_count=1, usage=usage, elapsed=elapsed)
103
73
  self._cleanup_driver_and_console()
104
74
 
105
- def _print_exit_reason_and_parts(self, final_event):
106
- exit_reason = (
107
- getattr(final_event, "metadata", {}).get("exit_reason")
108
- if hasattr(final_event, "metadata")
109
- else None
110
- )
111
- if exit_reason:
112
- print(f"[bold yellow]Exit reason: {exit_reason}[/bold yellow]")
113
- parts = getattr(final_event, "parts", None)
114
- if not exit_reason:
115
- if parts is None or len(parts) == 0:
116
- shared_console.print(
117
- "[yellow]No output produced by the model.[/yellow]"
118
- )
119
- else:
120
- if hasattr(self.args, "verbose_agent") and self.args.verbose_agent:
121
- print(
122
- "[yellow]No user-visible output. Model returned the following parts:"
123
- )
124
- for idx, part in enumerate(parts):
125
- print(
126
- f" [part {idx}] type: {type(part).__name__} | content: {getattr(part, 'content', repr(part))}"
127
- )
128
75
 
129
76
  def _cleanup_driver_and_console(self):
130
77
  if hasattr(self.agent, "join_driver"):
@@ -6,7 +6,7 @@ from rich import print as rich_print
6
6
  from rich.align import Align
7
7
  from rich.panel import Panel
8
8
  from rich.text import Text
9
- from janito.version import __version__ as VERSION
9
+ from janito import __version__ as VERSION
10
10
  from janito.cli.utils import format_tokens
11
11
 
12
12
 
janito/config_manager.py CHANGED
@@ -1,112 +1,125 @@
1
- import json
2
- from pathlib import Path
3
- from threading import Lock
4
-
5
-
6
- class ConfigManager:
7
- """
8
- Unified configuration manager supporting:
9
- - Defaults
10
- - File-based configuration
11
- - Runtime overrides (e.g., CLI args)
12
- """
13
-
14
- _instance = None
15
- _lock = Lock()
16
-
17
- def __new__(cls, *args, **kwargs):
18
- with cls._lock:
19
- if not cls._instance:
20
- cls._instance = super(ConfigManager, cls).__new__(cls)
21
- return cls._instance
22
-
23
- def __init__(self, config_path=None, defaults=None, runtime_overrides=None):
24
- # Lazy single-init
25
- if hasattr(self, "_initialized") and self._initialized:
26
- return
27
- self._initialized = True
28
-
29
- self.config_path = Path(config_path or Path.home() / ".janito" / "config.json")
30
- self.defaults = dict(defaults) if defaults else {}
31
- self.file_config = {}
32
- self.runtime_overrides = dict(runtime_overrides) if runtime_overrides else {}
33
- self._load_file_config()
34
-
35
- def _load_file_config(self):
36
- if self.config_path.exists():
37
- with open(self.config_path, "r", encoding="utf-8") as f:
38
- try:
39
- self.file_config = json.load(f)
40
- except Exception:
41
- self.file_config = {}
42
- else:
43
- self.file_config = {}
44
-
45
- def save(self):
46
- self.config_path.parent.mkdir(parents=True, exist_ok=True)
47
- with open(self.config_path, "w", encoding="utf-8") as f:
48
- json.dump(self.file_config, f, indent=2)
49
- f.write("\n")
50
-
51
- def get(self, key, default=None):
52
- # Precedence: runtime_overrides > file_config > defaults
53
- for layer in (self.runtime_overrides, self.file_config, self.defaults):
54
- if key in layer and layer[key] is not None:
55
- return layer[key]
56
- return default
57
-
58
- def runtime_set(self, key, value):
59
- self.runtime_overrides[key] = value
60
-
61
- def file_set(self, key, value):
62
- # Always reload, update, and persist
63
- self._load_file_config()
64
- self.file_config[key] = value
65
- with open(self.config_path, "w", encoding="utf-8") as f:
66
- json.dump(self.file_config, f, indent=2)
67
- f.write("\n")
68
-
69
- def all(self, layered=False):
70
- merged = dict(self.defaults)
71
- merged.update(self.file_config)
72
- merged.update(self.runtime_overrides)
73
- if layered:
74
- # Only file+runtime, i.e., what is saved to disk
75
- d = dict(self.file_config)
76
- d.update(self.runtime_overrides)
77
- return d
78
- return merged
79
-
80
- # Namespaced provider/model config
81
- def get_provider_config(self, provider, default=None):
82
- providers = self.file_config.get("providers") or {}
83
- return providers.get(provider) or (default or {})
84
-
85
- def set_provider_config(self, provider, key, value):
86
- if "providers" not in self.file_config:
87
- self.file_config["providers"] = {}
88
- if provider not in self.file_config["providers"]:
89
- self.file_config["providers"][provider] = {}
90
- self.file_config["providers"][provider][key] = value
91
-
92
- def get_provider_model_config(self, provider, model, default=None):
93
- return (
94
- self.file_config.get("providers")
95
- or {}.get(provider, {}).get("models", {}).get(model)
96
- or (default or {})
97
- )
98
-
99
- def set_provider_model_config(self, provider, model, key, value):
100
- if "providers" not in self.file_config:
101
- self.file_config["providers"] = {}
102
- if provider not in self.file_config["providers"]:
103
- self.file_config["providers"][provider] = {}
104
- if "models" not in self.file_config["providers"][provider]:
105
- self.file_config["providers"][provider]["models"] = {}
106
- if model not in self.file_config["providers"][provider]["models"]:
107
- self.file_config["providers"][provider]["models"][model] = {}
108
- self.file_config["providers"][provider]["models"][model][key] = value
109
-
110
- # Support loading runtime overrides after init (e.g. after parsing CLI args)
111
- def apply_runtime_overrides(self, overrides_dict):
112
- self.runtime_overrides.update(overrides_dict)
1
+ import json
2
+ from pathlib import Path
3
+ from threading import Lock
4
+
5
+
6
+ class ConfigManager:
7
+ """
8
+ Unified configuration manager supporting:
9
+ - Defaults
10
+ - File-based configuration
11
+ - Runtime overrides (e.g., CLI args)
12
+ """
13
+
14
+ _instance = None
15
+ _lock = Lock()
16
+
17
+ def __new__(cls, *args, **kwargs):
18
+ with cls._lock:
19
+ if not cls._instance:
20
+ cls._instance = super(ConfigManager, cls).__new__(cls)
21
+ return cls._instance
22
+
23
+ def __init__(self, config_path=None, defaults=None, runtime_overrides=None):
24
+ # Lazy single-init
25
+ if hasattr(self, "_initialized") and self._initialized:
26
+ return
27
+ self._initialized = True
28
+
29
+ self.config_path = Path(config_path or Path.home() / ".janito" / "config.json")
30
+ self.defaults = dict(defaults) if defaults else {}
31
+ self.file_config = {}
32
+ self.runtime_overrides = dict(runtime_overrides) if runtime_overrides else {}
33
+ self._load_file_config()
34
+ self._apply_tool_permissions_on_startup()
35
+
36
+ def _apply_tool_permissions_on_startup(self):
37
+ # On startup, read tool_permissions from config and set global permissions
38
+ perm_str = self.file_config.get("tool_permissions")
39
+ if perm_str:
40
+ try:
41
+ from janito.tools.permissions_parse import parse_permissions_string
42
+ from janito.tools.permissions import set_global_allowed_permissions
43
+ perms = parse_permissions_string(perm_str)
44
+ set_global_allowed_permissions(perms)
45
+ except Exception as e:
46
+ print(f"Warning: Failed to apply tool_permissions from config: {e}")
47
+
48
+ def _load_file_config(self):
49
+ if self.config_path.exists():
50
+ with open(self.config_path, "r", encoding="utf-8") as f:
51
+ try:
52
+ self.file_config = json.load(f)
53
+ except Exception:
54
+ self.file_config = {}
55
+ else:
56
+ self.file_config = {}
57
+
58
+ def save(self):
59
+ self.config_path.parent.mkdir(parents=True, exist_ok=True)
60
+ with open(self.config_path, "w", encoding="utf-8") as f:
61
+ json.dump(self.file_config, f, indent=2)
62
+ f.write("\n")
63
+
64
+ def get(self, key, default=None):
65
+ # Precedence: runtime_overrides > file_config > defaults
66
+ for layer in (self.runtime_overrides, self.file_config, self.defaults):
67
+ if key in layer and layer[key] is not None:
68
+ return layer[key]
69
+ return default
70
+
71
+ def runtime_set(self, key, value):
72
+ self.runtime_overrides[key] = value
73
+
74
+ def file_set(self, key, value):
75
+ # Always reload, update, and persist
76
+ self._load_file_config()
77
+ self.file_config[key] = value
78
+ with open(self.config_path, "w", encoding="utf-8") as f:
79
+ json.dump(self.file_config, f, indent=2)
80
+ f.write("\n")
81
+
82
+ def all(self, layered=False):
83
+ merged = dict(self.defaults)
84
+ merged.update(self.file_config)
85
+ merged.update(self.runtime_overrides)
86
+ if layered:
87
+ # Only file+runtime, i.e., what is saved to disk
88
+ d = dict(self.file_config)
89
+ d.update(self.runtime_overrides)
90
+ return d
91
+ return merged
92
+
93
+ # Namespaced provider/model config
94
+ def get_provider_config(self, provider, default=None):
95
+ providers = self.file_config.get("providers") or {}
96
+ return providers.get(provider) or (default or {})
97
+
98
+ def set_provider_config(self, provider, key, value):
99
+ if "providers" not in self.file_config:
100
+ self.file_config["providers"] = {}
101
+ if provider not in self.file_config["providers"]:
102
+ self.file_config["providers"][provider] = {}
103
+ self.file_config["providers"][provider][key] = value
104
+
105
+ def get_provider_model_config(self, provider, model, default=None):
106
+ return (
107
+ self.file_config.get("providers")
108
+ or {}.get(provider, {}).get("models", {}).get(model)
109
+ or (default or {})
110
+ )
111
+
112
+ def set_provider_model_config(self, provider, model, key, value):
113
+ if "providers" not in self.file_config:
114
+ self.file_config["providers"] = {}
115
+ if provider not in self.file_config["providers"]:
116
+ self.file_config["providers"][provider] = {}
117
+ if "models" not in self.file_config["providers"][provider]:
118
+ self.file_config["providers"][provider]["models"] = {}
119
+ if model not in self.file_config["providers"][provider]["models"]:
120
+ self.file_config["providers"][provider]["models"][model] = {}
121
+ self.file_config["providers"][provider]["models"][model][key] = value
122
+
123
+ # Support loading runtime overrides after init (e.g. after parsing CLI args)
124
+ def apply_runtime_overrides(self, overrides_dict):
125
+ self.runtime_overrides.update(overrides_dict)
Binary file
@@ -8,13 +8,11 @@ from typing import Dict, Type
8
8
  # --- Import driver classes ---
9
9
  from janito.drivers.anthropic.driver import AnthropicModelDriver
10
10
  from janito.drivers.azure_openai.driver import AzureOpenAIModelDriver
11
- from janito.drivers.mistralai.driver import MistralAIModelDriver
12
11
  from janito.drivers.openai.driver import OpenAIModelDriver
13
12
 
14
13
  _DRIVER_REGISTRY: Dict[str, Type] = {
15
14
  "AnthropicModelDriver": AnthropicModelDriver,
16
15
  "AzureOpenAIModelDriver": AzureOpenAIModelDriver,
17
- "MistralAIModelDriver": MistralAIModelDriver,
18
16
  "OpenAIModelDriver": OpenAIModelDriver,
19
17
  }
20
18
 
@@ -0,0 +1,20 @@
1
+ # OpenAI Driver Debugging
2
+
3
+ ## HTTP Debugging via Environment Variable
4
+
5
+ To debug HTTP requests and responses for the OpenAI driver, set the environment variable `OPENAI_DEBUG_HTTP=1` before running your application. This will print the full HTTP request and response bodies to the console for troubleshooting purposes.
6
+
7
+ **Example (PowerShell):**
8
+
9
+ ```
10
+ $env:OPENAI_DEBUG_HTTP=1
11
+ python your_app.py
12
+ ```
13
+
14
+ **Example (bash):**
15
+
16
+ ```
17
+ OPENAI_DEBUG_HTTP=1 python your_app.py
18
+ ```
19
+
20
+ This feature is implemented in `janito/drivers/openai/driver.py` and works by wrapping the OpenAI client HTTP transport with a debug logger when the environment variable is set.
Binary file
janito/event_bus/event.py CHANGED
@@ -1,6 +1,6 @@
1
1
  import attr
2
2
  from typing import ClassVar
3
- from datetime import datetime
3
+ from datetime import datetime, timezone
4
4
 
5
5
 
6
6
  @attr.s(auto_attribs=True, kw_only=True)
@@ -12,4 +12,4 @@ class Event:
12
12
  """
13
13
 
14
14
  category: ClassVar[str] = "generic"
15
- timestamp: datetime = attr.ib(factory=datetime.utcnow)
15
+ timestamp: datetime = attr.ib(factory=lambda: datetime.now(timezone.utc))
@@ -25,9 +25,9 @@ def format_tokens(n, tag=None, use_rich=False):
25
25
  return val
26
26
 
27
27
 
28
- def format_token_message_summary(msg_count, usage, width=96, use_rich=False):
28
+ def format_token_message_summary(msg_count, usage, width=96, use_rich=False, elapsed=None):
29
29
  """
30
- Returns a string (rich or pt markup) summarizing message count and last token usage.
30
+ Returns a string (rich or pt markup) summarizing message count, last token usage, and elapsed time.
31
31
  """
32
32
  left = f" Messages: {'[' if use_rich else '<'}msg_count{']' if use_rich else '>'}{msg_count}{'[/msg_count]' if use_rich else '</msg_count>'}"
33
33
  tokens_part = ""
@@ -40,15 +40,16 @@ def format_token_message_summary(msg_count, usage, width=96, use_rich=False):
40
40
  f"Completion: {format_tokens(completion_tokens, 'tokens_out', use_rich)}, "
41
41
  f"Total: {format_tokens(total_tokens, 'tokens_total', use_rich)}"
42
42
  )
43
- return f"{left}{tokens_part}"
43
+ elapsed_part = f" | Elapsed: [cyan]{elapsed:.2f}s[/cyan]" if elapsed is not None else ""
44
+ return f"{left}{tokens_part}{elapsed_part}"
44
45
 
45
46
 
46
- def print_token_message_summary(console, msg_count=None, usage=None, width=96):
47
- """Prints the summary using rich markup, using defaults from perf_singleton if not given."""
47
+ def print_token_message_summary(console, msg_count=None, usage=None, width=96, elapsed=None):
48
+ """Prints the summary using rich markup, using defaults from perf_singleton if not given. Optionally includes elapsed time."""
48
49
  if usage is None:
49
50
  usage = performance_collector.get_last_request_usage()
50
51
  if msg_count is None:
51
52
  msg_count = performance_collector.get_total_turns() or 0
52
- line = format_token_message_summary(msg_count, usage, width, use_rich=True)
53
+ line = format_token_message_summary(msg_count, usage, width, use_rich=True, elapsed=elapsed)
53
54
  if line.strip():
54
55
  console.print(Rule(line))
janito/i18n/pt.py CHANGED
@@ -3,7 +3,6 @@ translations = {
3
3
  "36107ed78ab25f6fb12ad8ce13018cd1ce6735d1": "Iniciando servidor web...",
4
4
  "70a0d194687568a47aa617fd85036ace1e69a982": "Deseja realmente sair? (s/n): ",
5
5
  "5c9ebcbbd7632ecb328bd52958b17158afaa32c6": "F12 = Ação Rápida (segue a ação recomendada)",
6
- "e4034394acca752c021b2ab50f60da8273e3c314": "TermWeb iniciado... Disponível em http://localhost:{selected_port}",
7
6
  "fe21121e2934234b68d19b2757532117d440c1e3": "Chave de API não encontrada. Por favor, configure 'api_key' no seu arquivo de configuração.",
8
7
  "c9e3759b1756eba35b381ce2b72cd659e132b01f": "Olá, {name}!",
9
8
  "ca1fee2f55baabdc2e4b0e9529c89ee024e62079": "Nenhum prompt fornecido nas mensagens",
janito/llm/README.md ADDED
@@ -0,0 +1,23 @@
1
+ # janito.llm Package
2
+
3
+ This directory contains generic, provider-agnostic classes and methods for working with Large Language Models (LLMs) in the `janito` framework. Its purpose is to provide base abstractions that can be extended by provider-specific implementations.
4
+
5
+ ## Scope and Contents
6
+
7
+ - **driver.py**
8
+ - Contains `LLMDriver`, an abstract base class defining the core methods for LLM drivers. Subclasses should implement provider/model-specific logic, while benefiting from consistent streaming and event interfaces.
9
+ - **provider.py**
10
+ - Contains `LLMProvider`, an abstract base class for LLM API providers. This outlines the required interface for integrating new providers and retrieving model info or driver classes.
11
+
12
+ ## Usage
13
+ - Extend these base classes when adding new drivers or providers.
14
+ - Do not include provider-specific logic here; only generic mechanisms, patterns, or utilities applicable to any LLM integration belong in this package.
15
+
16
+ ## Example
17
+ ```python
18
+ from janito.llm.driver import LLMDriver
19
+ from janito.llm.provider import LLMProvider
20
+ ```
21
+
22
+ ---
23
+ This README clarifies the intention of the `llm` package as the generic/static contract for LLM drivers and providers.