cognify-code 0.2.5__py3-none-any.whl → 0.2.6__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.
@@ -808,339 +808,6 @@ Provide a helpful, concise response.
808
808
  response = AgentResponse(message=full_response, intent=intent)
809
809
  yield ("", response)
810
810
 
811
-
812
-
813
- def process_stream(self, message: str, use_llm_classification: bool = True) -> Iterator[Tuple[str, Optional[AgentResponse]]]:
814
- """
815
- Process a user message with streaming output.
816
-
817
- Yields tuples of (chunk, final_response).
818
- During streaming, final_response is None.
819
- The last yield will have the complete AgentResponse.
820
- """
821
- # Classify intent (non-streaming, it's fast)
822
- if use_llm_classification:
823
- intent = self.intent_classifier.classify_with_llm(message)
824
- else:
825
- intent = self.intent_classifier.classify(message)
826
-
827
- # Route to appropriate streaming handler
828
- streaming_handlers = {
829
- IntentType.CODE_GENERATE: self._handle_generate_stream,
830
- IntentType.CODE_EDIT: self._handle_edit_stream,
831
- IntentType.CODE_REVIEW: self._handle_review_stream,
832
- IntentType.CODE_EXPLAIN: self._handle_explain_stream,
833
- IntentType.CODE_REFACTOR: self._handle_refactor_stream,
834
- IntentType.GENERAL_CHAT: self._handle_general_chat_stream,
835
- }
836
-
837
- handler = streaming_handlers.get(intent.type)
838
-
839
- if handler:
840
- yield from handler(message, intent)
841
- else:
842
- # Fall back to non-streaming for other intents
843
- response = self.process(message, use_llm_classification)
844
- yield (response.message, response)
845
-
846
- def _handle_explain_stream(self, message: str, intent: Intent) -> Iterator[Tuple[str, Optional[AgentResponse]]]:
847
- """Handle code explanation with streaming."""
848
- if not intent.file_paths:
849
- response = AgentResponse(
850
- message="Please specify which file or code you want me to explain.",
851
- intent=intent,
852
- )
853
- yield (response.message, response)
854
- return
855
-
856
- file_path = intent.file_paths[0]
857
- content = self.file_manager.read_file(file_path)
858
-
859
- if not content:
860
- response = AgentResponse(
861
- message=f"Cannot find file: {file_path}",
862
- intent=intent,
863
- )
864
- yield (response.message, response)
865
- return
866
-
867
- prompt = f"""Explain the following code in a clear, educational way.
868
-
869
- ## Code ({file_path})
870
- ```
871
- {content[:5000]}
872
- ```
873
-
874
- ## Instructions
875
- 1. Start with a high-level overview
876
- 2. Explain the main components/functions
877
- 3. Describe the flow of execution
878
- 4. Note any important patterns or techniques used
879
- 5. Keep the explanation concise but thorough
880
- """
881
-
882
- # Stream the explanation
883
- full_response = f"📖 **Explanation of {file_path}**\n\n"
884
- yield (f"📖 **Explanation of {file_path}**\n\n", None)
885
-
886
- for chunk in self.llm.stream(prompt):
887
- full_response += chunk
888
- yield (chunk, None)
889
-
890
- # Final response
891
- response = AgentResponse(
892
- message=full_response,
893
- intent=intent,
894
- )
895
- yield ("", response)
896
-
897
- def _handle_review_stream(self, message: str, intent: Intent) -> Iterator[Tuple[str, Optional[AgentResponse]]]:
898
- """Handle code review with streaming."""
899
- if not intent.file_paths:
900
- context = self.file_manager.get_project_context()
901
- py_files = [f.relative_path for f in context.files if f.extension == ".py"][:5]
902
-
903
- if py_files:
904
- msg = f"Which file would you like me to review? Found these Python files:\n" + \
905
- "\n".join(f" • {f}" for f in py_files)
906
- else:
907
- msg = "Please specify which file you want me to review."
908
-
909
- response = AgentResponse(message=msg, intent=intent)
910
- yield (msg, response)
911
- return
912
-
913
- file_path = intent.file_paths[0]
914
- content = self.file_manager.read_file(file_path)
915
-
916
- if not content:
917
- response = AgentResponse(
918
- message=f"Cannot find file: {file_path}",
919
- intent=intent,
920
- )
921
- yield (response.message, response)
922
- return
923
-
924
- yield (f"🔍 **Reviewing {file_path}...**\n\n", None)
925
-
926
- # Use streaming for the review
927
- prompt = f"""Review the following code for issues, bugs, and improvements.
928
-
929
- ## Code ({file_path})
930
- ```
931
- {content[:5000]}
932
- ```
933
-
934
- ## Review Format
935
- Provide a structured review with:
936
- 1. **Summary** - Brief overview
937
- 2. **Issues** - List any bugs, security issues, or problems
938
- 3. **Suggestions** - Improvements and best practices
939
- 4. **Score** - Rate the code quality (1-10)
940
-
941
- Be specific and actionable.
942
- """
943
-
944
- full_response = f"🔍 **Reviewing {file_path}...**\n\n"
945
-
946
- for chunk in self.llm.stream(prompt):
947
- full_response += chunk
948
- yield (chunk, None)
949
-
950
- response = AgentResponse(
951
- message=full_response,
952
- intent=intent,
953
- )
954
- yield ("", response)
955
-
956
- def _handle_generate_stream(self, message: str, intent: Intent) -> Iterator[Tuple[str, Optional[AgentResponse]]]:
957
- """Handle code generation with streaming."""
958
- yield ("🔨 **Generating code...**\n\n", None)
959
-
960
- request = CodeGenerationRequest(
961
- description=message,
962
- language=intent.language,
963
- file_path=intent.file_paths[0] if intent.file_paths else None,
964
- )
965
-
966
- # Generate code (this part streams)
967
- full_code = ""
968
- prompt = self.code_generator._build_prompt(request)
969
-
970
- for chunk in self.llm.stream(prompt):
971
- full_code += chunk
972
- yield (chunk, None)
973
-
974
- # Extract and create changeset
975
- code = self._extract_code(full_code)
976
- file_path = request.file_path or f"generated.{request.language or 'py'}"
977
-
978
- generated = GeneratedCode(
979
- code=code,
980
- language=request.language or "python",
981
- file_path=file_path,
982
- description=request.description,
983
- )
984
-
985
- changeset = ChangeSet(description=f"Generate: {message[:50]}...")
986
- diff = self.diff_engine.create_file_diff(generated.file_path, generated.code)
987
- changeset.diffs.append(diff)
988
-
989
- self._pending_changeset = changeset
990
-
991
- response = AgentResponse(
992
- message=f"\n\n✅ Code generated for {file_path}",
993
- intent=intent,
994
- generated_code=generated,
995
- changeset=changeset,
996
- requires_confirmation=True,
997
- )
998
- yield ("\n\n✅ Code generated. Apply changes? (yes/no)", response)
999
-
1000
- def _handle_edit_stream(self, message: str, intent: Intent) -> Iterator[Tuple[str, Optional[AgentResponse]]]:
1001
- """Handle code editing with streaming."""
1002
- if not intent.file_paths:
1003
- response = AgentResponse(
1004
- message="Please specify which file you want to edit.",
1005
- intent=intent,
1006
- )
1007
- yield (response.message, response)
1008
- return
1009
-
1010
- file_path = intent.file_paths[0]
1011
- original = self.file_manager.read_file(file_path)
1012
-
1013
- if not original:
1014
- response = AgentResponse(
1015
- message=f"Cannot find file: {file_path}",
1016
- intent=intent,
1017
- )
1018
- yield (response.message, response)
1019
- return
1020
-
1021
- yield (f"✏️ **Editing {file_path}...**\n\n", None)
1022
-
1023
- prompt = f"""Edit the following code according to the user's request.
1024
-
1025
- ## Original Code ({file_path})
1026
- ```
1027
- {original[:5000]}
1028
- ```
1029
-
1030
- ## User Request
1031
- {message}
1032
-
1033
- ## Instructions
1034
- Return the COMPLETE modified file.
1035
-
1036
- ```
1037
- """
1038
-
1039
- full_response = ""
1040
- for chunk in self.llm.stream(prompt):
1041
- full_response += chunk
1042
- yield (chunk, None)
1043
-
1044
- new_code = self._extract_code(full_response)
1045
-
1046
- changeset = ChangeSet(description=f"Edit: {message[:50]}...")
1047
- diff = self.diff_engine.create_diff(original, new_code, file_path)
1048
- changeset.diffs.append(diff)
1049
-
1050
- self._pending_changeset = changeset
1051
-
1052
- response = AgentResponse(
1053
- message=f"\n\n✅ Edit complete for {file_path}",
1054
- intent=intent,
1055
- changeset=changeset,
1056
- requires_confirmation=True,
1057
- )
1058
- yield ("\n\n✅ Edit complete. Apply changes? (yes/no)", response)
1059
-
1060
- def _handle_refactor_stream(self, message: str, intent: Intent) -> Iterator[Tuple[str, Optional[AgentResponse]]]:
1061
- """Handle code refactoring with streaming."""
1062
- if not intent.file_paths:
1063
- response = AgentResponse(
1064
- message="Please specify which file you want to refactor.",
1065
- intent=intent,
1066
- )
1067
- yield (response.message, response)
1068
- return
1069
-
1070
- file_path = intent.file_paths[0]
1071
- original = self.file_manager.read_file(file_path)
1072
-
1073
- if not original:
1074
- response = AgentResponse(
1075
- message=f"Cannot find file: {file_path}",
1076
- intent=intent,
1077
- )
1078
- yield (response.message, response)
1079
- return
1080
-
1081
- yield (f"🔄 **Refactoring {file_path}...**\n\n", None)
1082
-
1083
- prompt = f"""Refactor the following code to improve its quality.
1084
-
1085
- ## Original Code ({file_path})
1086
- ```
1087
- {original[:5000]}
1088
- ```
1089
-
1090
- ## User Request
1091
- {message}
1092
-
1093
- Return the COMPLETE refactored file.
1094
-
1095
- ```
1096
- """
1097
-
1098
- full_response = ""
1099
- for chunk in self.llm.stream(prompt):
1100
- full_response += chunk
1101
- yield (chunk, None)
1102
-
1103
- new_code = self._extract_code(full_response)
1104
-
1105
- changeset = ChangeSet(description=f"Refactor: {file_path}")
1106
- diff = self.diff_engine.create_diff(original, new_code, file_path)
1107
- changeset.diffs.append(diff)
1108
-
1109
- self._pending_changeset = changeset
1110
-
1111
- response = AgentResponse(
1112
- message=f"\n\n✅ Refactoring complete for {file_path}",
1113
- intent=intent,
1114
- changeset=changeset,
1115
- requires_confirmation=True,
1116
- )
1117
- yield ("\n\n✅ Refactoring complete. Apply changes? (yes/no)", response)
1118
-
1119
- def _handle_general_chat_stream(self, message: str, intent: Intent) -> Iterator[Tuple[str, Optional[AgentResponse]]]:
1120
- """Handle general chat with streaming."""
1121
- context = self.file_manager.get_project_context()
1122
-
1123
- prompt = f"""You are a helpful coding assistant. Answer the user's question.
1124
-
1125
- Project context:
1126
- - Root: {context.root_path.name}
1127
- - Languages: {', '.join(context.languages)}
1128
- - Files: {context.total_code_files} code files
1129
-
1130
- User: {message}
1131
-
1132
- Provide a helpful, concise response.
1133
- """
1134
-
1135
- full_response = ""
1136
- for chunk in self.llm.stream(prompt):
1137
- full_response += chunk
1138
- yield (chunk, None)
1139
-
1140
- response = AgentResponse(message=full_response, intent=intent)
1141
- yield ("", response)
1142
-
1143
-
1144
811
  def _extract_code(self, response: str) -> str:
1145
812
  """Extract code from LLM response."""
1146
813
  import re
ai_code_assistant/cli.py CHANGED
@@ -66,6 +66,59 @@ def get_components(config_path: Optional[Path] = None):
66
66
  return config, llm
67
67
 
68
68
 
69
+ def validate_llm_connection(llm: LLMManager, exit_on_failure: bool = True) -> bool:
70
+ """
71
+ Validate LLM connection before operations.
72
+
73
+ Args:
74
+ llm: The LLM manager instance
75
+ exit_on_failure: If True, exit with error code on failure
76
+
77
+ Returns:
78
+ True if connection is valid, False otherwise
79
+ """
80
+ try:
81
+ info = llm.get_model_info()
82
+ provider = info.get("provider", "ollama")
83
+
84
+ # Quick validation - don't do full connection check for cloud providers with API keys
85
+ if provider != "ollama" and llm.config.llm.api_key:
86
+ return True
87
+
88
+ # For Ollama, check connection
89
+ if provider == "ollama":
90
+ # Try a lightweight check first
91
+ import socket
92
+ base_url = info.get("base_url", "http://localhost:11434")
93
+ host = base_url.replace("http://", "").replace("https://", "").split(":")[0]
94
+ port = int(base_url.split(":")[-1].split("/")[0]) if ":" in base_url else 11434
95
+
96
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
97
+ sock.settimeout(2)
98
+ result = sock.connect_ex((host, port))
99
+ sock.close()
100
+
101
+ if result != 0:
102
+ console.print(f"\n[red]Error: Cannot connect to Ollama at {base_url}[/red]")
103
+ console.print("\n[yellow]Quick fix:[/yellow]")
104
+ console.print(" 1. Make sure Ollama is installed: [cyan]https://ollama.ai[/cyan]")
105
+ console.print(f" 2. Pull the model: [cyan]ollama pull {info.get('model', 'deepseek-coder:6.7b')}[/cyan]")
106
+ console.print(" 3. Start Ollama: [cyan]ollama serve[/cyan]")
107
+ console.print("\n[dim]Or use a cloud provider: cognify status --help[/dim]")
108
+
109
+ if exit_on_failure:
110
+ sys.exit(1)
111
+ return False
112
+
113
+ return True
114
+
115
+ except Exception as e:
116
+ console.print(f"\n[red]Error validating LLM connection: {e}[/red]")
117
+ if exit_on_failure:
118
+ sys.exit(1)
119
+ return False
120
+
121
+
69
122
  @click.group(invoke_without_command=True)
