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.
- package/.dockerignore +53 -0
- package/.gcloudignore +49 -0
- package/CONTRIBUTING.md +92 -0
- package/Dockerfile +63 -0
- package/LICENSE +21 -0
- package/README.md +248 -102
- package/cloud-run-deploy.yaml +135 -0
- package/cloud-run.yaml +135 -0
- package/cloud-run.yaml.template +143 -0
- package/deploy.sh +107 -0
- package/dist/core/agent-spawner.d.ts +89 -0
- package/dist/core/agent-spawner.d.ts.map +1 -0
- package/dist/core/agent-spawner.js +195 -0
- package/dist/core/agent-spawner.js.map +1 -0
- package/dist/core/client-agent.d.ts +82 -0
- package/dist/core/client-agent.d.ts.map +1 -0
- package/dist/core/client-agent.js +406 -0
- package/dist/core/client-agent.js.map +1 -0
- package/dist/core/config.d.ts +59 -10
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +19 -2
- package/dist/core/config.js.map +1 -1
- package/dist/core/conversation-manager.d.ts +10 -18
- package/dist/core/conversation-manager.d.ts.map +1 -1
- package/dist/core/conversation-manager.js +28 -60
- package/dist/core/conversation-manager.js.map +1 -1
- package/dist/core/dual-agent.d.ts +24 -3
- package/dist/core/dual-agent.d.ts.map +1 -1
- package/dist/core/dual-agent.js +112 -19
- package/dist/core/dual-agent.js.map +1 -1
- package/dist/core/manager-agent.d.ts +3 -1
- package/dist/core/manager-agent.d.ts.map +1 -1
- package/dist/core/manager-agent.js +66 -14
- package/dist/core/manager-agent.js.map +1 -1
- package/dist/core/worker-agent.d.ts +15 -1
- package/dist/core/worker-agent.d.ts.map +1 -1
- package/dist/core/worker-agent.js +244 -11
- package/dist/core/worker-agent.js.map +1 -1
- package/dist/core/workspace.d.ts +5 -0
- package/dist/core/workspace.d.ts.map +1 -1
- package/dist/core/workspace.js +47 -7
- package/dist/core/workspace.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/interfaces/cli/index.js +376 -44
- package/dist/interfaces/cli/index.js.map +1 -1
- package/dist/interfaces/cli/repl.d.ts.map +1 -1
- package/dist/interfaces/cli/repl.js +6 -0
- package/dist/interfaces/cli/repl.js.map +1 -1
- package/dist/interfaces/http/index.d.ts +22 -0
- package/dist/interfaces/http/index.d.ts.map +1 -0
- package/dist/interfaces/http/index.js +135 -0
- package/dist/interfaces/http/index.js.map +1 -0
- package/dist/interfaces/http/middleware/auth.d.ts +32 -0
- package/dist/interfaces/http/middleware/auth.d.ts.map +1 -0
- package/dist/interfaces/http/middleware/auth.js +176 -0
- package/dist/interfaces/http/middleware/auth.js.map +1 -0
- package/dist/interfaces/http/routes/chat.d.ts +7 -0
- package/dist/interfaces/http/routes/chat.d.ts.map +1 -0
- package/dist/interfaces/http/routes/chat.js +144 -0
- package/dist/interfaces/http/routes/chat.js.map +1 -0
- package/dist/interfaces/http/routes/health.d.ts +6 -0
- package/dist/interfaces/http/routes/health.d.ts.map +1 -0
- package/dist/interfaces/http/routes/health.js +25 -0
- package/dist/interfaces/http/routes/health.js.map +1 -0
- package/dist/interfaces/http/routes/session.d.ts +7 -0
- package/dist/interfaces/http/routes/session.d.ts.map +1 -0
- package/dist/interfaces/http/routes/session.js +114 -0
- package/dist/interfaces/http/routes/session.js.map +1 -0
- package/dist/interfaces/http/session-manager.d.ts +76 -0
- package/dist/interfaces/http/session-manager.d.ts.map +1 -0
- package/dist/interfaces/http/session-manager.js +339 -0
- package/dist/interfaces/http/session-manager.js.map +1 -0
- package/dist/interfaces/http/websocket-handler.d.ts +18 -0
- package/dist/interfaces/http/websocket-handler.d.ts.map +1 -0
- package/dist/interfaces/http/websocket-handler.js +146 -0
- package/dist/interfaces/http/websocket-handler.js.map +1 -0
- package/dist/mcp/client.d.ts +11 -2
- package/dist/mcp/client.d.ts.map +1 -1
- package/dist/mcp/client.js +44 -19
- package/dist/mcp/client.js.map +1 -1
- package/dist/mcp/server-manager.d.ts +1 -1
- package/dist/mcp/server-manager.d.ts.map +1 -1
- package/dist/mcp/server-manager.js +12 -2
- package/dist/mcp/server-manager.js.map +1 -1
- package/dist/personas/index.d.ts +13 -0
- package/dist/personas/index.d.ts.map +1 -0
- package/dist/personas/index.js +13 -0
- package/dist/personas/index.js.map +1 -0
- package/dist/personas/persona-loader.d.ts +30 -0
- package/dist/personas/persona-loader.d.ts.map +1 -0
- package/dist/personas/persona-loader.js +246 -0
- package/dist/personas/persona-loader.js.map +1 -0
- package/dist/personas/persona-manager.d.ts +82 -0
- package/dist/personas/persona-manager.d.ts.map +1 -0
- package/dist/personas/persona-manager.js +211 -0
- package/dist/personas/persona-manager.js.map +1 -0
- package/dist/personas/skill-loader.d.ts +35 -0
- package/dist/personas/skill-loader.d.ts.map +1 -0
- package/dist/personas/skill-loader.js +144 -0
- package/dist/personas/skill-loader.js.map +1 -0
- package/dist/personas/skill-packager.d.ts +25 -0
- package/dist/personas/skill-packager.d.ts.map +1 -0
- package/dist/personas/skill-packager.js +233 -0
- package/dist/personas/skill-packager.js.map +1 -0
- package/dist/personas/types.d.ts +134 -0
- package/dist/personas/types.d.ts.map +1 -0
- package/dist/personas/types.js +7 -0
- package/dist/personas/types.js.map +1 -0
- package/dist/personas/validator.d.ts +22 -0
- package/dist/personas/validator.d.ts.map +1 -0
- package/dist/personas/validator.js +144 -0
- package/dist/personas/validator.js.map +1 -0
- package/dist/storage/factory.d.ts +51 -0
- package/dist/storage/factory.d.ts.map +1 -0
- package/dist/storage/factory.js +154 -0
- package/dist/storage/factory.js.map +1 -0
- package/dist/storage/gcp-bucket-provider.d.ts +59 -0
- package/dist/storage/gcp-bucket-provider.d.ts.map +1 -0
- package/dist/storage/gcp-bucket-provider.js +275 -0
- package/dist/storage/gcp-bucket-provider.js.map +1 -0
- package/dist/storage/index.d.ts +33 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +37 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/local-provider.d.ts +36 -0
- package/dist/storage/local-provider.d.ts.map +1 -0
- package/dist/storage/local-provider.js +219 -0
- package/dist/storage/local-provider.js.map +1 -0
- package/dist/storage/provider.d.ts +137 -0
- package/dist/storage/provider.d.ts.map +1 -0
- package/dist/storage/provider.js +136 -0
- package/dist/storage/provider.js.map +1 -0
- package/dist/storage/types.d.ts +78 -0
- package/dist/storage/types.d.ts.map +1 -0
- package/dist/storage/types.js +14 -0
- package/dist/storage/types.js.map +1 -0
- package/dist/utils/orchestration-logger.d.ts +36 -0
- package/dist/utils/orchestration-logger.d.ts.map +1 -0
- package/dist/utils/orchestration-logger.js +224 -0
- package/dist/utils/orchestration-logger.js.map +1 -0
- package/jiva-new-demo.gif +0 -0
- package/package.json +30 -2
- package/.fluen/cache/state.json +0 -7
- package/actions/action_registry.py +0 -75
- package/actions/python_coder.py +0 -470
- package/api/main.py +0 -269
- package/downloaded_image.avif +0 -0
- package/downloads/snipping_tool.avif +0 -0
- package/image.avif +0 -0
- package/ms_image.avif +0 -0
- package/screenshot.png +0 -0
- package/snipping_tool.avif +0 -0
- package/tmp_image.avif +0 -0
package/package.json
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jiva-core",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Versatile autonomous AI agent powered by gpt-oss-120b
|
|
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
|
}
|
package/.fluen/cache/state.json
DELETED
|
@@ -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
|
package/actions/python_coder.py
DELETED
|
@@ -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)}"}
|