janito 0.4.0__py3-none-any.whl → 0.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 (49) hide show
  1. janito/__init__.py +48 -1
  2. janito/__main__.py +29 -334
  3. janito/agents/__init__.py +22 -0
  4. janito/agents/agent.py +21 -0
  5. janito/{claude.py → agents/claudeai.py} +10 -5
  6. janito/agents/openai.py +53 -0
  7. janito/agents/test.py +34 -0
  8. janito/analysis/__init__.py +33 -0
  9. janito/analysis/display.py +149 -0
  10. janito/analysis/options.py +112 -0
  11. janito/analysis/prompts.py +75 -0
  12. janito/change/__init__.py +19 -0
  13. janito/change/applier.py +269 -0
  14. janito/{contentchange.py → change/content.py} +5 -27
  15. janito/change/indentation.py +33 -0
  16. janito/change/position.py +169 -0
  17. janito/changehistory.py +46 -0
  18. janito/changeviewer/__init__.py +12 -0
  19. janito/changeviewer/diff.py +28 -0
  20. janito/changeviewer/panels.py +268 -0
  21. janito/changeviewer/styling.py +59 -0
  22. janito/changeviewer/themes.py +57 -0
  23. janito/cli/__init__.py +2 -0
  24. janito/cli/commands.py +53 -0
  25. janito/cli/functions.py +286 -0
  26. janito/cli/registry.py +26 -0
  27. janito/common.py +9 -9
  28. janito/console/__init__.py +3 -0
  29. janito/console/commands.py +112 -0
  30. janito/console/core.py +62 -0
  31. janito/console/display.py +157 -0
  32. janito/fileparser.py +292 -83
  33. janito/prompts.py +21 -6
  34. janito/qa.py +7 -5
  35. janito/review.py +13 -0
  36. janito/scan.py +44 -5
  37. janito/tests/test_fileparser.py +26 -0
  38. janito-0.5.0.dist-info/METADATA +146 -0
  39. janito-0.5.0.dist-info/RECORD +45 -0
  40. janito/analysis.py +0 -281
  41. janito/changeapplier.py +0 -436
  42. janito/changeviewer.py +0 -350
  43. janito/console.py +0 -330
  44. janito-0.4.0.dist-info/METADATA +0 -164
  45. janito-0.4.0.dist-info/RECORD +0 -21
  46. /janito/{contextparser.py → _contextparser.py} +0 -0
  47. {janito-0.4.0.dist-info → janito-0.5.0.dist-info}/WHEEL +0 -0
  48. {janito-0.4.0.dist-info → janito-0.5.0.dist-info}/entry_points.txt +0 -0
  49. {janito-0.4.0.dist-info → janito-0.5.0.dist-info}/licenses/LICENSE +0 -0
janito/__init__.py CHANGED
@@ -1,2 +1,49 @@
1
+ """Core package initialization for Janito."""
1
2
 
2
- # Empty file to make the directory a Python package
3
+ from .analysis import (
4
+ AnalysisOption,
5
+ parse_analysis_options,
6
+ format_analysis,
7
+ get_history_file_type,
8
+ get_history_path,
9
+ get_timestamp,
10
+ save_to_file,
11
+ build_request_analysis_prompt,
12
+ get_option_selection,
13
+ prompt_user,
14
+ validate_option_letter
15
+ )
16
+
17
+ from .change import (
18
+ apply_single_change,
19
+ parse_and_apply_changes_sequence,
20
+ get_file_type,
21
+ process_and_save_changes,
22
+ format_parsed_changes,
23
+ apply_content_changes,
24
+ handle_changes_file
25
+ )
26
+
27
+ __all__ = [
28
+ # Analysis exports
29
+ 'AnalysisOption',
30
+ 'parse_analysis_options',
31
+ 'format_analysis',
32
+ 'get_history_file_type',
33
+ 'get_history_path',
34
+ 'get_timestamp',
35
+ 'save_to_file',
36
+ 'build_request_analysis_prompt',
37
+ 'get_option_selection',
38
+ 'prompt_user',
39
+ 'validate_option_letter',
40
+
41
+ # Change exports
42
+ 'apply_single_change',
43
+ 'parse_and_apply_changes_sequence',
44
+ 'get_file_type',
45
+ 'process_and_save_changes',
46
+ 'format_parsed_changes',
47
+ 'apply_content_changes',
48
+ 'handle_changes_file'
49
+ ]
janito/__main__.py CHANGED
@@ -1,359 +1,54 @@
1
- import sys
2
1
  import typer
