| import os | |
| import sys | |
| import subprocess | |
| import requests | |
| import argparse | |
| from bs4 import BeautifulSoup | |
| from typing import Annotated, TypedDict | |
| from langchain_openai import ChatOpenAI | |
| from langchain_core.messages import AnyMessage, HumanMessage, AIMessage, ToolMessage, SystemMessage | |
| from langchain.tools import tool | |
| from langgraph.graph import StateGraph, END | |
| from langgraph.prebuilt import ToolNode | |
| from duckduckgo_search import DDGS | |
| # Globals for arguments and secure confirmations | |
| agent_args = None | |
| def confirm_tool(name: str, args_str: str) -> bool: | |
| global agent_args | |
| if agent_args and agent_args.confirm: | |
| console.print(f"\n[warning][Confirm Tool][/warning] Agent wants to execute [bold cyan]{name}[/bold cyan] with args: {args_str}") | |
| ans = console.input("[user]Allow execution? [y/N][/user] > ") | |
| return ans.strip().lower() == "y" | |
| return True | |
| def load_session_messages(path: str, clear: bool) -> list: | |
| if clear: | |
| return [] | |
| if not os.path.exists(path): | |
| return [] | |
| try: | |
| import json | |
| with open(path, "r", encoding="utf-8") as f: | |
| data = json.load(f) | |
| msgs = [] | |
| for m in data: | |
| role = m.get("role") | |
| content = m.get("content") or "" | |
| if role == "system": | |
| msgs.append(SystemMessage(content=content)) | |
| elif role == "user": | |
| msgs.append(HumanMessage(content=content)) | |
| elif role == "assistant": | |
| t_calls = [] | |
| for tc in m.get("tool_calls") or []: | |
| t_calls.append({ | |
| "id": tc.get("id"), | |
| "type": "function", | |
| "function": { | |
| "name": tc.get("function", {}).get("name"), | |
| "arguments": tc.get("function", {}).get("arguments") | |
| } | |
| }) | |
| msgs.append(AIMessage(content=content, tool_calls=t_calls)) | |
| elif role == "tool": | |
| msgs.append(ToolMessage( | |
| content=content, | |
| tool_call_id=m.get("tool_call_id"), | |
| name=m.get("name") | |
| )) | |
| return msgs | |
| except Exception as e: | |
| console.print(f"[warning]Failed to load session: {e}[/warning]") | |
| return [] | |
| def save_session_messages(path: str, msgs: list): | |
| try: | |
| import json | |
| serialized = [] | |
| for m in msgs: | |
| role = "user" | |
| if isinstance(m, SystemMessage): | |
| role = "system" | |
| elif isinstance(m, HumanMessage): | |
| role = "user" | |
| elif isinstance(m, AIMessage): | |
| role = "assistant" | |
| elif isinstance(m, ToolMessage): | |
| role = "tool" | |
| msg_dict = {"role": role, "content": m.content} | |
| if role == "tool": | |
| msg_dict["tool_call_id"] = m.tool_call_id | |
| msg_dict["name"] = m.name | |
| elif role == "assistant" and hasattr(m, "tool_calls") and m.tool_calls: | |
| t_calls = [] | |
| for tc in m.tool_calls: | |
| t_calls.append({ | |
| "id": tc.get("id"), | |
| "type": "function", | |
| "function": { | |
| "name": tc.get("function", {}).get("name"), | |
| "arguments": tc.get("function", {}).get("arguments") | |
| } | |
| }) | |
| msg_dict["tool_calls"] = t_calls | |
| serialized.append(msg_dict) | |
| with open(path, "w", encoding="utf-8") as f: | |
| json.dump(serialized, f, indent=2) | |
| except Exception as e: | |
| console.print(f"[warning]Failed to save session: {e}[/warning]") | |
| # Rich printing setup | |
| from rich.console import Console | |
| from rich.panel import Panel | |
| from rich.markdown import Markdown | |
| from rich.theme import Theme | |
| from rich.progress import Progress, SpinnerColumn, TextColumn | |
| custom_theme = Theme({ | |
| "info": "dim cyan", | |
| "warning": "magenta", | |
| "danger": "bold red", | |
| "success": "green", | |
| "agent": "bold purple", | |
| "tool": "bold yellow", | |
| "user": "bold cyan" | |
| }) | |
| console = Console(theme=custom_theme) | |
| # Define State | |
| class AgentState(TypedDict): | |
| messages: Annotated[list[AnyMessage], "The conversation history"] | |
| # ===================================================================== | |
| # POWERFUL AUTONOMOUS TOOL LIBRARY | |
| # ===================================================================== | |
| def read_file(file_path: str) -> str: | |
| """Reads the contents of a local file in the project directory.""" | |
| try: | |
| with open(file_path, "r", encoding="utf-8") as f: | |
| return f.read() | |
| except Exception as e: | |
| return f"Error reading file: {e}" | |
| def write_file(file_path: str, content: str) -> str: | |
| """Writes content to a file (creates directories if missing, overwrites if existing).""" | |
| if not confirm_tool("write_file", f"file_path={file_path}"): | |
| return "Error: User denied execution of this tool." | |
| try: | |
| os.makedirs(os.path.dirname(file_path), exist_ok=True) | |
| with open(file_path, "w", encoding="utf-8") as f: | |
| f.write(content) | |
| return f"Successfully wrote to {file_path}" | |
| except Exception as e: | |
| return f"Error writing file: {e}" | |
| def modify_file_content(file_path: str, target_text: str, replacement_text: str) -> str: | |
| """Replaces a targeted block of text inside a file. Use this for precise edits.""" | |
| if not confirm_tool("modify_file_content", f"file_path={file_path}"): | |
| return "Error: User denied execution of this tool." | |
| try: | |
| with open(file_path, "r", encoding="utf-8") as f: | |
| content = f.read() | |
| if target_text not in content: | |
| return f"Error: The target text to replace was not found in the file {file_path}." | |
| updated_content = content.replace(target_text, replacement_text, 1) | |
| with open(file_path, "w", encoding="utf-8") as f: | |
| f.write(updated_content) | |
| return f"Successfully updated content in {file_path}" | |
| except Exception as e: | |
| return f"Error modifying file: {e}" | |
| def list_directory(dir_path: str = ".") -> str: | |
| """Lists files and folders inside a given directory path.""" | |
| try: | |
| entries = os.listdir(dir_path) | |
| return "\n".join(entries) if entries else "Directory is empty." | |
| except Exception as e: | |
| return f"Error listing directory: {e}" | |
| def get_project_tree(root_path: str = ".") -> str: | |
| """Recursively returns a visual tree structure of directories and files, ignoring build artifacts.""" | |
| ignored = {".git", "node_modules", "target", ".gradle", "bin", ".idea", "dist", "build"} | |
| tree_lines = [] | |
| def walk(path: str, prefix: str = ""): | |
| try: | |
| entries = sorted(os.listdir(path)) | |
| except Exception: | |
| return | |
| entries = [e for e in entries if e not in ignored] | |
| for i, entry in enumerate(entries): | |
| is_last = (i == len(entries) - 1) | |
| connector = "โโโ " if is_last else "โโโ " | |
| full_path = os.path.join(path, entry) | |
| tree_lines.append(f"{prefix}{connector}{entry}") | |
| if os.path.isdir(full_path): | |
| new_prefix = prefix + (" " if is_last else "โ ") | |
| walk(full_path, new_prefix) | |
| tree_lines.append(root_path) | |
| walk(root_path) | |
| return "\n".join(tree_lines) | |
| def search_in_files(query: str, path: str = ".") -> str: | |
| """Grep-like tool. Searches files recursively for a string or regex pattern.""" | |
| results = [] | |
| ignored_dirs = {".git", "node_modules", "target", ".gradle", "bin", ".idea", "dist", "build"} | |
| for root, dirs, files in os.walk(path): | |
| dirs[:] = [d for d in dirs if d not in ignored_dirs] | |
| for file in files: | |
| file_path = os.path.join(root, file) | |
| try: | |
| with open(file_path, "r", encoding="utf-8", errors="ignore") as f: | |
| for line_num, line in enumerate(f, 1): | |
| if query in line: | |
| results.append(f"{file_path}:{line_num}: {line.strip()}") | |
| if len(results) >= 50: | |
| return "\n".join(results) + "\n\n(Truncated to 50 results)" | |
| except Exception: | |
| pass | |
| return "\n".join(results) if results else "No matches found." | |
| def search_web(query: str) -> str: | |
| """Searches the web using DuckDuckGo to find up-to-date documentation, GitHub repos, or coding help.""" | |
| try: | |
| results = DDGS().text(query, max_results=5) | |
| formatted = [] | |
| for r in results: | |
| formatted.append(f"Title: {r['title']}\nURL: {r['href']}\nSnippet: {r['body']}\n---") | |
| return "\n".join(formatted) | |
| except Exception as e: | |
| return f"Error searching web: {e}" | |
| def get_webpage_content(url: str) -> str: | |
| """Downloads a webpage and extracts clean, human-readable text.""" | |
| try: | |
| headers = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)"} | |
| resp = requests.get(url, headers=headers, timeout=10) | |
| resp.raise_for_status() | |
| soup = BeautifulSoup(resp.text, 'html.parser') | |
| # Remove script/style | |
| for script in soup(["script", "style"]): | |
| script.decompose() | |
| text = soup.get_text(separator="\n") | |
| # clean whitespace | |
| lines = (line.strip() for line in text.splitlines()) | |
| chunks = (phrase for line in lines for phrase in line.split(" ")) | |
| text = "\n".join(chunk for chunk in chunks if chunk) | |
| # limit to first 10000 chars to avoid token explosion | |
| return text[:10000] + ("\n\n(Truncated...)" if len(text) > 10000 else "") | |
| except Exception as e: | |
| return f"Error fetching webpage: {e}" | |
| def run_command(command: str) -> str: | |
| """Runs a shell command (e.g. build, tests, check linting) and returns the output.""" | |
| if not confirm_tool("run_command", command): | |
| return "Error: User denied execution of this tool." | |
| try: | |
| result = subprocess.run(command, shell=True, capture_output=True, text=True, timeout=60) | |
| output = f"Exit Code: {result.returncode}\n\nSTDOUT:\n{result.stdout}" | |
| if result.stderr: | |
| output += f"\n\nSTDERR:\n{result.stderr}" | |
| return output | |
| except subprocess.TimeoutExpired: | |
| return "Error: Command timed out after 60 seconds." | |
| except Exception as e: | |
| return f"Error running command: {e}" | |
| def http_request(method: str, url: str, headers: dict = None, json_body: dict = None) -> str: | |
| """Makes a network HTTP request (GET, POST, etc.) to query APIs or local development servers.""" | |
| try: | |
| resp = requests.request(method=method, url=url, headers=headers, json=json_body, timeout=15) | |
| return f"Status Code: {resp.status_code}\nResponse:\n{resp.text}" | |
| except Exception as e: | |
| return f"Network request failed: {e}" | |
| def deploy_tool(directory_path: str = ".") -> str: | |
| """Deploys the tool in the current directory to the server using the CLI's deploy function.""" | |
| if not confirm_tool("deploy_tool", f"cwd={directory_path}"): | |
| return "Error: User denied execution of this tool." | |
| try: | |
| # Check if Makefile or Go binary exists to trigger deployment | |
| # We can execute the compiled ie-cli binary directly | |
| cmd = "ie-cli deploy" | |
| res = subprocess.run(cmd, shell=True, capture_output=True, text=True, cwd=directory_path) | |
| return f"Deployment Triggered.\nExit Code: {res.returncode}\nStdout: {res.stdout}\nStderr: {res.stderr}" | |
| except Exception as e: | |
| return f"Deployment failed to run: {e}" | |
| def scaffold_tool_template(name: str, category: str) -> str: | |
| """Generate standard metadata and code files (tool.json and tool.html) based on category templates.""" | |
| try: | |
| if category == "data-visualization": | |
| tool_json = { | |
| "name": name, | |
| "version": "1.0.0", | |
| "description": "Dynamic Chart Visualization", | |
| "entrypoint": "tool.html", | |
| "parameters": [ | |
| {"name": "title", "type": "string", "label": "Chart Title", "required": True}, | |
| {"name": "values", "type": "string", "label": "Values (Comma Separated)", "required": True} | |
| ] | |
| } | |
| tool_html = """<!DOCTYPE html> | |
| <html> | |
| <head> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script> | |
| <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script> | |
| <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;800&display=swap'); | |
| body { font-family: 'Outfit', sans-serif; background-color: #0f172a; color: #f8fafc; } | |
| </style> | |
| </head> | |
| <body class="p-6"> | |
| <div id="root"></div> | |
| <script type="text/babel"> | |
| function App() { | |
| const [title, setTitle] = React.useState('Data Visualization'); | |
| const [values, setValues] = React.useState([10, 20, 30, 40]); | |
| const chartRef = React.useRef(null); | |
| const chartInstance = React.useRef(null); | |
| React.useEffect(() => { | |
| const handleMsg = (e) => { | |
| if (e.data && e.data.type === 'init') { | |
| const data = e.data.data || {}; | |
| if (data.title) setTitle(data.title); | |
| if (data.values) { | |
| const parsed = data.values.split(',').map(Number); | |
| setValues(parsed); | |
| } | |
| } | |
| }; | |
| window.addEventListener('message', handleMsg); | |
| window.parent.postMessage({ type: 'ready' }, '*'); | |
| return () => window.removeEventListener('message', handleMsg); | |
| }, []); | |
| React.useEffect(() => { | |
| if (!chartRef.current) return; | |
| if (chartInstance.current) chartInstance.current.destroy(); | |
| const ctx = chartRef.current.getContext('2d'); | |
| chartInstance.current = new Chart(ctx, { | |
| type: 'bar', | |
| data: { | |
| labels: values.map((_, i) => 'Item ' + (i + 1)), | |
| datasets: [{ | |
| label: 'Values', | |
| data: values, | |
| backgroundColor: 'rgba(56, 189, 248, 0.5)', | |
| borderColor: 'rgba(56, 189, 248, 1)', | |
| borderWidth: 1 | |
| }] | |
| }, | |
| options: { | |
| responsive: true, | |
| plugins: { | |
| legend: { labels: { color: '#94a3b8' } } | |
| }, | |
| scales: { | |
| x: { grid: { color: '#334155' }, ticks: { color: '#94a3b8' } }, | |
| y: { grid: { color: '#334155' }, ticks: { color: '#94a3b8' } } | |
| } | |
| } | |
| }); | |
| }, [title, values]); | |
| return ( | |
| <div class="max-w-xl mx-auto bg-slate-800 border border-slate-700 rounded-2xl p-6 shadow-2xl"> | |
| <h2 class="text-xl font-bold mb-4 text-sky-400">{title}</h2> | |
| <canvas ref={chartRef}></canvas> | |
| <div class="mt-4 flex justify-between items-center"> | |
| <span class="text-xs text-slate-400">Powered by React & Tailwind</span> | |
| <button | |
| onClick={() => { | |
| console.log("Submitting chart result..."); | |
| window.parent.postMessage({ type: 'submit', data: { title, values } }, '*'); | |
| }} | |
| class="bg-sky-500 hover:bg-sky-600 text-slate-950 font-bold px-4 py-2 rounded-lg text-sm transition" | |
| > | |
| Submit Result | |
| </button> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| const root = ReactDOM.createRoot(document.getElementById('root')); | |
| root.render(<App />); | |
| </script> | |
| </body> | |
| </html>""" | |
| elif category == "interactive-form": | |
| tool_json = { | |
| "name": name, | |
| "version": "1.0.0", | |
| "description": "Dynamic Feedback Form", | |
| "entrypoint": "tool.html", | |
| "parameters": [ | |
| {"name": "title", "type": "string", "label": "Form Title", "required": True}, | |
| {"name": "placeholder", "type": "string", "label": "Input Placeholder", "required": False} | |
| ] | |
| } | |
| tool_html = """<!DOCTYPE html> | |
| <html> | |
| <head> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| </head> | |
| <body class="bg-slate-50 p-6"> | |
| <div class="max-w-md mx-auto bg-white p-6 rounded-2xl shadow-sm border border-slate-100"> | |
| <h2 id="form-title" class="text-lg font-semibold text-slate-800 mb-4">Feedback Form</h2> | |
| <div class="space-y-4"> | |
| <input type="text" id="userInput" class="w-full px-3 py-2 border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Type here..."> | |
| <button onclick="submitFeedback()" class="w-full bg-blue-600 text-white py-2 rounded-lg hover:bg-blue-700 transition">Submit</button> | |
| </div> | |
| </div> | |
| <script> | |
| window.addEventListener('message', function(event) { | |
| if (event.data.type === 'init') { | |
| const payload = event.data.payload || {}; | |
| document.getElementById('form-title').innerText = payload.title || 'Form'; | |
| document.getElementById('userInput').placeholder = payload.placeholder || 'Type here...'; | |
| } | |
| }); | |
| function submitFeedback() { | |
| const val = document.getElementById('userInput').value; | |
| window.parent.postMessage({ | |
| type: 'submit', | |
| data: { value: val } | |
| }, '*'); | |
| } | |
| window.parent.postMessage({ type: 'ready' }, '*'); | |
| </script> | |
| </body> | |
| </html>""" | |
| else: | |
| tool_json = { | |
| "name": name, | |
| "version": "1.0.0", | |
| "description": "Simple mathematical operations", | |
| "entrypoint": "tool.html", | |
| "parameters": [ | |
| {"name": "operation", "type": "string", "label": "Operation (add, sub, mul, div)", "required": True} | |
| ] | |
| } | |
| tool_html = """<!DOCTYPE html> | |
| <html> | |
| <head> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| </head> | |
| <body class="p-6 bg-gray-100"> | |
| <div class="bg-white p-6 rounded-lg shadow-md max-w-sm mx-auto text-center"> | |
| <h2 class="text-xl font-bold mb-4">Smart Sandbox Calculator</h2> | |
| <div class="flex justify-center gap-4 mb-4"> | |
| <input type="number" id="num1" value="10" class="w-20 px-2 py-1 border rounded text-center"> | |
| <span id="op-symbol" class="text-xl font-bold">+</span> | |
| <input type="number" id="num2" value="5" class="w-20 px-2 py-1 border rounded text-center"> | |
| </div> | |
| <button onclick="calculate()" class="bg-emerald-500 text-white px-4 py-2 rounded hover:bg-emerald-600">Calculate</button> | |
| <div id="result" class="mt-4 text-2xl font-bold text-emerald-600">Result: 15</div> | |
| </div> | |
| <script> | |
| let op = 'add'; | |
| window.addEventListener('message', function(event) { | |
| if (event.data.type === 'init') { | |
| op = event.data.payload.operation || 'add'; | |
| const symbols = { 'add': '+', 'sub': '-', 'mul': '*', 'div': '/' }; | |
| document.getElementById('op-symbol').innerText = symbols[op] || '+'; | |
| } | |
| }); | |
| function calculate() { | |
| const n1 = parseFloat(document.getElementById('num1').value); | |
| const n2 = parseFloat(document.getElementById('num2').value); | |
| let res = 0; | |
| if (op === 'add') res = n1 + n2; | |
| else if (op === 'sub') res = n1 - n2; | |
| else if (op === 'mul') res = n1 * n2; | |
| else if (op === 'div') res = n1 / n2; | |
| document.getElementById('result').innerText = 'Result: ' + res; | |
| window.parent.postMessage({ type: 'result', value: res }, '*'); | |
| } | |
| window.parent.postMessage({ type: 'ready' }, '*'); | |
| </script> | |
| </body> | |
| </html>""" | |
| import json | |
| with open("tool.json", "w") as f: | |
| json.dump(tool_json, f, indent=2) | |
| with open("tool.html", "w") as f: | |
| f.write(tool_html) | |
| return f"Successfully scaffolded tool '{name}' with category '{category}' in current folder." | |
| except Exception as e: | |
| return f"Error scaffolding: {e}" | |
| def validate_tool_schema(path: str = "tool.json") -> str: | |
| """Perform structural and type validation on tool.json structure.""" | |
| try: | |
| import json | |
| with open(path, "r") as f: | |
| data = json.load(f) | |
| required = ["name", "version", "entrypoint", "parameters"] | |
| for r in required: | |
| if r not in data: | |
| return f"Validation failed: missing field '{r}'" | |
| return "Validation Succeeded: tool.json is well formed." | |
| except Exception as e: | |
| return f"Error validating: {e}" | |
| def lint_tool_html(path: str = "tool.html") -> str: | |
| """Audit tool.html script tags, layout, sandbox messaging protocol, and styling warnings.""" | |
| try: | |
| with open(path, "r") as f: | |
| content = f.read() | |
| warnings = [] | |
| if "window.addEventListener('message'" not in content: | |
| warnings.append("Missing iframe message listener 'window.addEventListener(\"message\", ...)'") | |
| if "window.parent.postMessage" not in content: | |
| warnings.append("Missing message post back to parent page 'window.parent.postMessage(...)'") | |
| if "cdn.tailwindcss.com" not in content and "<style>" not in content: | |
| warnings.append("Recommendation: Add TailwindCSS or styles for a beautiful layout.") | |
| return "Lint Warnings:\n- " + "\n- ".join(warnings) if warnings else "Lint Succeeded: No issues found." | |
| except Exception as e: | |
| return f"Error linting: {e}" | |
| def add_tool_parameter(path: str, name: str, paramType: str, label: str, required: bool) -> str: | |
| """Add a new configuration parameter dynamically to tool.json.""" | |
| try: | |
| import json | |
| with open(path, "r") as f: | |
| data = json.load(f) | |
| params = data.get("parameters", []) | |
| params.append({ | |
| "name": name, | |
| "type": paramType, | |
| "label": label, | |
| "required": required | |
| }) | |
| data["parameters"] = params | |
| with open(path, "w") as f: | |
| json.dump(data, f, indent=2) | |
| return f"Successfully added parameter '{name}' to '{path}'" | |
| except Exception as e: | |
| return f"Error adding parameter: {e}" | |
| def inject_javascript_library(path: str, libraryName: str) -> str: | |
| """Embed a popular script/library CDN into tool.html header automatically.""" | |
| try: | |
| with open(path, "r") as f: | |
| content = f.read() | |
| injections = { | |
| "TailwindCSS": '<script src="https://cdn.tailwindcss.com"></script>', | |
| "ChartJS": '<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>', | |
| "KaTeX": '<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.css"><script src="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.js"></script>', | |
| "jQuery": '<script src="https://code.jquery.com/jquery-3.7.0.min.js"></script>', | |
| "Bootstrap": '<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">' | |
| } | |
| inj = injections.get(libraryName, "") | |
| if not inj: | |
| return f"Library {libraryName} not found." | |
| if "</head>" in content: | |
| content = content.replace("</head>", f"{inj}\n</head>", 1) | |
| elif "<body>" in content: | |
| content = content.replace("<body>", f"<body>\n{inj}", 1) | |
| else: | |
| content = f"{inj}\n{content}" | |
| with open(path, "w") as f: | |
| f.write(content) | |
| return f"Successfully injected {libraryName} script tag." | |
| except Exception as e: | |
| return f"Error injecting: {e}" | |
| def generate_mock_payload(path: str) -> str: | |
| """Produce a mock postMessage payload matching parameters defined in tool.json.""" | |
| try: | |
| import json | |
| with open(path, "r") as f: | |
| data = json.load(f) | |
| mock_data = {} | |
| for p in data.get("parameters", []): | |
| ptype = p.get("type", "string") | |
| name = p.get("name", "val") | |
| if ptype == "number": | |
| mock_data[name] = 42 | |
| elif ptype == "boolean": | |
| mock_data[name] = True | |
| elif ptype == "array": | |
| mock_data[name] = ["val1", "val2"] | |
| elif ptype == "object": | |
| mock_data[name] = {"key": "val"} | |
| else: | |
| mock_data[name] = "Mock string value" | |
| payload = { | |
| "type": "init", | |
| "payload": mock_data | |
| } | |
| return json.dumps(payload, indent=2) | |
| except Exception as e: | |
| return f"Error generating payload: {e}" | |
| def preview_tool_in_dev(path: str = ".") -> str: | |
| """Registers tool variables in the active dev server configuration for direct browser validation.""" | |
| try: | |
| cmd = "ie-cli dev" | |
| subprocess.Popen(cmd, shell=True, cwd=path) | |
| return "Preview dev server started on http://localhost:3000" | |
| except Exception as e: | |
| return f"Error launching dev: {e}" | |
| def export_tool_package(dirPath: str = ".") -> str: | |
| """Package the local sandbox tool into a standard distributable ZIP package.""" | |
| try: | |
| import zipfile | |
| zip_path = os.path.join(dirPath, "tool-package.zip") | |
| with zipfile.ZipFile(zip_path, 'w') as zipf: | |
| for file in ["tool.json", "tool.html"]: | |
| f_path = os.path.join(dirPath, file) | |
| if os.path.exists(f_path): | |
| zipf.write(f_path, arcname=file) | |
| return "Tool packaged to tool-package.zip successfully." | |
| except Exception as e: | |
| return f"Error packaging: {e}" | |
| def optimize_tool_html(path: str) -> str: | |
| """Lint, format, and minify HTML and JavaScript inside tool.html.""" | |
| try: | |
| with open(path, "r") as f: | |
| content = f.read() | |
| lines = [l for l in content.splitlines() if l.strip()] | |
| with open(path, "w") as f: | |
| f.write("\n".join(lines)) | |
| return "HTML minified and formatted successfully." | |
| except Exception as e: | |
| return f"Error optimizing: {e}" | |
| def execute_python(code: str) -> str: | |
| """Executes arbitrary Python 3 code and returns the output.""" | |
| if not confirm_tool("execute_python", "code block"): | |
| return "Error: User denied execution of this tool." | |
| try: | |
| tmp_file = "temp_agent_script.py" | |
| with open(tmp_file, "w", encoding="utf-8") as f: | |
| f.write(code) | |
| result = subprocess.run([sys.executable, tmp_file], capture_output=True, text=True, timeout=30) | |
| if os.path.exists(tmp_file): | |
| os.remove(tmp_file) | |
| output = result.stdout | |
| if result.stderr: | |
| output += f"\nSTDERR:\n{result.stderr}" | |
| return output | |
| except Exception as e: | |
| return f"Error executing Python: {e}" | |
| def execute_go(code: str) -> str: | |
| """Executes arbitrary Go code by compiling and running it, returning the output.""" | |
| if not confirm_tool("execute_go", "code block"): | |
| return "Error: User denied execution of this tool." | |
| try: | |
| trimmed_code = code.strip() | |
| if not trimmed_code.startswith("package "): | |
| code = "package main\n\nimport \"fmt\"\n\nfunc main() {\n" + code + "\n}" | |
| tmp_file = "temp_agent_script.go" | |
| with open(tmp_file, "w", encoding="utf-8") as f: | |
| f.write(code) | |
| result = subprocess.run(["go", "run", tmp_file], capture_output=True, text=True, timeout=30) | |
| if os.path.exists(tmp_file): | |
| os.remove(tmp_file) | |
| output = result.stdout | |
| if result.stderr: | |
| output += f"\nSTDERR:\n{result.stderr}" | |
| return output | |
| except Exception as e: | |
| return f"Error executing Go: {e}" | |
| def find_files_by_name(pattern: str, path: str = ".") -> str: | |
| """Finds files recursively by matching their name against a pattern (case-insensitive).""" | |
| import fnmatch | |
| ignored_dirs = {".git", "node_modules", "target", ".gradle", "bin", ".idea", "dist", "build"} | |
| results = [] | |
| for root, dirs, files in os.walk(path): | |
| dirs[:] = [d for d in dirs if d not in ignored_dirs] | |
| for file in files: | |
| if fnmatch.fnmatch(file.lower(), f"*{pattern.lower()}*"): | |
| results.append(os.path.join(root, file)) | |
| if len(results) >= 100: | |
| return "\n".join(results) + "\n\n(Truncated to 100 results)" | |
| return "\n".join(results) if results else "No matching files found." | |
| def read_file_lines(path: str, start_line: int, end_line: int) -> str: | |
| """Reads specific lines from a file (1-indexed, inclusive). Use to read parts of large files.""" | |
| try: | |
| with open(path, "r", encoding="utf-8") as f: | |
| lines = f.readlines() | |
| start = max(0, start_line - 1) | |
| end = min(len(lines), end_line) | |
| if start >= len(lines) or start > end: | |
| return "Error: Invalid line range." | |
| return "".join(lines[start:end]) | |
| except Exception as e: | |
| return f"Error reading file lines: {e}" | |
| def ask_user(question: str) -> str: | |
| """Pauses agent execution and prompts the user for clarification, feedback, or permission.""" | |
| console.print(f"\n[tool][Agent Question][/tool] {question}") | |
| user_resp = console.input("[user]You[/user] > ") | |
| return user_resp | |
| def get_tool_templates_list() -> str: | |
| """Retrieve list of all available templates supported by scaffold_tool_template.""" | |
| return "Templates List:\n1. data-visualization (TailwindCSS, Chart.js, init message bindings)\n2. interactive-form (Input submissions, postMessage bindings)\n3. calculator (Default basic tool framework)" | |
| def call_go_agent(prompt: str) -> str: | |
| """Delegates a task back to the extremely fast Go agent. Useful for quick file generations, compiling, or standard tool invocations where Go shines.""" | |
| import subprocess | |
| import sys | |
| import os | |
| session_arg = [] | |
| if "--session" in sys.argv: | |
| idx = sys.argv.index("--session") | |
| if idx + 1 < len(sys.argv): | |
| session_arg = ["--session", sys.argv[idx + 1]] | |
| bin_path = "ie-cli" | |
| if os.path.exists("./cli/ie-cli"): | |
| bin_path = "./cli/ie-cli" | |
| cmd = [bin_path, "agent", "--prompt", prompt] + session_arg | |
| console.print(f"\n[bold cyan][Python Agent][/bold cyan] Invoking Go Agent with prompt: '{prompt}'...") | |
| try: | |
| result = subprocess.run(cmd, capture_output=True, text=True, check=True) | |
| return f"Go Agent executed successfully.\nOutput:\n{result.stdout}" | |
| except subprocess.CalledProcessError as e: | |
| return f"Go Agent execution failed!\nStdout: {e.stdout}\nStderr: {e.stderr}" | |
| except Exception as e: | |
| return str(e) | |
| tools = [ | |
| call_go_agent, | |
| read_file, | |
| write_file, | |
| modify_file_content, | |
| list_directory, | |
| get_project_tree, | |
| search_in_files, | |
| search_web, | |
| get_webpage_content, | |
| run_command, | |
| http_request, | |
| deploy_tool, | |
| scaffold_tool_template, | |
| validate_tool_schema, | |
| lint_tool_html, | |
| add_tool_parameter, | |
| inject_javascript_library, | |
| generate_mock_payload, | |
| preview_tool_in_dev, | |
| export_tool_package, | |
| optimize_tool_html, | |
| get_tool_templates_list, | |
| execute_python, | |
| execute_go, | |
| find_files_by_name, | |
| read_file_lines, | |
| ask_user | |
| ] | |
| # LangGraph state machine config | |
| def call_model(state: AgentState): | |
| system_prompt = SystemMessage(content="""You are an expert autonomous developer agent specializing in sandboxed React-like widgets and components. | |
| You possess a suite of 10 powerful tool-making tools: scaffold_tool_template, validate_tool_schema, lint_tool_html, add_tool_parameter, inject_javascript_library, generate_mock_payload, preview_tool_in_dev, export_tool_package, optimize_tool_html, and get_tool_templates_list. | |
| Use these specific tools directly when the user requests generating, validating, updating, or previewing extensions or widgets. | |
| PROMPT ENGINEERING GUIDELINES FOR HTML SANDBOX CREATION (tool.html): | |
| - Treat the single HTML file like a React/Vue application. Implement a reactive state object (e.g. const state = { data: [] };) and a render() function that updates the DOM dynamically when state changes. | |
| - Always implement the sandboxed handshake protocol: | |
| 1. Listen for the 'init' postMessage event to retrieve inputs (window.addEventListener('message', (e) => { if (e.data.type === 'init') { ... } })). | |
| 2. Send a 'ready' postMessage to the parent immediately after load (window.parent.postMessage({ type: 'ready' }, '*')). | |
| 3. Send interactive responses or final values back to the parent using window.parent.postMessage({ type: 'submit' / 'result', data: ... }, '*'). | |
| - Apply modern, premium aesthetics: | |
| - Import Tailwind CSS or define beautiful custom styles. | |
| - Use elegant typography (e.g. Outfit, Inter or Roboto from Google Fonts). | |
| - Use professional color palettes (dark modes, translucent backgrounds, glassmorphism). | |
| - Add smooth CSS transitions and micro-animations for hovered elements. | |
| - Never use raw default browser inputs/buttons; style them to look modern and premium.""") | |
| messages = [system_prompt] + state["messages"] | |
| model_name = agent_args.model if agent_args else "gpt-4o-mini" | |
| model = ChatOpenAI(model=model_name, temperature=0.1) | |
| model_with_tools = model.bind_tools(tools) | |
| response = model_with_tools.invoke(messages) | |
| return {"messages": [response]} | |
| def should_continue(state: AgentState): | |
| last_message = state["messages"][-1] | |
| if hasattr(last_message, "tool_calls") and last_message.tool_calls: | |
| return "tools" | |
| return END | |
| # Setup Graph Workflow | |
| workflow = StateGraph(AgentState) | |
| workflow.add_node("agent", call_model) | |
| workflow.add_node("tools", ToolNode(tools)) | |
| workflow.set_entry_point("agent") | |
| workflow.add_conditional_edges("agent", should_continue, {"tools": "tools", END: END}) | |
| workflow.add_edge("tools", "agent") | |
| app = workflow.compile() | |
| def main(): | |
| global agent_args | |
| parser = argparse.ArgumentParser(description="MioBook Autonomous LangGraph Agent") | |
| parser.add_argument("prompt", nargs="*", help="Positional arguments for single-shot prompt") | |
| parser.add_argument("--python", "-p", action="store_true", help="Launch Python agent") | |
| parser.add_argument("--confirm", "-c", action="store_true", help="Confirm execution of side-effecting tools") | |
| parser.add_argument("--model", "-m", default="gpt-4o-mini", help="OpenAI Model to use") | |
| parser.add_argument("--list-tools", action="store_true", help="List all registered tools and exit") | |
| parser.add_argument("--export", "-e", help="Export session history to a JSON file") | |
| parser.add_argument("--session", help="Path to custom session file") | |
| parser.add_argument("--clear-session", action="store_true", help="Clear current session and start fresh") | |
| agent_args = parser.parse_args() | |
| if agent_args.list_tools: | |
| console.print(Panel("[bold purple]Registered Agent Tools[/bold purple]", border_style="purple")) | |
| for t in tools: | |
| console.print(f" โข [bold green]{t.name:25s}[/bold green]: {t.description}") | |
| return | |
| console.print(Panel.fit( | |
| "[bold purple]๐ MioBook Fully Autonomous LangGraph Agent ๐[/bold purple]\n" | |
| "[dim]Powered by LangChain & LangGraph | Full File System, Web Access & Execution[/dim]", | |
| border_style="purple" | |
| )) | |
| session_path = agent_args.session if agent_args.session else os.path.expanduser("~/.ie-research-agent-session.json") | |
| system_prompt_str = """You are an expert autonomous developer agent specializing in sandboxed React-like widgets and components. | |
| You possess a suite of 10 powerful tool-making tools: scaffold_tool_template, validate_tool_schema, lint_tool_html, add_tool_parameter, inject_javascript_library, generate_mock_payload, preview_tool_in_dev, export_tool_package, optimize_tool_html, and get_tool_templates_list. | |
| Use these specific tools directly when the user requests generating, validating, updating, or previewing extensions or widgets. | |
| PROMPT ENGINEERING GUIDELINES FOR HTML SANDBOX CREATION (tool.html): | |
| - Treat the single HTML file like a React/Vue application. Implement a reactive state object (e.g. const state = { data: [] };) and a render() function that updates the DOM dynamically when state changes. | |
| - Always implement the sandboxed handshake protocol: | |
| 1. Listen for the 'init' postMessage event to retrieve inputs (window.addEventListener('message', (e) => { if (e.data.type === 'init') { ... } })). | |
| 2. Send a 'ready' postMessage to the parent immediately after load (window.parent.postMessage({ type: 'ready' }, '*')). | |
| 3. Send interactive responses or final values back to the parent using window.parent.postMessage({ type: 'submit' / 'result', data: ... }, '*'). | |
| - Apply modern, premium aesthetics: | |
| - Import Tailwind CSS or define beautiful custom styles. | |
| - Use elegant typography (e.g. Outfit, Inter or Roboto from Google Fonts). | |
| - Use professional color palettes (dark modes, translucent backgrounds, glassmorphism). | |
| - Add smooth CSS transitions and micro-animations for hovered elements. | |
| - Never use raw default browser inputs/buttons; style them to look modern and premium.""" | |
| messages = load_session_messages(session_path, agent_args.clear_session) | |
| if not messages: | |
| messages = [SystemMessage(content=system_prompt_str)] | |
| else: | |
| console.print(f"[success][Shared Session] Loaded {len(messages)} messages from history.[/success]") | |
| def export_session(msgs): | |
| if agent_args.export: | |
| serialized = [] | |
| for m in msgs: | |
| role = "user" if isinstance(m, HumanMessage) else ("assistant" if isinstance(m, AIMessage) else ("tool" if isinstance(m, ToolMessage) else "system")) | |
| serialized.append({"role": role, "content": str(m.content)}) | |
| with open(agent_args.export, "w", encoding="utf-8") as f: | |
| import json | |
| json.dump(serialized, f, indent=2) | |
| console.print(f"\n[success]Session history exported to {agent_args.export}[/success]") | |
| # Check if a single prompt is provided via CLI arguments | |
| if agent_args.prompt: | |
| prompt = " ".join(agent_args.prompt) | |
| console.print(Panel(f"[bold cyan]Single-Shot Mode[/bold cyan]\nPrompt: {prompt}", border_style="cyan")) | |
| messages.append(HumanMessage(content=prompt)) | |
| with Progress( | |
| SpinnerColumn(), | |
| TextColumn("[info]{task.description}[/info]"), | |
| console=console | |
| ) as progress: | |
| task = progress.add_task("Thinking...", total=None) | |
| for event in app.stream({"messages": messages}): | |
| for node, output in event.items(): | |
| if node == "agent": | |
| progress.update(task, description="Generating response...") | |
| msg = output["messages"][-1] | |
| if hasattr(msg, "content") and msg.content: | |
| console.print(Panel( | |
| Markdown(msg.content), | |
| title="๐ค Agent Response", | |
| border_style="purple", | |
| padding=(1, 2) | |
| )) | |
| elif node == "tools": | |
| for m in output["messages"]: | |
| if isinstance(m, ToolMessage): | |
| progress.update(task, description=f"Executing tool {m.name}...") | |
| content_str = str(m.content) | |
| truncated_content = content_str[:300] + "..." if len(content_str) > 300 else content_str | |
| tool_summary = ( | |
| f"[bold info]โ๏ธ Tool Name:[/bold info] [bold cyan]{m.name}[/bold cyan]\n" | |
| f"[bold info]โ Status:[/bold info] [bold green]Completed Successfully[/bold green]\n" | |
| f"[bold info]๐ Output:[/bold info]\n{truncated_content}" | |
| ) | |
| console.print(Panel(tool_summary, title="โ๏ธ Tool Execution Summary", border_style="yellow")) | |
| # Save final state | |
| state = app.get_state({"messages": messages}) | |
| final_msgs = state.values["messages"] | |
| save_session_messages(session_path, final_msgs) | |
| export_session(final_msgs) | |
| return | |
| while True: | |
| try: | |
| user_input = console.input("\n[user]You[/user] > ") | |
| if user_input.strip().lower() in ["exit", "quit", "q"]: | |
| console.print("[info]Exiting agent loop... Goodbye![/info]") | |
| break | |
| if not user_input.strip(): | |
| continue | |
| messages.append(HumanMessage(content=user_input)) | |
| with Progress( | |
| SpinnerColumn(), | |
| TextColumn("[info]{task.description}[/info]"), | |
| console=console | |
| ) as progress: | |
| task = progress.add_task("Thinking...", total=None) | |
| # Stream the state machine events | |
| for event in app.stream({"messages": messages}): | |
| for node, output in event.items(): | |
| if node == "agent": | |
| progress.update(task, description="Generating response...") | |
| msg = output["messages"][-1] | |
| if hasattr(msg, "content") and msg.content: | |
| console.print(Panel( | |
| Markdown(msg.content), | |
| title="๐ค Agent Response", | |
| border_style="purple", | |
| padding=(1, 2) | |
| )) | |
| elif node == "tools": | |
| # Output executed tools | |
| for m in output["messages"]: | |
| if isinstance(m, ToolMessage): | |
| progress.update(task, description=f"Executing tool {m.name}...") | |
| content_str = str(m.content) | |
| truncated_content = content_str[:300] + "..." if len(content_str) > 300 else content_str | |
| tool_summary = ( | |
| f"[bold info]โ๏ธ Tool Name:[/bold info] [bold cyan]{m.name}[/bold cyan]\n" | |
| f"[bold info]โ Status:[/bold info] [bold green]Completed Successfully[/bold green]\n" | |
| f"[bold info]๐ Output:[/bold info]\n{truncated_content}" | |
| ) | |
| console.print(Panel(tool_summary, title="โ๏ธ Tool Execution Summary", border_style="yellow")) | |
| # Store final agent response in current turn's message state | |
| state = app.get_state({"messages": messages}) | |
| messages = state.values["messages"] | |
| save_session_messages(session_path, messages) | |
| except KeyboardInterrupt: | |
| console.print("\n[warning]Session interrupted by user.[/warning]") | |
| break | |
| except Exception as e: | |
| console.print(f"\n[danger]An error occurred:[/danger] {e}") | |
| export_session(messages) | |
| if __name__ == "__main__": | |
| # Ensure OPENAI_API_KEY is present | |
| if not os.getenv("OPENAI_API_KEY"): | |
| console.print("[danger]Error: OPENAI_API_KEY environment variable is not set.[/danger]") | |
| console.print("[info]Please set it using: export OPENAI_API_KEY='your-key'[/info]") | |
| sys.exit(1) | |
| main() | |
Xet Storage Details
- Size:
- 44.8 kB
- Xet hash:
- 599f2bfeedd2667b43b6cac55e94a4d1a2f79edab2cd23e6299c9363c38b13a0
ยท
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.