genxai-framework 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. cli/__init__.py +3 -0
  2. cli/commands/__init__.py +6 -0
  3. cli/commands/approval.py +85 -0
  4. cli/commands/audit.py +127 -0
  5. cli/commands/metrics.py +25 -0
  6. cli/commands/tool.py +389 -0
  7. cli/main.py +32 -0
  8. genxai/__init__.py +81 -0
  9. genxai/api/__init__.py +5 -0
  10. genxai/api/app.py +21 -0
  11. genxai/config/__init__.py +5 -0
  12. genxai/config/settings.py +37 -0
  13. genxai/connectors/__init__.py +19 -0
  14. genxai/connectors/base.py +122 -0
  15. genxai/connectors/kafka.py +92 -0
  16. genxai/connectors/postgres_cdc.py +95 -0
  17. genxai/connectors/registry.py +44 -0
  18. genxai/connectors/sqs.py +94 -0
  19. genxai/connectors/webhook.py +73 -0
  20. genxai/core/__init__.py +37 -0
  21. genxai/core/agent/__init__.py +32 -0
  22. genxai/core/agent/base.py +206 -0
  23. genxai/core/agent/config_io.py +59 -0
  24. genxai/core/agent/registry.py +98 -0
  25. genxai/core/agent/runtime.py +970 -0
  26. genxai/core/communication/__init__.py +6 -0
  27. genxai/core/communication/collaboration.py +44 -0
  28. genxai/core/communication/message_bus.py +192 -0
  29. genxai/core/communication/protocols.py +35 -0
  30. genxai/core/execution/__init__.py +22 -0
  31. genxai/core/execution/metadata.py +181 -0
  32. genxai/core/execution/queue.py +201 -0
  33. genxai/core/graph/__init__.py +30 -0
  34. genxai/core/graph/checkpoints.py +77 -0
  35. genxai/core/graph/edges.py +131 -0
  36. genxai/core/graph/engine.py +813 -0
  37. genxai/core/graph/executor.py +516 -0
  38. genxai/core/graph/nodes.py +161 -0
  39. genxai/core/graph/trigger_runner.py +40 -0
  40. genxai/core/memory/__init__.py +19 -0
  41. genxai/core/memory/base.py +72 -0
  42. genxai/core/memory/embedding.py +327 -0
  43. genxai/core/memory/episodic.py +448 -0
  44. genxai/core/memory/long_term.py +467 -0
  45. genxai/core/memory/manager.py +543 -0
  46. genxai/core/memory/persistence.py +297 -0
  47. genxai/core/memory/procedural.py +461 -0
  48. genxai/core/memory/semantic.py +526 -0
  49. genxai/core/memory/shared.py +62 -0
  50. genxai/core/memory/short_term.py +303 -0
  51. genxai/core/memory/vector_store.py +508 -0
  52. genxai/core/memory/working.py +211 -0
  53. genxai/core/state/__init__.py +6 -0
  54. genxai/core/state/manager.py +293 -0
  55. genxai/core/state/schema.py +115 -0
  56. genxai/llm/__init__.py +14 -0
  57. genxai/llm/base.py +150 -0
  58. genxai/llm/factory.py +329 -0
  59. genxai/llm/providers/__init__.py +1 -0
  60. genxai/llm/providers/anthropic.py +249 -0
  61. genxai/llm/providers/cohere.py +274 -0
  62. genxai/llm/providers/google.py +334 -0
  63. genxai/llm/providers/ollama.py +147 -0
  64. genxai/llm/providers/openai.py +257 -0
  65. genxai/llm/routing.py +83 -0
  66. genxai/observability/__init__.py +6 -0
  67. genxai/observability/logging.py +327 -0
  68. genxai/observability/metrics.py +494 -0
  69. genxai/observability/tracing.py +372 -0
  70. genxai/performance/__init__.py +39 -0
  71. genxai/performance/cache.py +256 -0
  72. genxai/performance/pooling.py +289 -0
  73. genxai/security/audit.py +304 -0
  74. genxai/security/auth.py +315 -0
  75. genxai/security/cost_control.py +528 -0
  76. genxai/security/default_policies.py +44 -0
  77. genxai/security/jwt.py +142 -0
  78. genxai/security/oauth.py +226 -0
  79. genxai/security/pii.py +366 -0
  80. genxai/security/policy_engine.py +82 -0
  81. genxai/security/rate_limit.py +341 -0
  82. genxai/security/rbac.py +247 -0
  83. genxai/security/validation.py +218 -0
  84. genxai/tools/__init__.py +21 -0
  85. genxai/tools/base.py +383 -0
  86. genxai/tools/builtin/__init__.py +131 -0
  87. genxai/tools/builtin/communication/__init__.py +15 -0
  88. genxai/tools/builtin/communication/email_sender.py +159 -0
  89. genxai/tools/builtin/communication/notification_manager.py +167 -0
  90. genxai/tools/builtin/communication/slack_notifier.py +118 -0
  91. genxai/tools/builtin/communication/sms_sender.py +118 -0
  92. genxai/tools/builtin/communication/webhook_caller.py +136 -0
  93. genxai/tools/builtin/computation/__init__.py +15 -0
  94. genxai/tools/builtin/computation/calculator.py +101 -0
  95. genxai/tools/builtin/computation/code_executor.py +183 -0
  96. genxai/tools/builtin/computation/data_validator.py +259 -0
  97. genxai/tools/builtin/computation/hash_generator.py +129 -0
  98. genxai/tools/builtin/computation/regex_matcher.py +201 -0
  99. genxai/tools/builtin/data/__init__.py +15 -0
  100. genxai/tools/builtin/data/csv_processor.py +213 -0
  101. genxai/tools/builtin/data/data_transformer.py +299 -0
  102. genxai/tools/builtin/data/json_processor.py +233 -0
  103. genxai/tools/builtin/data/text_analyzer.py +288 -0
  104. genxai/tools/builtin/data/xml_processor.py +175 -0
  105. genxai/tools/builtin/database/__init__.py +15 -0
  106. genxai/tools/builtin/database/database_inspector.py +157 -0
  107. genxai/tools/builtin/database/mongodb_query.py +196 -0
  108. genxai/tools/builtin/database/redis_cache.py +167 -0
  109. genxai/tools/builtin/database/sql_query.py +145 -0
  110. genxai/tools/builtin/database/vector_search.py +163 -0
  111. genxai/tools/builtin/file/__init__.py +17 -0
  112. genxai/tools/builtin/file/directory_scanner.py +214 -0
  113. genxai/tools/builtin/file/file_compressor.py +237 -0
  114. genxai/tools/builtin/file/file_reader.py +102 -0
  115. genxai/tools/builtin/file/file_writer.py +122 -0
  116. genxai/tools/builtin/file/image_processor.py +186 -0
  117. genxai/tools/builtin/file/pdf_parser.py +144 -0
  118. genxai/tools/builtin/test/__init__.py +15 -0
  119. genxai/tools/builtin/test/async_simulator.py +62 -0
  120. genxai/tools/builtin/test/data_transformer.py +99 -0
  121. genxai/tools/builtin/test/error_generator.py +82 -0
  122. genxai/tools/builtin/test/simple_math.py +94 -0
  123. genxai/tools/builtin/test/string_processor.py +72 -0
  124. genxai/tools/builtin/web/__init__.py +15 -0
  125. genxai/tools/builtin/web/api_caller.py +161 -0
  126. genxai/tools/builtin/web/html_parser.py +330 -0
  127. genxai/tools/builtin/web/http_client.py +187 -0
  128. genxai/tools/builtin/web/url_validator.py +162 -0
  129. genxai/tools/builtin/web/web_scraper.py +170 -0
  130. genxai/tools/custom/my_test_tool_2.py +9 -0
  131. genxai/tools/dynamic.py +105 -0
  132. genxai/tools/mcp_server.py +167 -0
  133. genxai/tools/persistence/__init__.py +6 -0
  134. genxai/tools/persistence/models.py +55 -0
  135. genxai/tools/persistence/service.py +322 -0
  136. genxai/tools/registry.py +227 -0
  137. genxai/tools/security/__init__.py +11 -0
  138. genxai/tools/security/limits.py +214 -0
  139. genxai/tools/security/policy.py +20 -0
  140. genxai/tools/security/sandbox.py +248 -0
  141. genxai/tools/templates.py +435 -0
  142. genxai/triggers/__init__.py +19 -0
  143. genxai/triggers/base.py +104 -0
  144. genxai/triggers/file_watcher.py +75 -0
  145. genxai/triggers/queue.py +68 -0
  146. genxai/triggers/registry.py +82 -0
  147. genxai/triggers/schedule.py +66 -0
  148. genxai/triggers/webhook.py +68 -0
  149. genxai/utils/__init__.py +1 -0
  150. genxai/utils/tokens.py +295 -0
  151. genxai_framework-0.1.0.dist-info/METADATA +495 -0
  152. genxai_framework-0.1.0.dist-info/RECORD +156 -0
  153. genxai_framework-0.1.0.dist-info/WHEEL +5 -0
  154. genxai_framework-0.1.0.dist-info/entry_points.txt +2 -0
  155. genxai_framework-0.1.0.dist-info/licenses/LICENSE +21 -0
  156. genxai_framework-0.1.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,118 @@