3
- from typing import Optional, Dict, Any, List
2
+ from typing import Optional, List
4
3
  from pathlib import Path
5
- from janito.claude import ClaudeAPIAgent
6
- import shutil
7
- from janito.prompts import (
8
- build_selected_option_prompt,
9
- SYSTEM_PROMPT,
10
- )
11
4
  from rich.console import Console
12
- from rich.markdown import Markdown
13
- import re
14
- import tempfile
15
- import json
16
- from rich.syntax import Syntax
17
- from janito.contentchange import (
18
- handle_changes_file,
19
- get_file_type,
20
- parse_block_changes,
21
- preview_and_apply_changes,
22
- format_parsed_changes,
23
- )
24
- from rich.table import Table
25
- from rich.columns import Columns
26
- from rich.panel import Panel
27
- from rich.text import Text
28
- from rich.rule import Rule
29
- from rich import box
30
- from datetime import datetime, timezone
31
- from itertools import chain
32
- from janito.scan import collect_files_content, is_dir_empty, preview_scan
33
- from janito.qa import ask_question, display_answer
34
- from rich.prompt import Prompt, Confirm
35
- from janito.config import config
36
- from janito.version import get_version
37
- from janito.common import progress_send_message
38
- from janito.analysis import format_analysis, build_request_analysis_prompt, parse_analysis_options, get_history_file_type, AnalysisOption
39
-
40
-
41
- def prompt_user(message: str, choices: List[str] = None) -> str:
42
- """Display a prominent user prompt with optional choices using consistent colors"""
43
- console = Console()
44
-
45
- # Define consistent colors
46
- COLORS = {
47
- 'primary': '#729FCF', # Soft blue for primary elements
48
- 'secondary': '#8AE234', # Bright green for actions/success
49
- 'accent': '#AD7FA8', # Purple for accents
50
- 'muted': '#7F9F7F', # Muted green for less important text
51
- }
52
-
53
- console.print()
54
- console.print(Rule(" User Input Required ", style=f"bold {COLORS['primary']}"))
55
-
56
- if choices:
57
- choice_text = f"[{COLORS['accent']}]Options: {', '.join(choices)}[/{COLORS['accent']}]"
58
- console.print(Panel(choice_text, box=box.ROUNDED, border_style=COLORS['primary']))
59
-
60
- return Prompt.ask(f"[bold {COLORS['secondary']}]> {message}[/bold {COLORS['secondary']}]")
61
-
62
- def validate_option_letter(letter: str, options: dict) -> bool:
63
- """Validate if the given letter is a valid option or 'M' for modify"""
64
- return letter.upper() in options or letter.upper() == 'M'
65
-
66
- def get_option_selection() -> str:
67
- """Get user input for option selection with modify option"""
68
- console = Console()
69
- console.print("\n[cyan]Enter option letter or 'M' to modify request[/cyan]")
70
- while True:
71
- letter = prompt_user("Select option").strip().upper()
72
- if letter == 'M' or (letter.isalpha() and len(letter) == 1):
73
- return letter
74
- console.print("[red]Please enter a valid letter or 'M'[/red]")
75
-
76
- def get_changes_history_path(workdir: Path) -> Path:
77
- """Create and return the changes history directory path"""
78
- changes_history_dir = workdir / '.janito' / 'changes_history'
79
- changes_history_dir.mkdir(parents=True, exist_ok=True)
80
- return changes_history_dir
81
-
82
- def get_timestamp() -> str:
83
- """Get current UTC timestamp in YMD_HMS format with leading zeros"""
84
- return datetime.now(timezone.utc).strftime('%Y%m%d_%H%M%S')
85
-
86
- def save_prompt_to_file(prompt: str) -> Path:
87
- """Save prompt to a named temporary file that won't be deleted"""
88
- temp_file = tempfile.NamedTemporaryFile(prefix='selected_', suffix='.txt', delete=False)
89
- temp_path = Path(temp_file.name)
90
- temp_path.write_text(prompt)
91
- return temp_path
92
-
93
- def save_to_file(content: str, prefix: str, workdir: Path) -> Path:
94
- """Save content to a timestamped file in changes history directory"""
95
- changes_history_dir = get_changes_history_path(workdir)
96
- timestamp = get_timestamp()
97
- filename = f"{timestamp}_{prefix}.txt"
98
- file_path = changes_history_dir / filename
99
- file_path.write_text(content)
100
- return file_path
101
-
102
- def modify_request(request: str) -> str:
103
- """Display current request and get modified version with improved formatting"""
104
- console = Console()
105
-
106
- # Display current request in a panel with clear formatting
107
- console.print("\n[bold cyan]Current Request:[/bold cyan]")
108
- console.print(Panel(
109
- Text(request, style="white"),
110
- border_style="blue",
111
- title="Previous Request",
112
- padding=(1, 2)
113
- ))
114
-
115
- # Get modified request with clear prompt
116
- console.print("\n[bold cyan]Enter modified request below:[/bold cyan]")
117
- console.print("[dim](Press Enter to submit, Ctrl+C to cancel)[/dim]")
118
- try:
119
- new_request = prompt_user("Modified request")
120
- if not new_request.strip():
121
- console.print("[yellow]No changes made, keeping original request[/yellow]")
122
- return request
123
- return new_request
124
- except KeyboardInterrupt:
125
- console.print("\n[yellow]Modification cancelled, keeping original request[/yellow]")
126
- return request
127
-
128
- def format_option_text(option: AnalysisOption) -> str:
129
- """Format an AnalysisOption into a string representation"""
130
- option_text = f"Option {option.letter}:\n"
131
- option_text += f"Summary: {option.summary}\n\n"
132
- option_text += "Description:\n"
133
- for item in option.description_items:
134
- option_text += f"- {item}\n"
135
- option_text += "\nAffected files:\n"
136
- for file in option.affected_files:
137
- option_text += f"- {file}\n"
138
- return option_text
139
-
140
- def handle_option_selection(claude: ClaudeAPIAgent, initial_response: str, request: str, raw: bool = False, workdir: Optional[Path] = None, include: Optional[List[Path]] = None) -> None:
141
- """Handle option selection and implementation details"""
142
- options = parse_analysis_options(initial_response)
143
- if not options:
144
- console = Console()
145
- console.print("[red]No valid options found in the response[/red]")
146
- return
147
-
148
- while True:
149
- option = get_option_selection()
150
-
151
- if option == 'M':
152
- # Use the new modify_request function for better UX
153
- new_request = modify_request(request)
154
- if new_request == request:
155
- continue
156
-
157
- # Rerun analysis with new request
158
- paths_to_scan = [workdir] if workdir else []
159
- if include:
160
- paths_to_scan.extend(include)
161
- files_content = collect_files_content(paths_to_scan, workdir) if paths_to_scan else ""
162
-
163
- initial_prompt = build_request_analysis_prompt(files_content, new_request)
164
- initial_response = progress_send_message(claude, initial_prompt)
165
- save_to_file(initial_response, 'analysis', workdir)
166
-
167
- format_analysis(initial_response, raw, claude)
168
- options = parse_analysis_options(initial_response)
169
- if not options:
170
- console = Console()
171
- console.print("[red]No valid options found in the response[/red]")
172
- return
173
- continue
174
-
175
- if not validate_option_letter(option, options):
176
- console = Console()
177
- console.print(f"[red]Invalid option '{option}'. Valid options are: {', '.join(options.keys())} or 'M' to modify[/red]")
178
- continue
179
-
180
- break
181
-
182
- paths_to_scan = [workdir] if workdir else []
183
- if include:
184
- paths_to_scan.extend(include)
185
- files_content = collect_files_content(paths_to_scan, workdir) if paths_to_scan else ""
186
-
187
- # Format the selected option before building prompt
188
- selected_option = options[option]
189
- option_text = format_option_text(selected_option)
190
-
191
- # Remove initial_response from the arguments
192
- selected_prompt = build_selected_option_prompt(option_text, request, files_content)
193
- prompt_file = save_to_file(selected_prompt, 'selected', workdir)
194
- if config.verbose:
195
- print(f"\nSelected prompt saved to: {prompt_file}")
196
-
197
- selected_response = progress_send_message(claude, selected_prompt)
198
- changes_file = save_to_file(selected_response, 'changes', workdir)
199
-
200
- if config.verbose:
201
- try:
202
- rel_path = changes_file.relative_to(workdir)
203
- print(f"\nChanges saved to: ./{rel_path}")
204
- except ValueError:
205
- print(f"\nChanges saved to: {changes_file}")
206
-
207
- changes = parse_block_changes(selected_response)
208
- preview_and_apply_changes(changes, workdir, config.test_cmd)
209
-
210
- def replay_saved_file(filepath: Path, claude: ClaudeAPIAgent, workdir: Path, raw: bool = False) -> None:
211
- """Process a saved prompt file and display the response"""
212
- if not filepath.exists():
213
- raise FileNotFoundError(f"File {filepath} not found")
214
-
215
- content = filepath.read_text()
216
-
217
- # Add debug output of file content
218
- if config.debug:
219
- console = Console()
220
- console.print("\n[bold blue]Debug: File Content[/bold blue]")
221
- console.print(Panel(
222
- content,
223
- title=f"Content of {filepath.name}",
224
- border_style="blue",
225
- padding=(1, 2)
226
- ))
227
- console.print()
5
+ from .version import get_version
228
6
 
