jiva-core 0.2.2 → 0.3.1

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 (156) hide show
  1. package/.dockerignore +53 -0
  2. package/.gcloudignore +49 -0
  3. package/CONTRIBUTING.md +92 -0
  4. package/Dockerfile +63 -0
  5. package/LICENSE +21 -0
  6. package/README.md +248 -102
  7. package/cloud-run-deploy.yaml +135 -0
  8. package/cloud-run.yaml +135 -0
  9. package/cloud-run.yaml.template +143 -0
  10. package/deploy.sh +107 -0
  11. package/dist/core/agent-spawner.d.ts +89 -0
  12. package/dist/core/agent-spawner.d.ts.map +1 -0
  13. package/dist/core/agent-spawner.js +195 -0
  14. package/dist/core/agent-spawner.js.map +1 -0
  15. package/dist/core/client-agent.d.ts +82 -0
  16. package/dist/core/client-agent.d.ts.map +1 -0
  17. package/dist/core/client-agent.js +406 -0
  18. package/dist/core/client-agent.js.map +1 -0
  19. package/dist/core/config.d.ts +59 -10
  20. package/dist/core/config.d.ts.map +1 -1
  21. package/dist/core/config.js +19 -2
  22. package/dist/core/config.js.map +1 -1
  23. package/dist/core/conversation-manager.d.ts +10 -18
  24. package/dist/core/conversation-manager.d.ts.map +1 -1
  25. package/dist/core/conversation-manager.js +28 -60
  26. package/dist/core/conversation-manager.js.map +1 -1
  27. package/dist/core/dual-agent.d.ts +24 -3
  28. package/dist/core/dual-agent.d.ts.map +1 -1
  29. package/dist/core/dual-agent.js +112 -19
  30. package/dist/core/dual-agent.js.map +1 -1
  31. package/dist/core/manager-agent.d.ts +3 -1
  32. package/dist/core/manager-agent.d.ts.map +1 -1
  33. package/dist/core/manager-agent.js +66 -14
  34. package/dist/core/manager-agent.js.map +1 -1
  35. package/dist/core/worker-agent.d.ts +15 -1
  36. package/dist/core/worker-agent.d.ts.map +1 -1
  37. package/dist/core/worker-agent.js +244 -11
  38. package/dist/core/worker-agent.js.map +1 -1
  39. package/dist/core/workspace.d.ts +5 -0
  40. package/dist/core/workspace.d.ts.map +1 -1
  41. package/dist/core/workspace.js +47 -7
  42. package/dist/core/workspace.js.map +1 -1
  43. package/dist/index.d.ts +3 -0
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +4 -0
  46. package/dist/index.js.map +1 -1
  47. package/dist/interfaces/cli/index.js +376 -44
  48. package/dist/interfaces/cli/index.js.map +1 -1
  49. package/dist/interfaces/cli/repl.d.ts.map +1 -1
  50. package/dist/interfaces/cli/repl.js +6 -0
  51. package/dist/interfaces/cli/repl.js.map +1 -1
  52. package/dist/interfaces/http/index.d.ts +22 -0
  53. package/dist/interfaces/http/index.d.ts.map +1 -0
  54. package/dist/interfaces/http/index.js +135 -0
  55. package/dist/interfaces/http/index.js.map +1 -0
  56. package/dist/interfaces/http/middleware/auth.d.ts +32 -0
  57. package/dist/interfaces/http/middleware/auth.d.ts.map +1 -0
  58. package/dist/interfaces/http/middleware/auth.js +176 -0
  59. package/dist/interfaces/http/middleware/auth.js.map +1 -0
  60. package/dist/interfaces/http/routes/chat.d.ts +7 -0
  61. package/dist/interfaces/http/routes/chat.d.ts.map +1 -0
  62. package/dist/interfaces/http/routes/chat.js +144 -0
  63. package/dist/interfaces/http/routes/chat.js.map +1 -0
  64. package/dist/interfaces/http/routes/health.d.ts +6 -0
  65. package/dist/interfaces/http/routes/health.d.ts.map +1 -0
  66. package/dist/interfaces/http/routes/health.js +25 -0
  67. package/dist/interfaces/http/routes/health.js.map +1 -0
  68. package/dist/interfaces/http/routes/session.d.ts +7 -0
  69. package/dist/interfaces/http/routes/session.d.ts.map +1 -0
  70. package/dist/interfaces/http/routes/session.js +114 -0
  71. package/dist/interfaces/http/routes/session.js.map +1 -0
  72. package/dist/interfaces/http/session-manager.d.ts +76 -0
  73. package/dist/interfaces/http/session-manager.d.ts.map +1 -0
  74. package/dist/interfaces/http/session-manager.js +339 -0
  75. package/dist/interfaces/http/session-manager.js.map +1 -0
  76. package/dist/interfaces/http/websocket-handler.d.ts +18 -0
  77. package/dist/interfaces/http/websocket-handler.d.ts.map +1 -0
  78. package/dist/interfaces/http/websocket-handler.js +146 -0
  79. package/dist/interfaces/http/websocket-handler.js.map +1 -0
  80. package/dist/mcp/client.d.ts +11 -2
  81. package/dist/mcp/client.d.ts.map +1 -1
  82. package/dist/mcp/client.js +44 -19
  83. package/dist/mcp/client.js.map +1 -1
  84. package/dist/mcp/server-manager.d.ts +1 -1
  85. package/dist/mcp/server-manager.d.ts.map +1 -1
  86. package/dist/mcp/server-manager.js +12 -2
  87. package/dist/mcp/server-manager.js.map +1 -1
  88. package/dist/personas/index.d.ts +13 -0
  89. package/dist/personas/index.d.ts.map +1 -0
  90. package/dist/personas/index.js +13 -0
  91. package/dist/personas/index.js.map +1 -0
  92. package/dist/personas/persona-loader.d.ts +30 -0
  93. package/dist/personas/persona-loader.d.ts.map +1 -0
  94. package/dist/personas/persona-loader.js +246 -0
  95. package/dist/personas/persona-loader.js.map +1 -0
  96. package/dist/personas/persona-manager.d.ts +82 -0
  97. package/dist/personas/persona-manager.d.ts.map +1 -0
  98. package/dist/personas/persona-manager.js +211 -0
  99. package/dist/personas/persona-manager.js.map +1 -0
  100. package/dist/personas/skill-loader.d.ts +35 -0
  101. package/dist/personas/skill-loader.d.ts.map +1 -0
  102. package/dist/personas/skill-loader.js +144 -0
  103. package/dist/personas/skill-loader.js.map +1 -0
  104. package/dist/personas/skill-packager.d.ts +25 -0
  105. package/dist/personas/skill-packager.d.ts.map +1 -0
  106. package/dist/personas/skill-packager.js +233 -0
  107. package/dist/personas/skill-packager.js.map +1 -0
  108. package/dist/personas/types.d.ts +134 -0
  109. package/dist/personas/types.d.ts.map +1 -0
  110. package/dist/personas/types.js +7 -0
  111. package/dist/personas/types.js.map +1 -0
  112. package/dist/personas/validator.d.ts +22 -0
  113. package/dist/personas/validator.d.ts.map +1 -0
  114. package/dist/personas/validator.js +144 -0
  115. package/dist/personas/validator.js.map +1 -0
  116. package/dist/storage/factory.d.ts +51 -0
  117. package/dist/storage/factory.d.ts.map +1 -0
  118. package/dist/storage/factory.js +154 -0
  119. package/dist/storage/factory.js.map +1 -0
  120. package/dist/storage/gcp-bucket-provider.d.ts +59 -0
  121. package/dist/storage/gcp-bucket-provider.d.ts.map +1 -0
  122. package/dist/storage/gcp-bucket-provider.js +275 -0
  123. package/dist/storage/gcp-bucket-provider.js.map +1 -0
  124. package/dist/storage/index.d.ts +33 -0
  125. package/dist/storage/index.d.ts.map +1 -0
  126. package/dist/storage/index.js +37 -0
  127. package/dist/storage/index.js.map +1 -0
  128. package/dist/storage/local-provider.d.ts +36 -0
  129. package/dist/storage/local-provider.d.ts.map +1 -0
  130. package/dist/storage/local-provider.js +219 -0
  131. package/dist/storage/local-provider.js.map +1 -0
  132. package/dist/storage/provider.d.ts +137 -0
  133. package/dist/storage/provider.d.ts.map +1 -0
  134. package/dist/storage/provider.js +136 -0
  135. package/dist/storage/provider.js.map +1 -0
  136. package/dist/storage/types.d.ts +78 -0
  137. package/dist/storage/types.d.ts.map +1 -0
  138. package/dist/storage/types.js +14 -0
  139. package/dist/storage/types.js.map +1 -0
  140. package/dist/utils/orchestration-logger.d.ts +36 -0
  141. package/dist/utils/orchestration-logger.d.ts.map +1 -0
  142. package/dist/utils/orchestration-logger.js +224 -0
  143. package/dist/utils/orchestration-logger.js.map +1 -0
  144. package/jiva-new-demo.gif +0 -0
  145. package/package.json +30 -2
  146. package/.fluen/cache/state.json +0 -7
  147. package/actions/action_registry.py +0 -75
  148. package/actions/python_coder.py +0 -470
  149. package/api/main.py +0 -269
  150. package/downloaded_image.avif +0 -0
  151. package/downloads/snipping_tool.avif +0 -0
  152. package/image.avif +0 -0
  153. package/ms_image.avif +0 -0
  154. package/screenshot.png +0 -0
  155. package/snipping_tool.avif +0 -0
  156. package/tmp_image.avif +0 -0
