janito 0.7.0__py3-none-any.whl → 0.8.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 (112) hide show
  1. janito/__main__.py +127 -141
  2. janito/agents/__init__.py +22 -22
  3. janito/agents/agent.py +24 -27
  4. janito/agents/claudeai.py +41 -45
  5. janito/agents/deepseekai.py +47 -0
  6. janito/change/applied_blocks.py +34 -0
  7. janito/change/applier.py +167 -0
  8. janito/change/edit_blocks.py +148 -0
  9. janito/change/finder.py +72 -0
  10. janito/change/request.py +144 -0
  11. janito/change/validator.py +87 -269
  12. janito/change/view/content.py +63 -0
  13. janito/change/{viewer → view}/diff.py +44 -43
  14. janito/change/view/panels.py +201 -0
  15. janito/change/view/sections.py +69 -0
  16. janito/change/view/styling.py +140 -0
  17. janito/change/view/summary.py +37 -0
  18. janito/change/{viewer → view}/themes.py +62 -55
  19. janito/change/view/viewer.py +59 -0
  20. janito/cli/__init__.py +1 -1
  21. janito/cli/commands.py +68 -88
  22. janito/cli/functions.py +66 -111
  23. janito/common.py +132 -79
  24. janito/config.py +99 -101
  25. janito/data/change_prompt.txt +81 -0
  26. janito/data/system_prompt.txt +3 -0
  27. janito/qa.py +56 -57
  28. janito/version.py +22 -22
  29. janito/workspace/__init__.py +8 -6
  30. janito/workspace/analysis.py +120 -120
  31. janito/workspace/{types.py → models.py} +97 -98
  32. janito/workspace/show.py +115 -141
  33. janito/workspace/stats.py +42 -43
  34. janito/workspace/workset.py +135 -108
  35. janito/workspace/workspace.py +335 -114
  36. janito-0.8.0.dist-info/METADATA +106 -0
  37. janito-0.8.0.dist-info/RECORD +40 -0
  38. {janito-0.7.0.dist-info → janito-0.8.0.dist-info}/licenses/LICENSE +20 -20
  39. janito/__init__.py +0 -2
  40. janito/agents/openai.py +0 -57
  41. janito/agents/test.py +0 -34
  42. janito/change/__init__.py +0 -32
  43. janito/change/__main__.py +0 -0
  44. janito/change/analysis/__init__.py +0 -23
  45. janito/change/analysis/__main__.py +0 -7
  46. janito/change/analysis/analyze.py +0 -62
  47. janito/change/analysis/formatting.py +0 -78
  48. janito/change/analysis/options.py +0 -81
  49. janito/change/analysis/prompts.py +0 -90
  50. janito/change/analysis/view/__init__.py +0 -9
  51. janito/change/analysis/view/terminal.py +0 -181
  52. janito/change/applier/__init__.py +0 -5
  53. janito/change/applier/file.py +0 -58
  54. janito/change/applier/main.py +0 -156
  55. janito/change/applier/text.py +0 -247
  56. janito/change/applier/workspace_dir.py +0 -58
  57. janito/change/core.py +0 -124
  58. janito/change/history.py +0 -44
  59. janito/change/operations.py +0 -7
  60. janito/change/parser.py +0 -287
  61. janito/change/play.py +0 -54
  62. janito/change/preview.py +0 -82
  63. janito/change/prompts.py +0 -121
  64. janito/change/test.py +0 -0
  65. janito/change/viewer/__init__.py +0 -11
  66. janito/change/viewer/content.py +0 -66
  67. janito/change/viewer/panels.py +0 -533
  68. janito/change/viewer/styling.py +0 -114
  69. janito/clear_statement_parser/clear_statement_format.txt +0 -328
  70. janito/clear_statement_parser/examples.txt +0 -326
  71. janito/clear_statement_parser/models.py +0 -104
  72. janito/clear_statement_parser/parser.py +0 -496
  73. janito/cli/base.py +0 -30
  74. janito/cli/history.py +0 -61
  75. janito/cli/registry.py +0 -26
  76. janito/demo/__init__.py +0 -4
  77. janito/demo/data.py +0 -13
  78. janito/demo/mock_data.py +0 -20
  79. janito/demo/operations.py +0 -45
  80. janito/demo/runner.py +0 -59
  81. janito/demo/scenarios.py +0 -32
  82. janito/prompt.py +0 -36
  83. janito/review.py +0 -13
  84. janito/search_replace/README.md +0 -192
  85. janito/search_replace/__init__.py +0 -7
  86. janito/search_replace/__main__.py +0 -21
  87. janito/search_replace/core.py +0 -120
  88. janito/search_replace/logger.py +0 -35
  89. janito/search_replace/parser.py +0 -52
  90. janito/search_replace/play.py +0 -61
  91. janito/search_replace/replacer.py +0 -36
  92. janito/search_replace/searcher.py +0 -411
  93. janito/search_replace/strategy_result.py +0 -10
  94. janito/shell/__init__.py +0 -38
  95. janito/shell/bus.py +0 -31
  96. janito/shell/commands.py +0 -136
  97. janito/shell/history.py +0 -20
  98. janito/shell/processor.py +0 -32
  99. janito/shell/prompt.py +0 -48
  100. janito/shell/registry.py +0 -60
  101. janito/tui/__init__.py +0 -21
  102. janito/tui/base.py +0 -22
  103. janito/tui/flows/__init__.py +0 -5
  104. janito/tui/flows/changes.py +0 -65
  105. janito/tui/flows/content.py +0 -128
  106. janito/tui/flows/selection.py +0 -117
  107. janito/tui/screens/__init__.py +0 -3
  108. janito/tui/screens/app.py +0 -1
  109. janito-0.7.0.dist-info/METADATA +0 -167
  110. janito-0.7.0.dist-info/RECORD +0 -96
  111. {janito-0.7.0.dist-info → janito-0.8.0.dist-info}/WHEEL +0 -0
  112. {janito-0.7.0.dist-info → janito-0.8.0.dist-info}/entry_points.txt +0 -0