229
- file_type = get_history_file_type(filepath)
230
-
231
- if file_type == 'changes':
232
- changes = parse_block_changes(content)
233
- success = preview_and_apply_changes(changes, workdir, config.test_cmd)
234
- if not success:
235
- raise typer.Exit(1)
236
- elif file_type == 'analysis':
237
- format_analysis(content, raw, claude)
238
- handle_option_selection(claude, content, content, raw, workdir)
239
- elif file_type == 'selected':
240
- if raw:
241
- console = Console()
242
- console.print("\n=== Prompt Content ===")
243
- console.print(content)
244
- console.print("=== End Prompt Content ===\n")
245
-
246
- response = progress_send_message(claude, content)
247
- changes_file = save_to_file(response, 'changes_', workdir)
248
- print(f"\nChanges saved to: {changes_file}")
249
-
250
- changes = parse_block_changes(response)
251
- preview_and_apply_changes(changes, workdir, config.test_cmd)
252
- else:
253
- response = progress_send_message(claude, content)
254
- format_analysis(response, raw)
7
+ from janito.agents import AgentSingleton
8
+ from janito.config import config
255
9
 
256
- def process_question(question: str, workdir: Path, include: List[Path], raw: bool, claude: ClaudeAPIAgent) -> None:
257
- """Process a question about the codebase"""
258
- paths_to_scan = [workdir] if workdir else []
259
- if include:
260
- paths_to_scan.extend(include)
261
- files_content = collect_files_content(paths_to_scan, workdir)
262
- answer = ask_question(question, files_content, claude)
263
- display_answer(answer, raw)
10
+ from .cli.commands import handle_request, handle_ask, handle_play, handle_scan
264
11
 
