tahamajs's picture
download
raw
44.8 kB
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
# =====================================================================
@tool
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}"
@tool
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}"
@tool
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}"
@tool
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}"
@tool
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)
@tool
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."
@tool
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}"
@tool
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}"
@tool
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}"
@tool
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}"
@tool
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}"
@tool
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}"
@tool
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}"
@tool
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}"
@tool
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}"
@tool
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}"
@tool
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}"
@tool
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}"
@tool
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}"
@tool
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}"
@tool
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}"
@tool
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}"
@tool
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."
@tool
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}"
@tool
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
@tool
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)"
@tool
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.