janito/__main__.py CHANGED
@@ -1,142 +1,128 @@
1
- import typer
2
- from typing import Optional, List, Set
3
- from pathlib import Path
4
- from rich.text import Text
5
- from rich import print as rich_print
6
- from rich.console import Console
7
- from rich.text import Text
8
- from .version import get_version
9
-
10
- from janito.config import config
11
- from janito.workspace import workset
12
- from janito.workspace.types import ScanType # Add this import
13
- from .cli.commands import (
14
- handle_request, handle_ask, handle_play,
15
- handle_scan, handle_demo
16
- )
17
-
18
- app = typer.Typer(pretty_exceptions_enable=False)
19
-
20
- def validate_paths(paths: Optional[List[Path]]) -> Optional[List[Path]]:
21
- """Validate include paths for duplicates.
22
-
23
- Args:
24
- paths: List of paths to validate, or None if no paths provided
25
-
26
- Returns:
27
- Validated list of paths or None if no paths provided
28
- """
29
- if not paths: # This handles both None and empty list cases
30
- return None
31
-
32
- # Convert paths to absolute and resolve symlinks
33
- resolved_paths: Set[Path] = set()
34
- unique_paths: List[Path] = []
35
-
36
- for path in paths:
37
- resolved = path.absolute().resolve()
38
- if resolved in resolved_paths:
39
- error_text = Text(f"\nError: Duplicate path provided: {path} ", style="red")
40
- rich_print(error_text)
41
- raise typer.Exit(1)
42
- resolved_paths.add(resolved)
43
- unique_paths.append(path)
44
-
45
- return unique_paths if unique_paths else None
46
-
47
- def typer_main(
48
- change_request: str = typer.Argument(None, help="Change request or command"),
49
- workspace_dir: Optional[Path] = typer.Option(None, "-w", "--workspace_dir", help="Working directory", file_okay=False, dir_okay=True),
50
- debug: bool = typer.Option(False, "--debug", help="Show debug information"),
51
- verbose: bool = typer.Option(False, "--verbose", help="Show verbose output"),
52
- include: Optional[List[Path]] = typer.Option(None, "-i", "--include", help="Additional paths to include"),
53
- ask: Optional[str] = typer.Option(None, "--ask", help="Ask a question about the codebase"),
54
- play: Optional[Path] = typer.Option(None, "--play", help="Replay a saved prompt file"),
55
- scan: bool = typer.Option(False, "--scan", help="Preview files that would be analyzed"),
56
- version: bool = typer.Option(False, "--version", help="Show version information"),
57
- test_cmd: Optional[str] = typer.Option(None, "--test", help="Command to run tests after changes"),
58
- auto_apply: bool = typer.Option(False, "--auto-apply", help="Apply changes without confirmation"),
59
- tui: bool = typer.Option(False, "--tui", help="Use terminal user interface"),
60
- history: bool = typer.Option(False, "--history", help="Display history of requests"),
61
- recursive: Optional[List[Path]] = typer.Option(None, "-r", "--recursive", help="Paths to scan recursively (directories only)"),
62
- demo: bool = typer.Option(False, "--demo", help="Run demo scenarios"),
63
- skip_work: bool = typer.Option(False, "--skip-work", help="Skip scanning workspace_dir when using include paths"),
64
- ):
65
- """Janito - AI-powered code modification assistant"""
66
- if version:
67
- console = Console()
68
- console.print(f"Janito version {get_version()}")
69
- return
70
-
71
- if demo:
72
- handle_demo()
73
- return
74
-
75
- if history:
76
- from janito.cli.history import display_history
77
- display_history()
78
- return
79
-
80
- # Configure workspace
81
- config.set_workspace_dir(workspace_dir)
82
- config.set_debug(debug)
83
- config.set_verbose(verbose)
84
- config.set_auto_apply(auto_apply)
85
- config.set_tui(tui)
86
-
87
- # Configure workset with scan paths
88
- if include:
89
- if config.debug:
90
- Console(stderr=True).print("[cyan]Debug: Processing include paths...[/cyan]")
91
- for path in include:
92
- full_path = config.workspace_dir / path
93
- if not full_path.resolve().is_relative_to(config.workspace_dir):
94
- error_text = Text(f"\nError: Path must be within workspace: {path}", style="red")
95
- rich_print(error_text)
96
- raise typer.Exit(1)
97
- workset.add_scan_path(path, ScanType.PLAIN)
98
-
99
- if recursive:
100
- if config.debug:
101
- Console(stderr=True).print("[cyan]Debug: Processing recursive paths...[/cyan]")
102
- for path in recursive:
103
- full_path = config.workspace_dir / path
104
- if not path.is_dir():
105
- error_text = Text(f"\nError: Recursive path must be a directory: {path} ", style="red")
106
- rich_print(error_text)
107
- raise typer.Exit(1)
108
- if not full_path.resolve().is_relative_to(config.workspace_dir):
109
- error_text = Text(f"\nError: Path must be within workspace: {path}", style="red")
110
- rich_print(error_text)
111
- raise typer.Exit(1)
112
- workset.add_scan_path(path, ScanType.RECURSIVE)
113
-
114
- # Validate skip_work usage
115
- if skip_work and not workset.paths:
116
- error_text = Text("\nError: --skip-work requires at least one include path (-i or -r)", style="red")
117
- rich_print(error_text)
118
- raise typer.Exit(1)
119
-
120
- if test_cmd:
121
- config.set_test_cmd(test_cmd)
122
-
123
- # Refresh workset content before handling commands
124
- workset.refresh()
125
-
126
- if ask:
127
- handle_ask(ask)
128
- elif play:
129
- handle_play(play)
130
- elif scan:
131
- handle_scan()
132
- elif change_request:
133
- handle_request(change_request)
134
- else:
135
- from janito.shell import start_shell
136
- start_shell()
137
-
138
- def main():
139
- typer.run(typer_main)
140
-
141
- if __name__ == "__main__":
1
+ import typer
2
+ from typing import Optional, List, Set
3
+ from pathlib import Path
4
+ from rich.text import Text
5
+ from rich import print as rich_print
6
+ from rich.console import Console
7
+ from .version import get_version
8
+
9
+ from janito.config import config
10
+ from janito.workspace import workset
11
+ from janito.workspace.models import ScanType # Add this import
12
+ from .cli.commands import (
13
+ handle_request, handle_ask,
14
+ handle_scan
15
+ )
16
+
17
+
18
+ app = typer.Typer(pretty_exceptions_enable=False)
19
+
20
+ # Initialize console for CLI output
21
+ console = Console()
22
+
23
+
24
+
25
+ def typer_main(
26
+ user_request: Optional[str] = typer.Argument(None, help="User request"),
27
+ workspace_dir: Optional[Path] = typer.Option(None, "-w", "--workspace_dir", help="Working directory", file_okay=False, dir_okay=True),
28
+ debug: bool = typer.Option(False, "--debug", help="Show debug information"),
29
+ verbose: bool = typer.Option(False, "--verbose", help="Show verbose output"),
30
+ include: Optional[List[Path]] = typer.Option(None, "-i", "--include", help="Additional paths to include"),
31
+ ask: bool = typer.Option(False, "--ask", help="Treat the request as a question about the codebase"),
32
+ play: Optional[Path] = typer.Option(None, "--play", help="Replay a saved prompt file"),
33
+ replay: bool = typer.Option(False, "--replay", help="Trigger the replay response flow"),
34
+ scan: bool = typer.Option(False, "--scan", help="Preview files that would be analyzed"),
35
+ version: bool = typer.Option(False, "--version", help="Show version information"),
36
+ test_cmd: Optional[str] = typer.Option(None, "--test", help="Command to run tests after changes"),
37
+ auto_apply: bool = typer.Option(False, "--auto-apply", help="Apply changes without confirmation"),
38
+ recursive: Optional[List[Path]] = typer.Option(None, "-r", "--recursive", help="Paths to scan recursively (directories only)"),
39
+ skip_work: bool = typer.Option(False, "-s", "--skip-work", help="Skip scanning workspace_dir when using include paths"),
40
+ ):
41
+ """Janito - AI-powered code modification assistant"""
42
+ if version:
43
+ console.print(f"Janito version {get_version()}")
44
+ return
45
+
46
+ # Check if workspace directory exists and handle creation
47
+ if workspace_dir and not workspace_dir.exists():
48
+ create = typer.confirm(f"\nWorkspace directory '{workspace_dir}' does not exist. Create it?")
49
+ if create:
50
+ try:
51
+ workspace_dir.mkdir(parents=True)
52
+ console.print(f"[green]Created workspace directory: {workspace_dir}[/green]")
53
+ except Exception as e:
54
+ error_text = Text(f"\nError: Failed to create workspace directory: {e}", style="red")
55
+ rich_print(error_text)
56
+ raise typer.Exit(1)
57
+ else:
58
+ error_text = Text("\nError: Workspace directory does not exist and was not created", style="red")
59
+ rich_print(error_text)
60
+ raise typer.Exit(1)
61
+
62
+ # Configure workspace
63
+ config.set_workspace_dir(workspace_dir)
64
+ config.set_debug(debug)
65
+ config.set_verbose(verbose)
66
+ config.set_auto_apply(auto_apply)
67
+
68
+ # Configure workset with scan paths
69
+ if include:
70
+ if config.debug:
71
+ Console(stderr=True).print("[cyan]Debug: Processing include paths...[/cyan]")
72
+ for path in include:
73
+ full_path = config.workspace_dir / path
74
+ if not full_path.resolve().is_relative_to(config.workspace_dir):
75
+ error_text = Text(f"\nError: Path must be within workspace: {path}", style="red")
76
+ rich_print(error_text)
77
+ raise typer.Exit(1)
78
+ workset.add_scan_path(path, ScanType.PLAIN)
79
+
80
+ if recursive:
81
+ if config.debug:
82
+ Console(stderr=True).print("[cyan]Debug: Processing recursive paths...[/cyan]")
83
+ for path in recursive:
84
+ full_path = config.workspace_dir / path
85
+ if not path.is_dir():
86
+ error_text = Text(f"\nError: Recursive path must be a directory: {path} ", style="red")
87
+ rich_print(error_text)
88
+ raise typer.Exit(1)
89
+ if not full_path.resolve().is_relative_to(config.workspace_dir):
90
+ error_text = Text(f"\nError: Path must be within workspace: {path}", style="red")
91
+ rich_print(error_text)
92
+ raise typer.Exit(1)
93
+ workset.add_scan_path(path, ScanType.RECURSIVE)
94
+
95
+ # Validate skip_work usage
96
+ if skip_work:
97
+ # Check if any include or recursive paths are provided
98
+ if not include and not recursive:
99
+ error_text = Text("\nError: --skip-work requires at least one include path (-i or -r)", style="red")
100
+ rich_print(error_text)
101
+ raise typer.Exit(1)
102
+ # Remove root path from workset when skip_work is enabled
103
+ workset._scan_paths = [p for p in workset._scan_paths if p.path != Path(".")]
104
+
105
+ if test_cmd:
106
+ config.set_test_cmd(test_cmd)
107
+
108
+ # Refresh workset content before handling commands
109
+ workset.refresh()
110
+
111
+ if ask:
112
+ if not user_request:
113
+ error_text = Text("\nError: No question provided. Please provide a question as the main argument when using --ask", style="red")
114
+ rich_print(error_text)
115
+ raise typer.Exit(1)
116
+ handle_ask(user_request)
117
+ elif play:
118
+ handle_play(play)
119
+ elif scan:
120
+ handle_scan()
121
+ else:
122
+ handle_request(user_request, replay=replay)
123
+
124
+ def main():
125
+ typer.run(typer_main)
126
+
127
+ if __name__ == "__main__":
142
128
  main()