265
- def ensure_workdir(workdir: Path) -> Path:
266
- """Ensure working directory exists, prompt for creation if it doesn't"""
267
- if workdir.exists():
268
- return workdir
269
-
270
- console = Console()
271
- console.print(f"\n[yellow]Directory does not exist:[/yellow] {workdir}")
272
- if Confirm.ask("Create directory?"):
273
- workdir.mkdir(parents=True)
274
- console.print(f"[green]Created directory:[/green] {workdir}")
275
- return workdir
276
- raise typer.Exit(1)
12
+ app = typer.Typer(add_completion=False)
277
13
 
278
14
  def typer_main(
279
- request: Optional[str] = typer.Argument(None, help="The modification request"),
15
+ change_request: str = typer.Argument(None, help="Change request or command"),
16
+ workdir: Optional[Path] = typer.Option(None, "-w", "--workdir", help="Working directory", file_okay=False, dir_okay=True),
17
+ debug: bool = typer.Option(False, "--debug", help="Show debug information"),
18
+ verbose: bool = typer.Option(False, "--verbose", help="Show verbose output"),
19
+ include: Optional[List[Path]] = typer.Option(None, "-i", "--include", help="Additional paths to include"),
280
20
  ask: Optional[str] = typer.Option(None, "--ask", help="Ask a question about the codebase"),
281
- workdir: Optional[Path] = typer.Option(None, "-w", "--workdir",
282
- help="Working directory (defaults to current directory)",
283
- file_okay=False, dir_okay=True),
284
- raw: bool = typer.Option(False, "--raw", help="Print raw response instead of markdown format"),
285
21
  play: Optional[Path] = typer.Option(None, "--play", help="Replay a saved prompt file"),
286
- include: Optional[List[Path]] = typer.Option(None, "-i", "--include", help="Additional paths to include in analysis", exists=True),
287
- debug: bool = typer.Option(False, "--debug", help="Show debug information"),
288
- verbose: bool = typer.Option(False, "-v", "--verbose", help="Show verbose output"),
289
- scan: bool = typer.Option(False, "--scan", help="Preview files that would be analyzed"),
290
- version: bool = typer.Option(False, "--version", help="Show version and exit"),
291
- test: Optional[str] = typer.Option(None, "-t", "--test", help="Test command to run before applying changes"),
292
- ) -> None:
293
- """
294
- Analyze files and provide modification instructions.
295
- """
296
-
22
+ version: bool = typer.Option(False, "--version", help="Show version information"),
23
+ ):
24
+ """Janito - AI-powered code modification assistant"""
297
25
  if version:
298
26
  console = Console()
299
- console.print(f"Janito v{get_version()}")
300
- raise typer.Exit()
27
+ console.print(f"Janito version {get_version()}")
28
+ return
301
29
 
30
+ workdir = workdir or Path.cwd()
302
31
  config.set_debug(debug)
303
32
  config.set_verbose(verbose)
304
- config.set_test_cmd(test)
305
33
 
306
- claude = ClaudeAPIAgent(system_prompt=SYSTEM_PROMPT)
307
-
308
- if not any([request, ask, play, scan]):
309
- workdir = workdir or Path.cwd()
310
- workdir = ensure_workdir(workdir)
311
- from janito.console import start_console_session
312
- start_console_session(workdir, include)
313
- return
34
+ agent = AgentSingleton.get_agent()
314
35
 
315
- workdir = workdir or Path.cwd()
316
- workdir = ensure_workdir(workdir)
317
-
318
- if include:
319
- include = [
320
- path if path.is_absolute() else (workdir / path).resolve()
321
- for path in include
322
- ]
323
-
324
36
  if ask:
325
- process_question(ask, workdir, include, raw, claude)
326
- return
327
-
328
- if scan:
37
+ handle_ask(ask, workdir, include, False, agent)
38
+ elif play:
39
+ handle_play(play, workdir, False)
40
+ elif change_request == "scan":
329
41
  paths_to_scan = include if include else [workdir]
330
- preview_scan(paths_to_scan, workdir)
331
- return
332
-
333
- if play:
334
- replay_saved_file(play, claude, workdir, raw)
335
- return
336
-
337
- paths_to_scan = include if include else [workdir]
338
-
339
- is_empty = is_dir_empty(workdir)
340
- if is_empty and not include:
341
- console = Console()
342
- console.print("\n[bold blue]Empty directory - will create new files as needed[/bold blue]")
343
- files_content = ""
42
+ handle_scan(paths_to_scan, workdir)
43
+ elif change_request:
44
+ handle_request(change_request, workdir, include, False, agent)
344
45
  else:
345
- files_content = collect_files_content(paths_to_scan, workdir)
346
-
347
- initial_prompt = build_request_analysis_prompt(files_content, request)
348
- initial_response = progress_send_message(claude, initial_prompt)
349
- save_to_file(initial_response, 'analysis', workdir)
350
-
351
- format_analysis(initial_response, raw, claude)
352
-
353
- handle_option_selection(claude, initial_response, request, raw, workdir, include)
46
+ console = Console()
47
+ console.print("Error: Please provide a change request or use --ask/--play options")
48
+ raise typer.Exit(1)
354
49
 
355
50
  def main():
356
51
  typer.run(typer_main)
357
52
 
358
53
  if __name__ == "__main__":