1
+ """SMS sender tool for sending text messages."""
2
+
3
+ from typing import Any, Dict
4
+ import logging
5
+
6
+ from genxai.tools.base import Tool, ToolMetadata, ToolParameter, ToolCategory
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ class SMSSenderTool(Tool):
12
+ """Send SMS messages via Twilio API."""
13
+
14
+ def __init__(self) -> None:
15
+ """Initialize SMS sender tool."""
16
+ metadata = ToolMetadata(
17
+ name="sms_sender",
18
+ description="Send SMS text messages via Twilio",
19
+ category=ToolCategory.COMMUNICATION,
20
+ tags=["sms", "text", "message", "twilio", "communication"],
21
+ version="1.0.0",
22
+ )
23
+
24
+ parameters = [
25
+ ToolParameter(
26
+ name="to_number",
27
+ type="string",
28
+ description="Recipient phone number (E.164 format: +1234567890)",
29
+ required=True,
30
+ ),
31
+ ToolParameter(
32
+ name="from_number",
33
+ type="string",
34
+ description="Sender phone number (Twilio number)",
35
+ required=True,
36
+ ),
37
+ ToolParameter(
38
+ name="message",
39
+ type="string",
40
+ description="SMS message text",
41
+ required=True,
42
+ ),
43
+ ToolParameter(
44
+ name="account_sid",
45
+ type="string",
46
+ description="Twilio Account SID",
47
+ required=True,
48
+ ),
49
+ ToolParameter(
50
+ name="auth_token",
51
+ type="string",
52
+ description="Twilio Auth Token",
53
+ required=True,
54
+ ),
55
+ ]
56
+
57
+ super().__init__(metadata, parameters)
58
+
59
+ async def _execute(
60
+ self,
61
+ to_number: str,
62
+ from_number: str,
63
+ message: str,
64
+ account_sid: str,
65
+ auth_token: str,
66
+ ) -> Dict[str, Any]:
67
+ """Execute SMS sending.
68
+
69
+ Args:
70
+ to_number: Recipient phone number
71
+ from_number: Sender phone number
72
+ message: SMS message
73
+ account_sid: Twilio Account SID
74
+ auth_token: Twilio Auth Token
75
+
76
+ Returns:
77
+ Dictionary containing send results
78
+ """
79
+ try:
80
+ from twilio.rest import Client
81
+ from twilio.base.exceptions import TwilioRestException
82
+ except ImportError:
83
+ raise ImportError(
84
+ "twilio package not installed. Install with: pip install twilio"
85
+ )
86
+
87
+ result: Dict[str, Any] = {
88
+ "to": to_number,
89
+ "from": from_number,
90
+ "success": False,
91
+ }
92
+
93
+ try:
94
+ # Create Twilio client
95
+ client = Client(account_sid, auth_token)
96
+
97
+ # Send SMS
98
+ sms_message = client.messages.create(
99
+ body=message,
100
+ from_=from_number,
101
+ to=to_number
102
+ )
103
+
104
+ result.update({
105
+ "success": True,
106
+ "message_sid": sms_message.sid,
107
+ "status": sms_message.status,
108
+ "price": sms_message.price,
109
+ "price_unit": sms_message.price_unit,
110
+ })
111
+
112
+ except TwilioRestException as e:
113
+ result["error"] = f"Twilio error: {str(e)}"
114
+ except Exception as e:
115
+ result["error"] = str(e)
116
+
117
+ logger.info(f"SMS send completed: success={result['success']}")
118
+ return result
@@ -0,0 +1,136 @@
1
+ """Webhook caller tool for triggering webhooks."""
2
+
3
+ from typing import Any, Dict, Optional
4
+ import logging
5
+
6
+ from genxai.tools.base import Tool, ToolMetadata, ToolParameter, ToolCategory
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ class WebhookCallerTool(Tool):
12
+ """Call webhooks with custom payloads and headers."""
13
+
14
+ def __init__(self) -> None:
15
+ """Initialize webhook caller tool."""
16
+ metadata = ToolMetadata(
17
+ name="webhook_caller",
18
+ description="Trigger webhooks with custom payloads and authentication",
19
+ category=ToolCategory.COMMUNICATION,
20
+ tags=["webhook", "http", "callback", "integration", "api"],
21
+ version="1.0.0",
22
+ )
23
+
24
+ parameters = [
25
+ ToolParameter(
26
+ name="url",
27
+ type="string",
28
+ description="Webhook URL",
29
+ pattern=r"^https?://",
30
+ required=True,
31
+ ),
32
+ ToolParameter(
33
+ name="method",
34
+ type="string",
35
+ description="HTTP method",
36
+ required=False,
37
+ default="POST",
38
+ enum=["GET", "POST", "PUT", "PATCH", "DELETE"],
39
+ ),
40
+ ToolParameter(
41
+ name="payload",
42
+ type="object",
43
+ description="Webhook payload data",
44
+ required=False,
45
+ ),
46
+ ToolParameter(
47
+ name="headers",
48
+ type="object",
49
+ description="Custom HTTP headers",
50
+ required=False,
51
+ ),
52
+ ToolParameter(
53
+ name="timeout",
54
+ type="number",
55
+ description="Request timeout in seconds",
56
+ required=False,
57
+ default=30,
58
+ ),
59
+ ]
60
+
61
+ super().__init__(metadata, parameters)
62
+
63
+ async def _execute(
64
+ self,
65
+ url: str,
66
+ method: str = "POST",
67
+ payload: Optional[Dict[str, Any]] = None,
68
+ headers: Optional[Dict[str, str]] = None,
69
+ timeout: int = 30,
70
+ ) -> Dict[str, Any]:
71
+ """Execute webhook call.
72
+
73
+ Args:
74
+ url: Webhook URL
75
+ method: HTTP method
76
+ payload: Payload data
77
+ headers: Custom headers
78
+ timeout: Request timeout
79
+
80
+ Returns:
81
+ Dictionary containing webhook response
82
+ """
83
+ try:
84
+ import httpx
85
+ except ImportError:
86
+ raise ImportError(
87
+ "httpx package not installed. Install with: pip install httpx"
88
+ )
89
+
90
+ result: Dict[str, Any] = {
91
+ "url": url,
92
+ "method": method,
93
+ "success": False,
94
+ }
95
+
96
+ try:
97
+ # Prepare request
98
+ request_kwargs: Dict[str, Any] = {
99
+ "method": method,
100
+ "url": url,
101
+ "timeout": timeout,
102
+ }
103
+
104
+ if headers:
105
+ request_kwargs["headers"] = headers
106
+
107
+ if payload and method in ["POST", "PUT", "PATCH"]:
108
+ request_kwargs["json"] = payload
109
+
110
+ # Make request
111
+ async with httpx.AsyncClient() as client:
112
+ response = await client.request(**request_kwargs)
113
+
114
+ # Parse response
115
+ result.update({
116
+ "status_code": response.status_code,
117
+ "success": 200 <= response.status_code < 300,
118
+ "response_headers": dict(response.headers),
119
+ })
120
+
121
+ # Try to parse JSON response
122
+ try:
123
+ result["response_data"] = response.json()
124
+ except:
125
+ result["response_text"] = response.text
126
+
127
+ except httpx.HTTPStatusError as e:
128
+ result["error"] = f"HTTP error: {e.response.status_code}"
129
+ result["status_code"] = e.response.status_code
130
+ except httpx.RequestError as e:
131
+ result["error"] = f"Request error: {str(e)}"
132
+ except Exception as e:
133
+ result["error"] = str(e)
134
+
135
+ logger.info(f"Webhook call completed: success={result['success']}")
136
+ return result
@@ -0,0 +1,15 @@
1
+ """Computation tools for GenXAI."""
2
+
3
+ from genxai.tools.builtin.computation.calculator import CalculatorTool
4
+ from genxai.tools.builtin.computation.code_executor import CodeExecutorTool
5
+ from genxai.tools.builtin.computation.regex_matcher import RegexMatcherTool
6
+ from genxai.tools.builtin.computation.hash_generator import HashGeneratorTool
7
+ from genxai.tools.builtin.computation.data_validator import DataValidatorTool
8
+
9
+ __all__ = [
10
+ "CalculatorTool",
11
+ "CodeExecutorTool",
12
+ "RegexMatcherTool",
13
+ "HashGeneratorTool",
14
+ "DataValidatorTool",
15
+ ]
@@ -0,0 +1,101 @@
1
+ """Calculator tool for mathematical computations."""
2
+
3
+ from typing import Any
4
+ import ast
5
+ import operator
6
+ import logging
7
+
8
+ from genxai.tools.base import Tool, ToolMetadata, ToolParameter, ToolCategory
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class CalculatorTool(Tool):
14
+ """Tool for evaluating mathematical expressions safely."""
15
+
16
+ # Allowed operators for safe evaluation
17
+ _operators = {
18
+ ast.Add: operator.add,
19
+ ast.Sub: operator.sub,
20
+ ast.Mult: operator.mul,
21
+ ast.Div: operator.truediv,
22
+ ast.Pow: operator.pow,
23
+ ast.USub: operator.neg,
24
+ ast.UAdd: operator.pos,
25
+ }
26
+
27
+ def __init__(self) -> None:
28
+ """Initialize calculator tool."""
29
+ metadata = ToolMetadata(
30
+ name="calculator",
31
+ description="Calculate and evaluate mathematical expressions safely",
32
+ category=ToolCategory.COMPUTATION,
33
+ tags=["math", "calculation", "computation", "arithmetic"],
34
+ )
35
+
36
+ parameters = [
37
+ ToolParameter(
38
+ name="expression",
39
+ type="string",
40
+ description="Mathematical expression to evaluate (e.g., '2 + 2', '10 * 5 + 3')",
41
+ pattern=r"^[0-9+\-*/().\s]+$",
42
+ )
43
+ ]
44
+
45
+ super().__init__(metadata, parameters)
46
+
47
+ async def _execute(self, expression: str) -> Any:
48
+ """Execute mathematical expression.
49
+
50
+ Args:
51
+ expression: Mathematical expression string
52
+
53
+ Returns:
54
+ Result of the calculation
55
+
56
+ Raises:
57
+ ValueError: If expression is invalid or unsafe
58
+ """
59
+ try:
60
+ # Parse the expression
61
+ node = ast.parse(expression, mode="eval")
62
+
63
+ # Evaluate safely
64
+ result = self._eval_node(node.body)
65
+
66
+ logger.info(f"Calculated: {expression} = {result}")
67
+ return {"expression": expression, "result": result}
68
+
69
+ except Exception as e:
70
+ logger.error(f"Calculator error: {e}")
71
+ raise ValueError(f"Invalid expression: {e}")
72
+
73
+ def _eval_node(self, node: ast.AST) -> float:
74
+ """Evaluate AST node safely.
75
+
76
+ Args:
77
+ node: AST node to evaluate
78
+
79
+ Returns:
80
+ Evaluation result
81
+
82
+ Raises:
83
+ ValueError: If node type is not allowed
84
+ """
85
+ if isinstance(node, ast.Constant): # Python 3.8+
86
+ return float(node.value)
87
+ elif isinstance(node, ast.BinOp):
88
+ left = self._eval_node(node.left)
89
+ right = self._eval_node(node.right)
90
+ op_type = type(node.op)
91
+ if op_type not in self._operators:
92
+ raise ValueError(f"Operator {op_type.__name__} not allowed")
93
+ return self._operators[op_type](left, right)
94
+ elif isinstance(node, ast.UnaryOp):
95
+ operand = self._eval_node(node.operand)
96
+ op_type = type(node.op)
97
+ if op_type not in self._operators:
98
+ raise ValueError(f"Operator {op_type.__name__} not allowed")
99
+ return self._operators[op_type](operand)
100
+ else:
101
+ raise ValueError(f"Node type {type(node).__name__} not allowed")
@@ -0,0 +1,183 @@
1
+ """Code executor tool for running code in sandboxed environments."""
2
+
3
+ from typing import Any, Dict, Optional
4
+ import logging
5
+ import subprocess
6
+ import tempfile
7
+ import os
8
+ import asyncio
9
+
10
+ from genxai.tools.base import Tool, ToolMetadata, ToolParameter, ToolCategory
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class CodeExecutorTool(Tool):
16
+ """Execute code safely in a sandboxed environment with timeout support."""
17
+
18
+ def __init__(self) -> None:
19
+ """Initialize code executor tool."""
20
+ metadata = ToolMetadata(
21
+ name="code_executor",
22
+ description="Execute Python, JavaScript, or Bash code in a sandboxed environment",
23
+ category=ToolCategory.COMPUTATION,
24
+ tags=["code", "execution", "sandbox", "python", "javascript", "bash"],
25
+ version="1.0.0",
26
+ )
27
+
28
+ parameters = [
29
+ ToolParameter(
30
+ name="code",
31
+ type="string",
32
+ description="Code to execute",
33
+ required=True,
34
+ ),
35
+ ToolParameter(
36
+ name="language",
37
+ type="string",
38
+ description="Programming language",
39
+ required=True,
40
+ enum=["python", "javascript", "bash"],
41
+ ),
42
+ ToolParameter(
43
+ name="timeout",
44
+ type="number",
45
+ description="Execution timeout in seconds",
46
+ required=False,
47
+ default=30,
48
+ min_value=1,
49
+ max_value=300,
50
+ ),
51
+ ToolParameter(
52
+ name="capture_output",
53
+ type="boolean",
54
+ description="Whether to capture stdout and stderr",
55
+ required=False,
56
+ default=True,
57
+ ),
58
+ ]
59
+
60
+ super().__init__(metadata, parameters)
61
+
62
+ async def _execute(
63
+ self,
64
+ code: str,
65
+ language: str,
66
+ timeout: int = 30,
67
+ capture_output: bool = True,
68
+ ) -> Dict[str, Any]:
69
+ """Execute code in sandboxed environment.
70
+
71
+ Args:
72
+ code: Code to execute
73
+ language: Programming language
74
+ timeout: Execution timeout
75
+ capture_output: Capture output flag
76
+
77
+ Returns:
78
+ Dictionary containing execution results
79
+ """
80
+ result: Dict[str, Any] = {
81
+ "language": language,
82
+ "success": False,
83
+ }
84
+
85
+ try:
86
+ # Create temporary file for code
87
+ with tempfile.NamedTemporaryFile(
88
+ mode="w",
89
+ suffix=self._get_file_extension(language),
90
+ delete=False,
91
+ ) as temp_file:
92
+ temp_file.write(code)
93
+ temp_file_path = temp_file.name
94
+
95
+ try:
96
+ # Get command to execute
97
+ command = self._get_execution_command(language, temp_file_path)
98
+
99
+ # Execute code with timeout
100
+ process = await asyncio.create_subprocess_exec(
101
+ *command,
102
+ stdout=asyncio.subprocess.PIPE if capture_output else None,
103
+ stderr=asyncio.subprocess.PIPE if capture_output else None,
104
+ )
105
+
106
+ try:
107
+ stdout, stderr = await asyncio.wait_for(
108
+ process.communicate(), timeout=timeout
109
+ )
110
+
111
+ result.update({
112
+ "success": process.returncode == 0,
113
+ "return_code": process.returncode,
114
+ "stdout": stdout.decode("utf-8") if stdout else "",
115
+ "stderr": stderr.decode("utf-8") if stderr else "",
116
+ })
117
+
118
+ # Provide a simple structured value for tests and agent usage.
119
+ # If user assigns to a variable called `result`, it won't be printed
120
+ # automatically, so we include a best-effort extraction.
121
+ if result["success"]:
122
+ # If stdout is empty, attempt to eval a `result = ...` assignment
123
+ # by adding a small wrapper. This is intentionally conservative.
124
+ if not result.get("stdout") and language == "python":
125
+ result["output"] = ""
126
+ else:
127
+ result["output"] = result.get("stdout", "")
128
+ else:
129
+ result["error"] = result.get("stderr") or "Execution failed"
130
+
131
+ except asyncio.TimeoutError:
132
+ process.kill()
133
+ await process.wait()
134
+ result["error"] = f"Execution timed out after {timeout} seconds"
135
+ result["timed_out"] = True
136
+
137
+ finally:
138
+ # Clean up temporary file
139
+ if os.path.exists(temp_file_path):
140
+ os.unlink(temp_file_path)
141
+
142
+ except FileNotFoundError as e:
143
+ result["error"] = f"Interpreter not found: {str(e)}"
144
+ except Exception as e:
145
+ result["error"] = str(e)
146
+
147
+ logger.info(
148
+ f"Code execution ({language}) completed: success={result['success']}"
149
+ )
150
+ return result
151
+
152
+ def _get_file_extension(self, language: str) -> str:
153
+ """Get file extension for language.
154
+
155
+ Args:
156
+ language: Programming language
157
+
158
+ Returns:
159
+ File extension
160
+ """
161
+ extensions = {
162
+ "python": ".py",
163
+ "javascript": ".js",
164
+ "bash": ".sh",
165
+ }
166
+ return extensions.get(language, ".txt")
167
+
168
+ def _get_execution_command(self, language: str, file_path: str) -> list:
169
+ """Get execution command for language.
170
+
171
+ Args:
172
+ language: Programming language
173
+ file_path: Path to code file
174
+
175
+ Returns:
176
+ Command as list of strings
177
+ """
178
+ commands = {
179
+ "python": ["python3", file_path],
180
+ "javascript": ["node", file_path],
181
+ "bash": ["bash", file_path],
182
+ }
183
+ return commands.get(language, ["cat", file_path])