70
123
  @click.version_option(version=__version__, prog_name="cognify")
71
124
  @click.option("--config", "-c", type=click.Path(exists=True, path_type=Path), help="Config file path")
@@ -104,6 +157,7 @@ def review(ctx, files: Tuple[Path, ...], review_type: str, output_format: str,
104
157
  sys.exit(1)
105
158
 
106
159
  config, llm = get_components(ctx.obj.get("config_path"))
160
+ validate_llm_connection(llm)
107
161
  analyzer = CodeAnalyzer(config, llm)
108
162
  file_handler = FileHandler(config)
109
163
  formatter = get_formatter(output_format, config.output.use_colors)
@@ -178,6 +232,7 @@ def generate(ctx, description: str, mode: str, language: str, name: Optional[str
178
232
  from rich.panel import Panel
179
233
 
180
234
  config, llm = get_components(ctx.obj.get("config_path"))
235
+ validate_llm_connection(llm)
181
236
  generator = CodeGenerator(config, llm)
182
237
  formatter = get_formatter(output_format, config.output.use_colors)
183
238
 
@@ -267,6 +322,7 @@ def generate(ctx, description: str, mode: str, language: str, name: Optional[str
267
322
  def chat(ctx, context: Tuple[Path, ...], stream: bool):
268
323
  """Start an interactive chat session about code."""
269
324
  config, llm = get_components(ctx.obj.get("config_path"))
325
+ validate_llm_connection(llm)
270
326
  session = ChatSession(config, llm)
271
327
 
272
328
  # Load context files
@@ -558,6 +614,7 @@ def edit(ctx, file: Path, instruction: str, mode: str, preview: bool,
558
614
  ai-assist edit config.py "Update the timeout value" -s 10 -e 20
559
615
  """
560
616
  config, llm = get_components(ctx.obj.get("config_path"))
617
+ validate_llm_connection(llm)
561
618
  editor = FileEditor(config, llm)
562
619
 
563
620
  # Determine edit mode
@@ -647,6 +704,7 @@ def refactor(ctx, instruction: str, files: Tuple[Path, ...], pattern: Optional[s
647
704
  from ai_code_assistant.utils import FileHandler
648
705
 
649
706
  config, llm = get_components(ctx.obj.get("config_path"))
707
+ validate_llm_connection(llm)
650
708
  editor = MultiFileEditor(config, llm)
651
709
  file_handler = FileHandler(config)
652
710
 
@@ -788,6 +846,7 @@ def rename(ctx, old_name: str, new_name: str, symbol_type: str, files: Tuple[Pat
788
846
  from ai_code_assistant.utils import FileHandler
789
847
 
790
848
  config, llm = get_components(ctx.obj.get("config_path"))
849
+ validate_llm_connection(llm)
791
850
  editor = MultiFileEditor(config, llm)
792
851
  file_handler = FileHandler(config)
793
852
 
@@ -1182,6 +1241,7 @@ def git_commit(ctx, message: Optional[str], stage_all: bool, push_after: bool, n
1182
1241
  from ai_code_assistant.git import GitManager, CommitMessageGenerator
1183
1242
 
1184
1243
  config, llm = get_components(ctx.obj.get("config_path"))
1244
+ validate_llm_connection(llm)
1185
1245
 
1186
1246
  try:
1187
1247
  git_mgr = GitManager()
@@ -1315,6 +1375,7 @@ def git_sync(ctx, message: Optional[str], no_confirm: bool):
1315
1375
  from ai_code_assistant.git import GitManager, CommitMessageGenerator
1316
1376
 
1317
1377
  config, llm = get_components(ctx.obj.get("config_path"))
1378
+ validate_llm_connection(llm)
1318
1379
 
1319
1380
  try:
1320
1381
  git_mgr = GitManager()
@@ -1451,9 +1512,10 @@ def agent(ctx, path: Path):
1451
1512
  from ai_code_assistant.chat import AgentChatSession
1452
1513
  from rich.markdown import Markdown
1453
1514
  from rich.prompt import Prompt
1454
-
1515
+
1455
1516
  config, llm = get_components(ctx.obj.get("config_path"))
1456
-
1517
+ validate_llm_connection(llm)
1518
+
1457
1519
  # Initialize agent session
1458
1520
  session = AgentChatSession(config, llm, path.resolve())
1459
1521
 
@@ -1591,10 +1653,11 @@ def agent_review(ctx, file: Path, path: Path, stream: bool):
1591
1653
  ai-assist agent-review main.py --no-stream
1592
1654
  """
1593
1655
  from ai_code_assistant.agent import CodeAgent
1594
-
1656
+
1595
1657
  config, llm = get_components(ctx.obj.get("config_path"))
1658
+ validate_llm_connection(llm)
1596
1659
  agent = CodeAgent(llm, path.resolve())
1597
-
1660
+
1598
1661
  console.print(f"\n[bold]Reviewing {file}...[/bold]\n")
1599
1662
 
1600
1663
  if stream:
@@ -1628,10 +1691,11 @@ def agent_generate(ctx, description: str, file: Optional[Path], language: Option
1628
1691
  ai-assist agent-generate "hello world" --no-stream
1629
1692
  """
1630
1693
  from ai_code_assistant.agent import CodeAgent
1631
-
1694
+
1632
1695
  config, llm = get_components(ctx.obj.get("config_path"))
1696
+ validate_llm_connection(llm)
1633
1697
  agent = CodeAgent(llm, path.resolve())
1634
-
1698
+
1635
1699
  # Build the request
1636
1700
  request = description
1637
1701
  if file:
@@ -1681,10 +1745,11 @@ def agent_explain(ctx, file: Path, path: Path, stream: bool):
1681
1745
  ai-assist agent-explain main.py --no-stream
1682
1746
  """
1683
1747
  from ai_code_assistant.agent import CodeAgent
1684
-
1748
+
1685
1749
  config, llm = get_components(ctx.obj.get("config_path"))
1750
+ validate_llm_connection(llm)
1686
1751
  agent = CodeAgent(llm, path.resolve())
1687
-
1752
+
1688
1753
  console.print(f"\n[bold]Explaining {file}...[/bold]\n")
1689
1754
 
1690
1755
  if stream:
@@ -3,10 +3,20 @@
3
3
  from abc import ABC, abstractmethod
4
4
  from enum import Enum
5
5
  from typing import Any, Dict, Iterator, List, Optional
6
+ import logging
6
7
 
7
8
  from langchain_core.language_models import BaseChatModel
8
9
  from langchain_core.messages import BaseMessage, HumanMessage, SystemMessage
9
10
  from pydantic import BaseModel
11
+ from tenacity import (
12
+ retry,
13
+ stop_after_attempt,
14
+ wait_exponential,
15
+ retry_if_exception_type,
16
+ before_sleep_log,
17
+ )
18
+
19
+ logger = logging.getLogger(__name__)
10
20
 
11
21
 
12
22
  class ProviderType(str, Enum):
@@ -39,6 +49,21 @@ class ModelInfo(BaseModel):
39
49
  is_free: bool = True
40
50
 
41
51
 
52
+ def _is_retryable_error(exception: Exception) -> bool:
53
+ """Check if an exception should trigger a retry."""
54
+ error_msg = str(exception).lower()
55
+ # Rate limits, temporary server issues, timeout errors
56
+ retryable_keywords = [
57
+ "rate limit", "rate_limit", "429",
58
+ "503", "502", "504",
59
+ "timeout", "timed out",
60
+ "temporarily unavailable",
61
+ "server error", "internal error",
62
+ "connection", "network",
63
+ ]
64
+ return any(keyword in error_msg for keyword in retryable_keywords)
65
+
66
+
42
67
  class BaseProvider(ABC):
43
68
  """Abstract base class for LLM providers."""
44
69
 
@@ -52,6 +77,11 @@ class BaseProvider(ABC):
52
77
  # Available models for this provider
53
78
  available_models: List[ModelInfo] = []
54
79
 
80
+ # Retry settings for cloud providers
81
+ _retry_attempts: int = 3
82
+ _retry_min_wait: int = 2
83
+ _retry_max_wait: int = 30
84
+
55
85
  def __init__(self, config: ProviderConfig):
56
86
  """Initialize the provider with configuration."""
57
87
  self.config = config
@@ -74,6 +104,10 @@ class BaseProvider(ABC):
74
104
  """Validate the provider configuration. Returns (is_valid, error_message)."""
75
105
  pass
76
106
 
107
+ def _should_retry(self) -> bool:
108
+ """Check if this provider should use retry logic (cloud providers only)."""
109
+ return self.provider_type != ProviderType.OLLAMA
110
+
77
111
  def invoke(self, prompt: str, system_prompt: Optional[str] = None) -> str:
78
112
  """Invoke the LLM with a prompt and optional system message."""
79
113
  messages: List[BaseMessage] = []
@@ -81,8 +115,33 @@ class BaseProvider(ABC):
81
115
  messages.append(SystemMessage(content=system_prompt))
82
116
  messages.append(HumanMessage(content=prompt))
83
117
 
84
- response = self.llm.invoke(messages)
85
- return str(response.content)
118
+ if self._should_retry():
119
+ return self._invoke_with_retry(messages)
120
+ else:
121
+ response = self.llm.invoke(messages)
122
+ return str(response.content)
123
+
124
+ def _invoke_with_retry(self, messages: List[BaseMessage]) -> str:
125
+ """Invoke with exponential backoff retry for cloud providers."""
126
+ @retry(
127
+ stop=stop_after_attempt(self._retry_attempts),
128
+ wait=wait_exponential(multiplier=1, min=self._retry_min_wait, max=self._retry_max_wait),
129
+ retry=retry_if_exception_type(Exception),
130
+ before_sleep=before_sleep_log(logger, logging.WARNING),
131
+ reraise=True,
132
+ )
133
+ def _do_invoke():
134
+ try:
135
+ response = self.llm.invoke(messages)
136
+ return str(response.content)
137
+ except Exception as e:
138
+ if _is_retryable_error(e):
139
+ logger.warning(f"Retryable error from {self.provider_type.value}: {e}")
140
+ raise # Will be retried
141
+ else:
142
+ raise # Non-retryable, will not retry
143
+
144
+ return _do_invoke()
86
145
 
87
146
  def stream(self, prompt: str, system_prompt: Optional[str] = None) -> Iterator[str]:
88
147
  """Stream LLM response for real-time output."""
@@ -91,8 +150,39 @@ class BaseProvider(ABC):
91
150
  messages.append(SystemMessage(content=system_prompt))
92
151
  messages.append(HumanMessage(content=prompt))
93
152
 
94
- for chunk in self.llm.stream(messages):
95
- yield str(chunk.content)
153
+ if self._should_retry():
154
+ yield from self._stream_with_retry(messages)
155
+ else:
156
+ for chunk in self.llm.stream(messages):
157
+ yield str(chunk.content)
158
+
159
+ def _stream_with_retry(self, messages: List[BaseMessage]) -> Iterator[str]:
160
+ """Stream with retry logic for cloud providers."""
161
+ last_exception = None
162
+
163
+ for attempt in range(self._retry_attempts):
164
+ try:
165
+ for chunk in self.llm.stream(messages):
166
+ yield str(chunk.content)
167
+ return # Success, exit
168
+ except Exception as e:
169
+ last_exception = e
170
+ if _is_retryable_error(e) and attempt < self._retry_attempts - 1:
171
+ import time
172
+ wait_time = min(
173
+ self._retry_max_wait,
174
+ self._retry_min_wait * (2 ** attempt)
175
+ )
176
+ logger.warning(
177
+ f"Retryable error from {self.provider_type.value}: {e}. "
178
+ f"Retrying in {wait_time}s (attempt {attempt + 1}/{self._retry_attempts})"
179
+ )
180
+ time.sleep(wait_time)
181
+ else:
182
+ raise
183
+
184
+ if last_exception:
185
+ raise last_exception
96
186
 
97
187
  def check_connection(self) -> bool:
98
188
  """Check if the provider is accessible."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cognify-code
3
- Version: 0.2.5
3
+ Version: 0.2.6
4
4
  Summary: Your local AI-powered code assistant. Review, generate, search, and refactor code with an intelligent AI agent—all running locally with complete privacy.
5
5
  Author-email: Ashok Kumar <akkssy@users.noreply.github.com>
6
6
  Maintainer-email: Ashok Kumar <akkssy@users.noreply.github.com>
@@ -39,6 +39,7 @@ Requires-Dist: rich>=13.0.0
39
39
  Requires-Dist: pyyaml>=6.0
40
40
  Requires-Dist: pydantic>=2.0.0
41
41
  Requires-Dist: pydantic-settings>=2.0.0
42
+ Requires-Dist: tenacity>=8.0.0
42
43
  Requires-Dist: chromadb>=0.4.0
43
44
  Requires-Dist: sentence-transformers>=2.0.0
44
45
  Requires-Dist: watchdog>=4.0.0
@@ -68,11 +69,11 @@ Dynamic: license-file
68
69
 
69
70
  <p align="center">
70
71
  <a href="#features">Features</a> •
72
+ <a href="#vscode-extension">VSCode Extension</a> •
71
73
  <a href="#installation">Installation</a> •
72
74
  <a href="#providers">Providers</a> •
73
75
  <a href="#usage">Usage</a> •
74
- <a href="#documentation">Docs</a>
75
- <a href="#contributing">Contributing</a>
76
+ <a href="#documentation">Docs</a>
76
77
  </p>
77
78
 
78
79
  <p align="center">
@@ -80,11 +81,12 @@ Dynamic: license-file
80
81
  <img src="https://img.shields.io/badge/license-MIT-green.svg" alt="MIT License">
81
82
  <img src="https://img.shields.io/badge/tests-144%20passed-brightgreen.svg" alt="Tests">
82
83
  <img src="https://img.shields.io/badge/providers-6%20supported-purple.svg" alt="6 Providers">
84
+ <img src="https://img.shields.io/badge/VSCode-Extension-blue.svg" alt="VSCode Extension">
83
85
  </p>
84
86
 
85
87
  ---
86
88
 
87
- A powerful CLI tool that brings AI-powered code cognition to your terminal. Review code, generate functions, search your codebase semantically, and refactor projects—with support for **multiple LLM providers** including local (Ollama) and cloud options with free tiers.
89
+ A powerful CLI tool and **VSCode extension** that brings AI-powered code cognition to your development workflow. Review code, generate functions, search your codebase semantically, and refactor projects—with support for **multiple LLM providers** including local (Ollama) and cloud options with free tiers.
88
90
 
89
91
  ## ✨ Features
90
92
 
@@ -95,10 +97,67 @@ A powerful CLI tool that brings AI-powered code cognition to your terminal. Revi
95
97
  | 🔎 **Semantic Search** | Search your codebase using natural language queries |
96
98
  | 📝 **AI File Editing** | Edit files with natural language instructions |
97
99
  | 🔄 **Multi-File Refactor** | Refactor across multiple files at once |
98
- | ��️ **Symbol Renaming** | Rename functions, classes, variables across your project |
100
+ | ✏️ **Symbol Renaming** | Rename functions, classes, variables across your project |
99
101
  | 💬 **Interactive Chat** | Chat with AI about your code |
100
102
  | 📊 **Codebase Indexing** | Create searchable semantic index with RAG |
101
103
  | 🌐 **Multi-Provider** | Support for 6 LLM providers (local & cloud) |
104
+ | 🖥️ **VSCode Extension** | Full IDE integration with sidebar chat |
105
+
106
+ ## 🖥️ VSCode Extension
107
+
108
+ <p align="center">
109
+ <img src="vscode-extension/images/icon.png" width="64" alt="Cognify VSCode">
110
+ </p>
111
+
112
+ The Cognify AI VSCode extension brings all the power of Cognify directly into your IDE with a beautiful sidebar chat interface.
113
+
114
+ ### Extension Features
115
+
116
+ - ⚛️ **Sidebar Chat Panel** - Chat with AI directly in VSCode sidebar
117
+ - 📎 **Add Context** - Include code from your editor in conversations
118
+ - 🔍 **Code Review** - Review files with inline diagnostics
119
+ - ✨ **Code Generation** - Generate code from descriptions
120
+ - 💡 **Code Explanation** - Get explanations for selected code
121
+ - ✏️ **AI Editing** - Edit code with natural language
122
+
123
+ ### Quick Actions
124
+ - 📋 Review current file
125
+ - 💡 Explain selected code
126
+ - ✨ Suggest improvements
127
+ - 🧪 Generate tests
128
+
129
+ ### Install VSCode Extension
130
+
131
+ ```bash
132
+ # Build from source
133
+ cd vscode-extension
134
+ npm install
135
+ npm run package
136
+
137
+ # Install the extension
138
+ code --install-extension cognify-ai-0.2.0.vsix
139
+ ```
140
+
141
+ Or install via VSCode:
142
+ 1. Open VSCode
143
+ 2. Go to Extensions sidebar
144
+ 3. Click "..." → "Install from VSIX..."
145
+ 4. Select `cognify-ai-0.2.0.vsix`
146
+
147
+ ### Using the Extension
148
+
149
+ 1. Click the ⚛️ Cognify icon in the Activity Bar (left sidebar)
150
+ 2. Use quick actions or type your question
151
+ 3. Click "📎 Add Context" to include code from your editor
152
+ 4. Press Enter or click Send
153
+
154
+ **Keyboard Shortcuts:**
155
+ | Command | Mac | Windows/Linux |
156
+ |---------|-----|---------------|
157
+ | Review File | `Cmd+Shift+R` | `Ctrl+Shift+R` |
158
+ | Generate Code | `Cmd+Shift+G` | `Ctrl+Shift+G` |
159
+ | Explain Code | `Cmd+Shift+E` | `Ctrl+Shift+E` |
160
+ | Open Chat | `Cmd+Shift+C` | `Ctrl+Shift+C` |
102
161
 
103
162
  ## 🤖 Supported Providers
104
163
 
@@ -153,87 +212,46 @@ pip install -e .
153
212
 
154
213
  ```bash
155
214
  # Check status and available providers
156
- ai-assist status
157
- ai-assist providers
158
- ```
159
-
160
- ## 🌐 Provider Management
161
-
162
- ### List Available Providers
163
- ```bash
164
- ai-assist providers
215
+ cognify status
216
+ cognify providers
165
217
  ```
166
- Shows all providers with their models, free tier status, and API key requirements.
167
-
168
- ### Switch Providers
169
- ```bash
170
- # Switch to Groq (fast cloud inference)
171
- ai-assist use-provider groq --test
172
218
 
173
- # Switch to Google with specific model
174
- ai-assist use-provider google --model gemini-1.5-pro --test
175
-
176
- # Use OpenRouter with free DeepSeek R1
177
- ai-assist use-provider openrouter --model deepseek/deepseek-r1:free --test
178
- ```
179
-
180
- ### Test Provider Connection
181
- ```bash
182
- ai-assist test-provider
183
- ai-assist test-provider --provider groq --prompt "Hello world"
184
- ```
185
-
186
- ## 📖 Usage
219
+ ## 📖 CLI Usage
187
220
 
188
221
  ### Code Review
189
222
  ```bash
190
- ai-assist review path/to/file.py
191
- ai-assist review src/ --format json
223
+ cognify review path/to/file.py
224
+ cognify review src/ --format json
192
225
  ```
193
226
 
194
227
  ### Code Generation
195
228
  ```bash
196
- ai-assist generate "binary search function" --language python
197
- ai-assist generate "REST API client class" --mode class
198
- ai-assist generate "unit tests for calculator" --mode test
229
+ cognify generate "binary search function" --language python
230
+ cognify generate "REST API client class" --mode class
199
231
  ```
200
232
 
201
233
  ### Semantic Search
202
234
  ```bash
203
235
  # First, index your codebase
204
- ai-assist index .
236
+ cognify index .
205
237
 
206
238
  # Then search
207
- ai-assist search "error handling"
208
- ai-assist search "database connection" -k 10
239
+ cognify search "error handling"
209
240
  ```
210
241
 
211
242
  ### File Editing
212
243
  ```bash
213
- ai-assist edit config.py "add logging to all functions" --preview
214
- ai-assist edit utils.py "add type hints" --backup
215
- ```
216
-
217
- ### Multi-File Refactoring
218
- ```bash
219
- ai-assist refactor "add docstrings to all functions" -p "src/**/*.py" --dry-run
220
- ai-assist refactor "convert print to logging" --pattern "**/*.py" --confirm
221
- ```
222
-
223
- ### Symbol Renaming
224
- ```bash
225
- ai-assist rename old_function new_function --type function --dry-run
226
- ai-assist rename MyClass BetterClass --type class -p "src/**/*.py"
244
+ cognify edit config.py "add logging to all functions" --preview
227
245
  ```
228
246
 
229
247
  ### Interactive Chat
230
248
  ```bash
231
- ai-assist chat
249
+ cognify chat
232
250
  ```
233
251
 
234
252
  ### All Commands
235
253
  ```bash
236
- ai-assist --help
254
+ cognify --help
237
255
  ```
238
256
 
239
257
  ## ⚙️ Configuration
@@ -242,32 +260,11 @@ Configuration is managed via `config.yaml`:
242
260
 
243
261
  ```yaml
244
262
  llm:
245
- provider: "ollama" # ollama, google, groq, cerebras, openrouter, openai
263
+ provider: "ollama"
246
264
  model: "deepseek-coder:6.7b"
247
- base_url: "http://localhost:11434" # For Ollama
265
+ base_url: "http://localhost:11434"
248
266
  temperature: 0.1
249
267
  max_tokens: 4096
250
- timeout: 120
251
-
252
- review:
253
- severity_levels: [critical, warning, suggestion]
254
- categories: [bugs, security, performance, style]
255
-
256
- generation:
257
- include_type_hints: true
258
- include_docstrings: true
259
-
260
- retrieval:
261
- embedding_model: "all-MiniLM-L6-v2"
262
- chunk_size: 50
263
-
264
- editor:
265
- create_backup: true
266
- show_diff: true
267
-
268
- refactor:
269
- max_files: 20
270
- require_confirmation: true
271
268
  ```
272
269
 
273
270
  Or use environment variables:
@@ -281,100 +278,36 @@ export GROQ_API_KEY="your-key"
281
278
 
282
279
  ```
283
280
  cognify-ai/
284
- ├── src/ai_code_assistant/
285
- │ ├── cli.py # Command-line interface
286
- │ ├── config.py # Configuration management
287
- │ ├── llm.py # LLM integration
288
- │ ├── providers/ # Multi-provider support
289
- ├── base.py # Provider base class
290
- ├── factory.py # Provider factory
291
- │ ├── ollama.py # Ollama (local)
292
- │ │ ├── google.py # Google AI Studio
293
- ├── groq.py # Groq
294
- ├── cerebras.py # Cerebras
295
- │ ├── openrouter.py # OpenRouter
296
- │ │ └── openai.py # OpenAI
297
- ├── reviewer/ # Code review module
298
- │ ├── generator/ # Code generation module
299
- │ ├── retrieval/ # Semantic search & indexing (RAG)
300
- │ ├── editor/ # AI file editing
301
- │ ├── refactor/ # Multi-file refactoring
302
- │ ├── chat/ # Interactive chat
303
- │ └── utils/ # Utilities & formatters
304
- ├── tests/ # 144 unit tests
305
- ├── docs/ # Documentation
306
- ├── config.yaml # Configuration
307
- └── pyproject.toml # Dependencies
281
+ ├── src/ai_code_assistant/ # Core Python package
282
+ │ ├── cli.py # Command-line interface
283
+ │ ├── providers/ # LLM providers
284
+ │ ├── reviewer/ # Code review
285
+ │ ├── generator/ # Code generation
286
+ │ ├── retrieval/ # Semantic search
287
+ │ ├── editor/ # AI file editing
288
+ └── chat/ # Interactive chat
289
+ ├── vscode-extension/ # VSCode Extension
290
+ │ ├── src/ # Extension source
291
+ │ ├── images/ # Icons
292
+ └── package.json # Extension manifest
293
+ ├── tests/ # Unit tests
294
+ ├── docs/ # Documentation
295
+ └── config.yaml # Configuration
308
296
  ```
309
297
 
310
298
  ## 🧪 Testing
311
299
 
312
300
  ```bash
313
- # Run all tests
314
301
  PYTHONPATH=src pytest tests/ -v
315
-
316
- # Run with coverage
317
- PYTHONPATH=src pytest tests/ --cov=ai_code_assistant
318
- ```
319
-
320
- ## 🛠️ Tech Stack
321
-
322
- | Component | Technology |
323
- |-----------|------------|
324
- | LLM Framework | LangChain |
325
- | Local LLM | Ollama |
326
- | Cloud LLMs | Google, Groq, OpenRouter, OpenAI |
327
- | Vector Database | ChromaDB |
328
- | Embeddings | Sentence Transformers |
329
- | CLI | Click + Rich |
330
- | Config | Pydantic |
331
- | Testing | Pytest |
332
-
333
- ## 🐛 Troubleshooting
334
-
335
- **"Connection refused" error (Ollama)**
336
- ```bash
337
- ollama serve # Make sure Ollama is running
338
- ```
339
-
340
- **API Key errors**
341
- ```bash
342
- ai-assist providers # Check which API keys are set
343
- export GROQ_API_KEY="your-key" # Set the appropriate key
344
- ```
345
-
346
- **Test provider connection**
347
- ```bash
348
- ai-assist test-provider --provider groq
349
- ```
350
-
351
- **Import errors**
352
- ```bash
353
- pip install -e ".[dev]"
354
302
  ```
355
303
 
356
304
  ## 🤝 Contributing
357
305
 
358
306
  Contributions are welcome! Please feel free to submit a Pull Request.
359
307
 
360
- 1. Fork the repository
361
- 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
362
- 3. Commit your changes (`git commit -m 'Add amazing feature'`)
363
- 4. Push to the branch (`git push origin feature/amazing-feature`)
364
- 5. Open a Pull Request
365
-
366
308
  ## 📄 License
367
309
 
368
- This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
369
-
370
- ## 🙏 Acknowledgments
371
-
372
- - [Ollama](https://ollama.ai) - Local LLM runtime
373
- - [LangChain](https://langchain.com) - LLM framework
374
- - [Google AI Studio](https://aistudio.google.com) - Gemini models
375
- - [Groq](https://groq.com) - Fast inference
376
- - [OpenRouter](https://openrouter.ai) - Multi-provider access
377
- - [ChromaDB](https://www.trychroma.com) - Vector database
310
+ MIT License - see [LICENSE](LICENSE) for details.
378
311
 
379
312
  ---
380
313
 
@@ -1,10 +1,10 @@
1
1
  ai_code_assistant/__init__.py,sha256=XnpG4h-2gW3cXseFvqQT_-XyOmVJtikVMrHUnmy8XKI,409
2
- ai_code_assistant/cli.py,sha256=Js8CyT_OjBymChhBeXKWrt2fK8KT0_2tfrTttgtsZSE,65497
2
+ ai_code_assistant/cli.py,sha256=vrz1zt8JbNXflXVpcSr1ZF_SphBrAPTwDcn-qGR5vfU,67912
3
3
  ai_code_assistant/config.py,sha256=6sAufexwzfCu2JNWvt9KevS9k_gMcjj1TAnwuaO1ZFw,4727
4
4
  ai_code_assistant/llm.py,sha256=DfcWJf6zEAUsPSEZLdEmb9o6BQNf1Ja88nswjpy6cOw,4209
5
5
  ai_code_assistant/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  ai_code_assistant/agent/__init__.py,sha256=BcVe4Ebopv_J01ApnRl05oN5yOet5mEefBrQmdPsUj0,1284
7
- ai_code_assistant/agent/code_agent.py,sha256=-zO9cANnYIIxLa4h-vJaqbxakHkBoa7Tek7z2RdWJkk,39668
7
+ ai_code_assistant/agent/code_agent.py,sha256=lykEvcbGl9V4RdKp2sCrP8-jlL-C7mIqvcAwpXHjcSY,28518
8
8
  ai_code_assistant/agent/code_generator.py,sha256=rAaziRU-mJ5NooERjR_Cd6_hwO0kuULw3Sp8Ca9kR48,13138
9
9
  ai_code_assistant/agent/code_reviewer.py,sha256=YiM7lRJhoN-vBnQb29jF-5nmE9ppL-OJffvx4ocTHEU,12066
10
10
  ai_code_assistant/agent/diff_engine.py,sha256=A5jszowc5VmWbdidpIW_QhswG_Hats3FYuemP8VoYv4,11018
@@ -27,7 +27,7 @@ ai_code_assistant/git/__init__.py,sha256=YgqmzneAnZyRrbazMqGoFSPIk5Yf5OTm2LXPbkQ
27
27
  ai_code_assistant/git/commit_generator.py,sha256=CzDH5ZPqEaXyPznBg8FgTz8wbV4adALUQD__kl8au6o,4135
28
28
  ai_code_assistant/git/manager.py,sha256=BYeYSz3yPpeJJESy2Zmu4MKEvJ5YAtw3HAmU6uba3nM,6815
29
29
  ai_code_assistant/providers/__init__.py,sha256=T8eLHOcjWvqNxLsD8uLmU2H1mJbGbZgUrUcrrVRcqPs,832
30
- ai_code_assistant/providers/base.py,sha256=6NJxzidnf5e_0hrbq5PxL4qsk9lGGU1Uzk4WDiWsZso,3969
30
+ ai_code_assistant/providers/base.py,sha256=Ep0ZXT2u3_nGUKItAMjf4fxDh2kDRT5CXaZn9P1r6Ys,7279
31
31
  ai_code_assistant/providers/cerebras.py,sha256=PfjfFtkFOip5OquyOnxlSQowpy8uPWNRLA6y4m-iYio,3098
32
32
  ai_code_assistant/providers/factory.py,sha256=U2zH3HFDGhed2nPRpTyDqG4JcFNHvTvxw25NER2NEi0,4579
33
33
  ai_code_assistant/providers/google.py,sha256=nEHsAUiBhV9TjtJEwxkMWydtnWiouVtl_2MrcU8GQNI,3344
@@ -50,9 +50,9 @@ ai_code_assistant/reviewer/prompts.py,sha256=9RrHEBttS5ngxY2BNsUvqGC6-cTxco-kDPb
50
50
  ai_code_assistant/utils/__init__.py,sha256=3HO-1Bj4VvUtM7W1C3MKR4DzQ9Xc875QKSHHkHwuqVs,368
51
51
  ai_code_assistant/utils/file_handler.py,sha256=jPxvtI5dJxkpPjELgRJ11WXamtyKKmZANQ1fcfMVtiU,5239
52
52
  ai_code_assistant/utils/formatters.py,sha256=5El9ew9HS6JLBucBUxxcw4fO5nLpOucgNJrJj2NC3zw,8945
53
- cognify_code-0.2.5.dist-info/licenses/LICENSE,sha256=5yu_kWq2bK-XKhWo79Eykdg4Qf3O8V2Ys7cpOO7GyyE,1063
54
- cognify_code-0.2.5.dist-info/METADATA,sha256=nXVlyJL2PI0JSu0rLtttjNgPTrhL9ZAJ6EejVEttKEM,11862
55
- cognify_code-0.2.5.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
56
- cognify_code-0.2.5.dist-info/entry_points.txt,sha256=MrBnnWPHZVozqqKyTlnJO63YN2kE5yPWKlr2nnRFRks,94
57
- cognify_code-0.2.5.dist-info/top_level.txt,sha256=dD_r1x-oX0s1uspYY72kig4jfIsjh3oDKwOBCMYXqpo,18
58
- cognify_code-0.2.5.dist-info/RECORD,,
53
+ cognify_code-0.2.6.dist-info/licenses/LICENSE,sha256=5yu_kWq2bK-XKhWo79Eykdg4Qf3O8V2Ys7cpOO7GyyE,1063
54
+ cognify_code-0.2.6.dist-info/METADATA,sha256=JD70dP-OBqiN8_VPo0T1qp-Ifw1OsQLesAD4sruE7vk,10189
55
+ cognify_code-0.2.6.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
56
+ cognify_code-0.2.6.dist-info/entry_points.txt,sha256=MrBnnWPHZVozqqKyTlnJO63YN2kE5yPWKlr2nnRFRks,94
57
+ cognify_code-0.2.6.dist-info/top_level.txt,sha256=dD_r1x-oX0s1uspYY72kig4jfIsjh3oDKwOBCMYXqpo,18
58
+ cognify_code-0.2.6.dist-info/RECORD,,