janito/agents/__init__.py CHANGED
@@ -1,22 +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
- import warnings
9
- warnings.warn(
10
- "Using deprecated OpenAI backend. Please switch to Claude AI backend by removing AI_BACKEND=openai "
11
- "from your environment variables.",
12
- DeprecationWarning,
13
- stacklevel=2
14
- )
15
- from .openai import OpenAIAgent as AIAgent
16
- elif ai_backend == 'claudeai':
17
- from .claudeai import ClaudeAIAgent as AIAgent
18
- else:
19
- raise ValueError(f"Unsupported AI_BACKEND: {ai_backend}")
20
-
21
- # Create a singleton instance
22
- agent = AIAgent(SYSTEM_PROMPT)
1
+ import os
2
+
3
+ # Try to determine backend from available API keys if not explicitly set
4
+ ai_backend = os.getenv('AI_BACKEND', '').lower()
5
+
6
+ if not ai_backend:
7
+ if os.getenv('ANTHROPIC_API_KEY'):
8
+ ai_backend = 'claudeai'
9
+ elif os.getenv('DEEPSEEK_API_KEY'):
10
+ ai_backend = 'deepseekai'
11
+ else:
12
+ raise ValueError("No AI backend API keys found. Please set either ANTHROPIC_API_KEY or DEEPSEEK_API_KEY")
13
+
14
+ if ai_backend == "deepseekai":
15
+ from .deepseekai import DeepSeekAIAgent as AIAgent
16
+ elif ai_backend == 'claudeai':
17
+ from .claudeai import ClaudeAIAgent as AIAgent
18
+ else:
19
+ raise ValueError(f"Unsupported AI_BACKEND: {ai_backend}")
20
+
21
+ # Create a singleton instance
22
+ agent = AIAgent()
janito/agents/agent.py CHANGED
@@ -1,28 +1,25 @@
1
- from abc import ABC, abstractmethod
2
- from threading import Event
3
- from typing import Optional, List, Tuple
4
-
5
- class Agent(ABC):
6
- """Abstract base class for AI agents"""
7
- def __init__(self, api_key: Optional[str] = None, system_prompt: str = None):
8
- self.api_key = api_key
9
- self.system_message = system_prompt
10
- self.last_prompt = None
11
- self.last_full_message = None
12
- self.last_response = None
13
- self.messages_history: List[Tuple[str, str]] = []
14
- if system_prompt:
15
- self.messages_history.append(("system", system_prompt))
16
-
17
- @abstractmethod
18
- def send_message(self, message: str, system: str) -> str:
19
- """Send message to the AI agent
20
-
21
- Args:
22
- message: The message to send
23
- stop_event: Optional event to signal cancellation
24
-
25
- Returns:
26
- The response from the AI agent
27
- """
1
+ from abc import ABC, abstractmethod
2
+ from typing import Optional
3
+
4
+ class Agent(ABC):
5
+ """Abstract base class for AI agents"""
6
+ friendly_name = "Unknown"
7
+
8
+ def __init__(self, api_key: Optional[str] = None):
9
+ self.api_key = api_key
10
+ self.last_prompt = None
11
+ self.last_full_message = None
12
+ self.last_response = None
13
+
14
+ @abstractmethod
15
+ def send_message(self, message: str, system: str) -> str:
16
+ """Send message to the AI agent
17
+
18
+ Args:
19
+ message: The message to send
20
+ stop_event: Optional event to signal cancellation
21
+
22
+ Returns:
23
+ The response from the AI agent
24
+ """
28
25
  pass