359
- main()
54
+ main()
@@ -0,0 +1,22 @@
1
+ import os
2
+
3
+ SYSTEM_PROMPT = """I am Janito, your friendly software development buddy. I help you with coding tasks while being clear and concise in my responses."""
4
+
5
+ ai_backend = os.getenv('AI_BACKEND', 'claudeai').lower()
6
+
7
+ if ai_backend == 'openai':
8
+ from .openai import OpenAIAgent as AIAgent
9
+ elif ai_backend == 'claudeai':
10
+ from .claudeai import ClaudeAIAgent as AIAgent
11
+ else:
12
+ raise ValueError(f"Unsupported AI_BACKEND: {ai_backend}")
13
+
14
+ class AgentSingleton:
15
+ _instance = None
16
+
17
+ @classmethod
18
+ def get_agent(cls):
19
+ if cls._instance is None:
20
+ cls._instance = AIAgent(SYSTEM_PROMPT)
21
+ return cls._instance
22
+
janito/agents/agent.py ADDED
@@ -0,0 +1,21 @@
1
+
2
+ from abc import ABC, abstractmethod
3
+ from threading import Event
4
+ from typing import Optional, List, Tuple
5
+
6
+ class Agent(ABC):
7
+ """Abstract base class for AI agents"""
8
+ def __init__(self, api_key: Optional[str] = None, system_prompt: str = None):
9
+ self.api_key = api_key
10
+ self.system_message = system_prompt
11
+ self.last_prompt = None
12
+ self.last_full_message = None
13
+ self.last_response = None
14
+ self.messages_history: List[Tuple[str, str]] = []
15
+ if system_prompt:
16
+ self.messages_history.append(("system", system_prompt))
17
+
18
+ @abstractmethod
19
+ def send_message(self, message: str, stop_event: Event = None) -> str:
20
+ """Send message to AI service and return response"""
21
+ pass
@@ -2,17 +2,22 @@ import anthropic
2
2
  import os
3
3
  from typing import Optional
4
4
  from threading import Event
5
+ from .agent import Agent
5
6
 
6
- class ClaudeAPIAgent:
7
+ class ClaudeAIAgent(Agent):
7
8
  """Handles interaction with Claude API, including message handling"""
8
- def __init__(self, api_key: Optional[str] = None, system_prompt: str = None):
9
+ DEFAULT_MODEL = "claude-3-5-sonnet-20241022"
10
+
11
+ def __init__(self, system_prompt: str = None):
12
+ self.api_key = os.getenv('ANTHROPIC_API_KEY')
13
+ super().__init__(self.api_key, system_prompt)
9
14
  if not system_prompt:
10
15
  raise ValueError("system_prompt is required")
11
- self.api_key = api_key or os.getenv('ANTHROPIC_API_KEY')
16
+
12
17
  if not self.api_key:
13
18
  raise ValueError("ANTHROPIC_API_KEY environment variable is required")
14
19
  self.client = anthropic.Client(api_key=self.api_key)
15
- self.model = "claude-3-5-sonnet-20241022"
20
+ self.model = os.getenv('CLAUDE_MODEL', self.DEFAULT_MODEL)
16
21
  self.system_message = system_prompt
17
22
  self.last_prompt = None
18
23
  self.last_full_message = None