package/package.json CHANGED
@@ -1,16 +1,21 @@
1
1
  {
2
2
  "name": "jiva-core",
3
- "version": "0.2.2",
4
- "description": "Versatile autonomous AI agent powered by gpt-oss-120b with full MCP support. Mission-driven execution, smart conversations, and extensible tooling.",
3
+ "version": "0.3.1",
4
+ "description": "Versatile autonomous AI agent with three-agent architecture (Manager, Worker, Client) powered by gpt-oss-120b. Adaptive validation, full MCP support, and intelligent quality control.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
7
7
  "jiva": "dist/interfaces/cli/index.js"
8
8
  },
9
9
  "type": "module",
10
10
  "scripts": {
11
+ "test:web": "playwright test",
12
+ "postinstall": "playwright install",
13
+
11
14
  "build": "tsc",
12
15
  "dev": "tsx src/interfaces/cli/index.ts",
16
+ "dev:http": "tsx src/interfaces/http/index.ts",
13
17
  "start": "node --loader ts-node/esm src/interfaces/cli/index.ts",
18
+ "serve": "node dist/interfaces/http/index.js",
14
19
  "type-check": "tsc --noEmit",
15
20
  "fix-filesystem": "node fix-filesystem-config.js",
16
21
  "prepublishOnly": "npm run build"
@@ -46,23 +51,46 @@
46
51
  "dependencies": {
47
52
  "@modelcontextprotocol/sdk": "^1.0.4",
48
53
  "@modelcontextprotocol/server-filesystem": "^2025.11.25",
54
+ "archiver": "^7.0.1",
49
55
  "chalk": "^5.3.0",
50
56
  "commander": "^12.1.0",
51
57
  "conf": "^13.0.1",
58
+ "express": "^4.18.2",
52
59
  "inquirer": "^12.0.1",
60
+ "jsonwebtoken": "^9.0.2",
53
61
  "marked": "^15.0.12",
54
62
  "marked-terminal": "^7.3.0",
55
63
  "ora": "^8.1.1",
64
+ "unzipper": "^0.12.3",
65
+ "ws": "^8.16.0",
66
+ "yaml": "^2.6.1",
56
67
  "zod": "^3.24.1"
57
68
  },
58
69
  "devDependencies": {
59
70
  "ts-node": "^10.9.2",
71
+ "@types/archiver": "^6.0.2",
72
+ "@types/express": "^4.17.21",
60
73
  "@types/inquirer": "^9.0.7",
74
+ "@types/jsonwebtoken": "^9.0.5",
61
75
  "@types/marked-terminal": "^6.1.1",
62
76
  "@types/node": "^22.10.2",
77
+ "@types/unzipper": "^0.10.10",
78
+ "@types/ws": "^8.5.10",
63
79
  "tsx": "^4.19.2",
64
80
  "typescript": "^5.7.2"
65
81
  },
82
+ "peerDependencies": {
83
+ "@google-cloud/storage": "^7.0.0",
84
+ "firebase-admin": "^12.0.0"
85
+ },
86
+ "peerDependenciesMeta": {
87
+ "@google-cloud/storage": {
88
+ "optional": true
89
+ },
90
+ "firebase-admin": {
91
+ "optional": true
92
+ }
93
+ },
66
94
  "engines": {
67
95
  "node": ">=20.0.0"
68
96
  }
@@ -1,7 +0,0 @@
1
- {
2
- "last_commit": "fe577ea54ef245da4b80b33846e5db8f01a8e564",
3
- "last_run_timestamp": null,
4
- "files_processed": 223,
5
- "total_files": 223,
6
- "manifest_path": null
7
- }
@@ -1,75 +0,0 @@
1
- # actions/action_registry.py
2
-
3
- from typing import Dict, Callable, Any
4
- from core.llm_interface import LLMInterface
5
- from core.memory import Memory
6
-
7
- # Import actions with their existing docstrings
8
- from actions.file_operations import (
9
- read_file, write_file, append_file, delete_file,
10
- list_directory, create_directory,
11
- read_json, write_json,
12
- read_csv, write_csv
13
- )
14
- import actions.python_coder as py
15
- import actions.memory_retrieval as mem
16
- import actions.think as think
17
-
18
- import actions.web_interface as wi
19
-
20
- def get_action_registry(llm_interface: LLMInterface, memory: Memory) -> Dict[str, Callable]:
21
- """
22
- Get the registry of all available actions.
23
-
24
- Args:
25
- llm_interface (LLMInterface): The language model interface.
26
- memory (Memory): The memory interface.
27
-
28
- Returns:
29
- Dict[str, Callable]: A dictionary mapping action names to their corresponding functions.
30
- """
31
- wi.set_llm_interface(llm=llm_interface)
32
- think.set_llm_interface(llm=llm_interface)
33
- py.set_llm_interface(llm=llm_interface)
34
- mem.set_memory(memory_instance=memory)
35
-
36
- actions = {
37
- # File operations
38
- "read_file": read_file,
39
- "write_file": write_file,
40
- "append_file": append_file,
41
- "delete_file": delete_file,
42
- "list_directory": list_directory,
43
- "create_directory": create_directory,
44
- "read_json": read_json,
45
- "write_json": write_json,
46
- "read_csv": read_csv,
47
- "write_csv": write_csv,
48
-
49
- # Python coding actions
50
- "generate_python_code": py.generate_python_code,
51
- "write_python_code": py.write_python_code,
52
- "execute_python_code": py.execute_python_code,
53
- "analyze_python_code": py.analyze_python_code,
54
- "test_python_function": py.test_python_function,
55
-
56
- # Memory operations
57
- # "retrieve_recent_memory": lambda n: retrieve_recent_memory(memory, n),
58
- # "retrieve_task_result": lambda task_description: retrieve_task_result(memory, task_description),
59
- # "retrieve_context_for_task": lambda task_description, n=5: retrieve_context_for_task(memory, task_description, n),
60
- "query_long_term_memory": mem.query_long_term_memory,
61
-
62
- # Think action
63
- "think": think.think,
64
- "replan_tasks": think.replan_tasks,
65
- "sleep": think.sleep,
66
- "rerun_tasks": think.rerun_tasks,
67
-
68
- # Web search actions
69
- "web_search": wi.web_search,
70
- "visit_page": wi.visit_page,
71
- "find_links": wi.find_links,
72
- }
73
-
74
-
75
- return actions
@@ -1,470 +0,0 @@
1
- import asyncio
2
- import ast
3
- import re
4
- import sys
5
- import os
6
- import subprocess
7
- import logging
8
- from typing import Dict, List, Any, Optional
9
-
10
- from core.llm_interface import LLMInterface
11
- from actions.file_operations import read_file, write_file
12
-
13
- logger = logging.getLogger(__name__)
14
- llm_interface: Optional[LLMInterface] = None
15
-
16
- def set_llm_interface(llm: LLMInterface):
17
- """Set the LLM interface for code generation functions."""
18
- global llm_interface
19
- llm_interface = llm
20
-
21
- def extract_python_code(llm_response: str) -> str:
22
- """
23
- Extract Python code from an LLM response that contains markdown code blocks.
24
-
25
- Args:
26
- llm_response (str): The full response from the LLM
27
-
28
- Returns:
29
- str: The extracted Python code, or empty string if no code found
30
- """
31
- # Find Python code blocks (```python ... ```)
32
- python_blocks = re.findall(r'```(?:python)?\s*([\s\S]*?)\s*```', llm_response)
33
-
34
- if not python_blocks:
35
- # If no code blocks with backticks, try to find the entire code
36
- # This is a fallback in case the LLM didn't format with code blocks
37
- if "def " in llm_response and ":" in llm_response:
38
- # Try to extract what looks like a function definition
39
- return llm_response
40
- return ""
41
-
42
- # Join multiple code blocks if present
43
- return "\n\n".join(python_blocks)
44
-
45
- async def generate_python_code(prompt: str, context: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
46
- """
47
- Generate Python code based on a prompt. This uses the LLM to create code
48
- and extracts just the Python code from the response.
49
-
50
- Args:
51
- prompt (str): Instruction for what code to generate
52
- context (Optional[Dict[str, Any]]): Additional context
53
-
54
- Returns:
55
- Dict[str, Any]: Result containing the generated code and explanation
56
- """
57
- if not llm_interface:
58
- return {"success": False, "error": "LLM interface not set. Cannot generate code."}
59
-
60
- enhanced_prompt = f"""
61
- Write Python code for the following:
62
- {prompt}
63
-
64
- Your response should include:
65
- 1. A brief explanation of your approach
66
- 2. Complete, working Python code that addresses the requirement
67
- 3. Comments explaining any complex parts
68
-
69
- Present your code in a ```python code block and ensure it's functional and correct.
70
- Make sure your code handles errors appropriately and follows best practices.
71
- """
72
-
73
- if context:
74
- enhanced_prompt += f"\n\nAdditional context:\n{context}"
75
-
76
- try:
77
- llm_response = await llm_interface.generate(enhanced_prompt)
78
-
79
- # Extract the code
80
- code = extract_python_code(llm_response)
81
-
82
- if not code:
83
- return {
84
- "success": False,
85
- "error": "Failed to extract Python code from the LLM response",
86
- "full_response": llm_response
87
- }
88
-
89
- # Get the explanation (everything before the first code block)
90
- explanation_match = re.match(r'(.*?)```', llm_response, re.DOTALL)
91
- explanation = explanation_match.group(1).strip() if explanation_match else ""
92
-
93
- return {
94
- "success": True,
95
- "code": code,
96
- "explanation": explanation,
97
- "full_response": llm_response
98
- }
99
- except Exception as e:
100
- logger.error(f"Error generating Python code: {str(e)}")
101
- return {"success": False, "error": f"Failed to generate code: {str(e)}"}
102
-
103
- async def write_python_code(file_path: str, code: str, description: str = "") -> Dict[str, Any]:
104
- """
105
- Write Python code to a file. Validates syntax before writing.
106
-
107
- Args:
108
- file_path (str): Path where the Python file should be created
109
- code (str): Python code to write
110
- description (str): Optional description of what the code does
111
-
112
- Returns:
113
- Dict[str, Any]: Result indicating success or failure with details
114
- """
115
- # Handle the case where code is a dictionary from generate_python_code
116
- if isinstance(code, dict):
117
- if 'code' in code:
118
- code = code['code']
119
- elif 'success' in code and not code.get('success', False):
120
- return {
121
- "success": False,
122
- "error": f"Cannot write Python code: {code.get('error', 'Unknown error')}"
123
- }
124
-
125
- # Create directory if it doesn't exist
126
- dir_path = os.path.dirname(file_path)
127
- if dir_path and not os.path.exists(dir_path):
128
- os.makedirs(dir_path, exist_ok=True)
129
-
130
- # Validate Python syntax
131
- try:
132
- ast.parse(code)
133
- except SyntaxError as e:
134
- return {
135
- "success": False,
136
- "error": f"Python code has syntax errors: {str(e)}",
137
- "line": e.lineno,
138
- "offset": e.offset,
139
- "text": e.text
140
- }
141
-
142
- # Add description as doc comment if provided
143
- if description:
144
- doc_comment = f'"""\n{description}\n"""\n\n'
145
- if not code.startswith('"""'):
146
- code = doc_comment + code
147
-
148
- # Write the code to file
149
- try:
150
- with open(file_path, 'w') as f:
151
- f.write(code)
152
-
153
- return {
154
- "success": True,
155
- "message": f"Python code written to {file_path}",
156
- "file_path": file_path,
157
- "code_length": len(code)
158
- }
159
- except Exception as e:
160
- return {
161
- "success": False,
162
- "error": f"Failed to write Python code: {str(e)}"
163
- }
164
-
165
- async def execute_python_code(file_path: str, args: List[str] = None, timeout: int = 30) -> Dict[str, Any]:
166
- """
167
- Execute a Python file and return the output.
168
-
169
- Args:
170
- file_path (str): Path to the Python file to execute
171
- args (List[str]): Optional command line arguments
172
- timeout (int): Maximum execution time in seconds
173
-
174
- Returns:
175
- Dict[str, Any]: The execution results including stdout, stderr, and return code
176
- """
177
- if not os.path.exists(file_path):
178
- return {
179
- "success": False,
180
- "error": f"File not found: {file_path}"
181
- }
182
-
183
- # Check if the file contains valid Python code
184
- try:
185
- with open(file_path, 'r') as f:
186
- code = f.read()
187
-
188
- # Check if it looks like a serialized object
189
- if code.strip().startswith('{') and ('code' in code or 'success' in code):
190
- # Try to extract actual Python code
191
- try:
192
- import json
193
- data = json.loads(code)
194
- if isinstance(data, dict) and 'code' in data:
195
- # Extract and save the actual code
196
- extracted_code = data['code']
197
- with open(file_path, 'w') as f:
198
- f.write(extracted_code)
199
- logger.info(f"Fixed malformed Python file: {file_path}")
200
- else:
201
- # Try regex as a fallback
202
- import re
203
- code_match = re.search(r"'code':\s*'([^']+)'", code)
204
- if code_match:
205
- # Extract and save the actual code
206
- extracted_code = code_match.group(1).replace('\\n', '\n').replace('\\t', '\t')
207
- with open(file_path, 'w') as f:
208
- f.write(extracted_code)
209
- logger.info(f"Fixed malformed Python file with regex: {file_path}")
210
- else:
211
- return {
212
- "success": False,
213
- "error": f"File contains a serialized object, not valid Python code"
214
- }
215
- except Exception as e:
216
- logger.warning(f"Error fixing Python file content: {e}")
217
- return {
218
- "success": False,
219
- "error": f"File appears to contain invalid content: {str(e)}"
220
- }
221
- except Exception as e:
222
- logger.warning(f"Error checking Python file: {str(e)}")
223
-
224
- # Execute the file
225
- cmd = [sys.executable, file_path]
226
- if args:
227
- cmd.extend(args)
228
-
229
- try:
230
- process = await asyncio.create_subprocess_exec(
231
- *cmd,
232
- stdout=asyncio.subprocess.PIPE,
233
- stderr=asyncio.subprocess.PIPE,
234
- limit=1024 * 1024 # 1MB limit for output
235
- )
236
-
237
- try:
238
- stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=timeout)
239
- stdout_str = stdout.decode('utf-8', errors='replace')
240
- stderr_str = stderr.decode('utf-8', errors='replace')
241
-
242
- if process.returncode != 0:
243
- return {
244
- "success": False,
245
- "stdout": stdout_str,
246
- "stderr": stderr_str,
247
- "returncode": process.returncode,
248
- "error": f"Process exited with code {process.returncode}"
249
- }
250
-
251
- return {
252
- "success": True,
253
- "stdout": stdout_str,
254
- "stderr": stderr_str,
255
- "returncode": process.returncode
256
- }
257
-
258
- except asyncio.TimeoutError:
259
- try:
260
- process.kill()
261
- except:
262
- pass
263
- return {
264
- "success": False,
265
- "error": f"Execution timed out after {timeout} seconds"
266
- }
267
-
268
- except Exception as e:
269
- return {
270
- "success": False,
271
- "error": f"Error executing Python code: {str(e)}"
272
- }
273
-
274
- async def analyze_python_code(file_path: str) -> Dict[str, Any]:
275
- """
276
- Analyze Python code for errors, potential improvements, and best practices.
277
-
278
- Args:
279
- file_path (str): Path to the Python file to analyze
280
-
281
- Returns:
282
- Dict[str, Any]: Analysis results with suggestions
283
- """
284
- if not llm_interface:
285
- return {"error": "LLM interface not set. Cannot analyze code."}
286
-
287
- if not os.path.exists(file_path):
288
- return {"error": f"File not found: {file_path}"}
289
-
290
- try:
291
- code = await read_file(file_path)
292
- if isinstance(code, dict) and 'error' in code:
293
- return {"error": f"Failed to read file: {code['error']}"}
294
-
295
- # Check syntax
296
- try:
297
- ast.parse(code)
298
- except SyntaxError as e:
299
- return {
300
- "success": False,
301
- "has_syntax_errors": True,
302
- "error": f"Syntax error at line {e.lineno}, column {e.offset}: {e.msg}",
303
- "line": e.lineno,
304
- "text": e.text
305
- }
306
-
307
- # Use LLM to analyze the code
308
- prompt = f"""
309
- Analyze the following Python code for:
310
- 1. Potential bugs or errors
311
- 2. Performance improvements
312
- 3. Best practices and PEP8 compliance
313
- 4. Security concerns
314
- 5. Overall code quality
315
-
316
- Provide specific suggestions for improvements.
317
-
318
- Python code to analyze:
319
- ```python
320
- {code}
321
- ```
322
-
323
- Format your response as a detailed analysis with sections for each category.
324
- Include line numbers when referring to specific code.
325
- """
326
-
327
- analysis = await llm_interface.generate(prompt)
328
-
329
- return {
330
- "success": True,
331
- "has_syntax_errors": False,
332
- "analysis": analysis,
333
- "code": code
334
- }
335
-
336
- except Exception as e:
337
- return {"error": f"Error analyzing Python code: {str(e)}"}
338
-
339
- async def test_python_function(file_path: str, function_name: str, test_cases: List[Dict[str, Any]]) -> Dict[str, Any]:
340
- """
341
- Test a Python function with multiple test cases.
342
-
343
- Args:
344
- file_path (str): Path to the Python file containing the function
345
- function_name (str): Name of the function to test
346
- test_cases (List[Dict[str, Any]]): List of test cases, each with 'inputs' and 'expected_output'
347
-
348
- Returns:
349
- Dict[str, Any]: Test results for each test case
350
- """
351
- if not os.path.exists(file_path):
352
- return {"error": f"File not found: {file_path}"}
353
-
354
- # Create a temporary test file
355
- import tempfile
356
-
357
- try:
358
- module_name = os.path.basename(file_path).replace('.py', '')
359
-
360
- with tempfile.NamedTemporaryFile(suffix='.py', mode='w', delete=False) as test_file:
361
- test_file_path = test_file.name
362
-
363
- # Write test code
364
- test_code = f"""
365
- import sys
366
- import json
367
- import traceback
368
- from pathlib import Path
369
-
370
- # Add the directory containing the module to Python path
371
- file_dir = Path(r'{os.path.dirname(os.path.abspath(file_path))}')
372
- if str(file_dir) not in sys.path:
373
- sys.path.insert(0, str(file_dir))
374
-
375
- try:
376
- # Import the function
377
- from {module_name} import {function_name}
378
-
379
- # Run test cases
380
- results = []
381
-
382
- test_cases = {test_cases}
383
-
384
- for i, test_case in enumerate(test_cases):
385
- try:
386
- inputs = test_case['inputs']
387
- expected = test_case['expected_output']
388
-
389
- # Handle different input types
390
- if isinstance(inputs, list):
391
- actual = {function_name}(*inputs)
392
- elif isinstance(inputs, dict):
393
- actual = {function_name}(**inputs)
394
- else:
395
- actual = {function_name}(inputs)
396
-
397
- success = actual == expected
398
-
399
- results.append({{
400
- 'test_case': i + 1,
401
- 'inputs': inputs,
402
- 'expected': expected,
403
- 'actual': actual,
404
- 'success': success
405
- }})
406
- except Exception as e:
407
- results.append({{
408
- 'test_case': i + 1,
409
- 'inputs': inputs,
410
- 'error': str(e),
411
- 'traceback': traceback.format_exc(),
412
- 'success': False
413
- }})
414
-
415
- print(json.dumps(results))
416
-
417
- except Exception as e:
418
- print(json.dumps({{
419
- 'error': str(e),
420
- 'traceback': traceback.format_exc()
421
- }}))
422
- """
423
- test_file.write(test_code)
424
-
425
- # Execute the test file
426
- result = await execute_python_code(test_file_path)
427
-
428
- # Clean up the temporary file
429
- try:
430
- os.unlink(test_file_path)
431
- except:
432
- pass
433
-
434
- if not result['success']:
435
- return {
436
- "success": False,
437
- "error": f"Error running tests: {result.get('stderr', result.get('error', 'Unknown error'))}"
438
- }
439
-
440
- # Parse the test results
441
- try:
442
- test_results = json.loads(result['stdout'])
443
-
444
- # Check if there was an error importing the function
445
- if 'error' in test_results and 'traceback' in test_results:
446
- return {
447
- "success": False,
448
- "error": test_results['error'],
449
- "traceback": test_results['traceback']
450
- }
451
-
452
- # Count successes
453
- successful_tests = sum(1 for r in test_results if r.get('success', False))
454
-
455
- return {
456
- "success": True,
457
- "total_tests": len(test_results),
458
- "successful_tests": successful_tests,
459
- "test_results": test_results
460
- }
461
-
462
- except json.JSONDecodeError:
463
- return {
464
- "success": False,
465
- "error": "Failed to parse test results",
466
- "stdout": result['stdout']
467
- }
468
-
469
- except Exception as e:
470
- return {"error": f"Error testing Python function: {str(e)}"}