janito/agents/claudeai.py CHANGED
@@ -1,45 +1,41 @@
1
- import anthropic
2
- import os
3
- from typing import Optional
4
- from threading import Event
5
- from .agent import Agent
6
-
7
- class ClaudeAIAgent(Agent):
8
- """Handles interaction with Claude API, including message handling"""
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)
14
- if not system_prompt:
15
- raise ValueError("system_prompt is required")
16
-
17
- if not self.api_key:
18
- raise ValueError("ANTHROPIC_API_KEY environment variable is required")
19
- self.client = anthropic.Client(api_key=self.api_key)
20
- self.model = os.getenv('CLAUDE_MODEL', self.DEFAULT_MODEL)
21
- self.system_message = system_prompt
22
- self.last_prompt = None
23
- self.last_full_message = None
24
- self.last_response = None
25
-
26
-
27
- def send_message(self, message: str, system_message: str = None) -> str:
28
- """Send message to Claude API and return response"""
29
- self.messages_history.append(("user", message))
30
- # Store the full message
31
- self.last_full_message = message
32
-
33
- response = self.client.messages.create(
34
- model=self.model, # Use discovered model
35
- system=system_message or self.system_message,
36
- max_tokens=8192,
37
- messages=[
38
- {"role": "user", "content": message}
39
- ],
40
- temperature=0,
41
- )
42
-
43
-
44
- # Always return the response, let caller handle cancellation
45
- return response
1
+ import anthropic
2
+ import os
3
+
4
+ from .agent import Agent
5
+
6
+ class ClaudeAIAgent(Agent):
7
+ """Handles interaction with Claude API, including message handling"""
8
+ DEFAULT_MODEL = "claude-3-5-sonnet-20241022"
9
+ friendly_name = "Claude"
10
+
11
+ def __init__(self):
12
+ self.api_key = os.getenv('ANTHROPIC_API_KEY')
13
+ super().__init__(self.api_key)
14
+
15
+ if not self.api_key:
16
+ raise ValueError("ANTHROPIC_API_KEY environment variable is required")
17
+ self.client = anthropic.Client(api_key=self.api_key)
18
+ self.model = os.getenv('CLAUDE_MODEL', self.DEFAULT_MODEL)
19
+ self.last_prompt = None
20
+ self.last_full_message = None
21
+ self.last_response = None
22
+
23
+
24
+ def send_message(self, system_message: str, message: str) -> str:
25
+ """Send message to Claude API and return response"""
26
+ # Store the full message
27
+ self.last_full_message = message
28
+
29
+ response = self.client.messages.create(
30
+ model=self.model, # Use discovered model
31
+ system=system_message or self.system_message,
32
+ max_tokens=8192,
33
+ messages=[
34
+ {"role": "user", "content": message}
35
+ ],
36
+ temperature=0,
37
+ )
38
+
39
+
40
+ # Always return the response, let caller handle cancellation
41
+ return response
@@ -0,0 +1,47 @@
1
+ from openai import OpenAI
2
+ import os
3
+ from typing import Optional
4
+ from threading import Event
5
+ from .agent import Agent
6
+
7
+ class DeepSeekAIAgent(Agent):
8
+ """ DeepSeek AI Agent """
9
+ DEFAULT_MODEL = "deepseek-chat"
10
+ friendly_name = "DeepSeek"
11
+ api_key = None
12
+
13
+ def __init__(self, system_prompt: str = None):
14
+ self.api_key = os.getenv('DEEPSEEK_API_KEY')
15
+ super().__init__(self.api_key, system_prompt)
16
+ if not system_prompt:
17
+ raise ValueError("system_prompt is required")
18
+ if not self.api_key:
19
+ raise ValueError("DEEPSEEK_API_KEY environment variable is required")
20
+ self.client = OpenAI(api_key=self.api_key, base_url="https://api.deepseek.com")
21
+ self.model = self.DEFAULT_MODEL
22
+ self.system_message = system_prompt
23
+
24
+ def send_message(self, message: str, system_message: str = None) -> str:
25
+ """Send message to OpenAI API and return response"""
26
+ self.last_full_message = message
27
+
28
+ try:
29
+ messages = [
30
+ { "role": "system", "content": system_message},
31
+ { "role": "user", "content": message}
32
+ ]
33
+
34
+ response = self.client.chat.completions.create(
35
+ model=self.model,
36
+ messages=messages,
37
+ max_completion_tokens=4096,
38
+ temperature=0,
39
+ )
40
+
41
+ response_text = response.choices[0].message.content
42
+ self.last_response = response_text
43
+
44
+ return response
45
+
46
+ except KeyboardInterrupt:
47
+ return ""
@@ -0,0 +1,34 @@
1
+ from dataclasses import dataclass
2
+ from pathlib import Path
3
+ from typing import List, Optional
4
+ from .edit_blocks import EditType
5
+
6
+ @dataclass
7
+ class AppliedBlock:
8
+ filename: Path
9
+ edit_type: EditType
10
+ reason: str
11
+ original_content: List[str]
12
+ modified_content: List[str]
13
+ range_start: int
14
+ range_end: int
15
+ block_marker: Optional[str] = None
16
+ error_message: Optional[str] = None
17
+ has_error: bool = False
18
+
19
+ @dataclass
20
+ class AppliedBlocks:
21
+ blocks: List[AppliedBlock]
22
+
23
+ def get_changes_summary(self):
24
+ """Get summary info for all applied blocks"""
25
+ return [{
26
+ 'file': block.filename,
27
+ 'type': block.edit_type.name,
28
+ 'reason': block.reason,
29
+ 'lines_original': len(block.original_content),
30
+ 'lines_modified': len(block.modified_content),
31
+ 'range_start': block.range_start,
32
+ 'range_end': block.range_end,
33
+ 'block_marker': block.block_marker
34
+ } for block in self.blocks]