@@ -35,7 +40,7 @@ class ClaudeAPIAgent:
35
40
  response = self.client.messages.create(
36
41
  model=self.model, # Use discovered model
37
42
  system=self.system_message,
38
- max_tokens=4000,
43
+ max_tokens=8192,
39
44
  messages=[
40
45
  {"role": "user", "content": message}
41
46
  ],
@@ -0,0 +1,53 @@
1
+ import openai # updated import
2
+ import os
3
+ from typing import Optional
4
+ from threading import Event
5
+ from .agent import Agent
6
+
7
+ class OpenAIAgent(Agent):
8
+ """Handles interaction with OpenAI API, including message handling"""
9
+ DEFAULT_MODEL = "o1-mini-2024-09-12"
10
+
11
+ def __init__(self, api_key: Optional[str] = None, system_prompt: str = None):
12
+ super().__init__(api_key, system_prompt)
13
+ if not system_prompt:
14
+ raise ValueError("system_prompt is required")
15
+ self.api_key = api_key or os.getenv('OPENAI_API_KEY')
16
+ if not self.api_key:
17
+ raise ValueError("OPENAI_API_KEY environment variable is required")
18
+ openai.api_key = self.api_key
19
+ openai.organization = os.getenv("OPENAI_ORG")
20
+ self.client = openai.Client() # initialized client
21
+ self.model = os.getenv('OPENAI_MODEL', "o1-mini-2024-09-12") # reverted to original default model
22
+
23
+ def send_message(self, message: str, stop_event: Event = None) -> str:
24
+ """Send message to OpenAI API and return response"""
25
+ self.messages_history.append(("user", message))
26
+ self.last_full_message = message
27
+
28
+ try:
29
+ if stop_event and stop_event.is_set():
30
+ return ""
31
+
32
+ #messages = [{"role": "system", "content": self.system_message}]
33
+ messages = [{"role": "user", "content": message}]
34
+
35
+ response = self.client.chat.completions.create(
36
+ model=self.model,
37
+ messages=messages,
38
+ max_completion_tokens=4000,
39
+ temperature=1,
40
+ )
41
+
42
+ response_text = response.choices[0].message.content
43
+
44
+ if not (stop_event and stop_event.is_set()):
45
+ self.last_response = response_text
46
+ self.messages_history.append(("assistant", response_text))
47
+
48
+ return response_text
49
+
50
+ except KeyboardInterrupt:
51
+ if stop_event:
52
+ stop_event.set()
53
+ return ""
janito/agents/test.py ADDED
@@ -0,0 +1,34 @@
1
+ import unittest
2
+ import os
3
+ from unittest.mock import patch, MagicMock
4
+ from .openai import OpenAIAgent
5
+ from .claudeai import AIAgent
6
+
7
+ class TestAIAgents(unittest.TestCase):
8
+ def setUp(self):
9
+ self.system_prompt = "You are a helpful assistant."
10
+ self.test_message = "Hello, how are you?"
11
+
12
+ def test_openai_agent_initialization(self):
13
+ with patch.dict(os.environ, {'OPENAI_API_KEY': 'test_key'}):
14
+ agent = OpenAIAgent(system_prompt=self.system_prompt)
15
+
16
+ def test_claudeai_agent_initialization(self):
17
+ with patch.dict(os.environ, {'ANTHROPIC_API_KEY': 'test_key'}):
18
+ agent = AIAgent(system_prompt=self.system_prompt)
19
+
20
+ def test_openai_agent_send_message(self):
21
+ with patch('openai.OpenAI.chat.completions.create') as mock_create:
22
+ mock_response = MagicMock()
23
+ mock_response.choices[0].message.content = "I'm good, thank you!"
24
+ mock_create.return_value = mock_response
25
+ response = self.openai_agent.send_message(self.test_message)
26
+ self.assertEqual(response, "I'm good, thank you!")
27
+
28
+ def test_claudeai_agent_send_message(self):
29
+ with patch('anthropic.Client.messages.create') as mock_create:
30
+ mock_response = MagicMock()
31
+ mock_response.content[0].text = "I'm Claude, how can I assist you?"
32
+ mock_create.return_value = mock_response
33
+ response = self.claudeai_agent.send_message(self.test_message)
34
+ self.assertEqual(response, "I'm Claude, how can I assist you?")
@@ -0,0 +1,33 @@
1
+ """Analysis module for Janito.
2
+
3
+ This module provides functionality for analyzing and displaying code changes.
4
+ """
5
+
6
+ from .options import AnalysisOption, parse_analysis_options
7
+ from .display import (
8
+ format_analysis,
9
+ get_history_file_type,
10
+ get_history_path,
11
+ get_timestamp,
12
+ save_to_file
13
+ )
14
+ from .prompts import (
15
+ build_request_analysis_prompt,
16
+ get_option_selection,
17
+ prompt_user,
18
+ validate_option_letter
19
+ )
20
+
21
+ __all__ = [
22
+ 'AnalysisOption',
23
+ 'parse_analysis_options',
24
+ 'format_analysis',
25
+ 'get_history_file_type',
26
+ 'get_history_path',
27
+ 'get_timestamp',
28
+ 'save_to_file',
29
+ 'build_request_analysis_prompt',
30
+ 'get_option_selection',
31
+ 'prompt_user',
32
+ 'validate_option_letter'
33
+ ]