Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/reverse_api/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""Reverse API - Browser traffic capture for API reverse engineering."""

__version__ = "0.2.2"
__version__ = "0.2.3"
129 changes: 43 additions & 86 deletions src/reverse_api/browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from rich.status import Status

from .utils import get_har_dir, get_timestamp
from . import __version__

console = Console()

Expand Down Expand Up @@ -386,7 +387,7 @@ def _start_with_stealth_chromium(self, start_url: Optional[str] = None) -> Path:
# Wait for browser to close
try:
while self._context.pages:
self._context.pages[0].wait_for_timeout(100)
self._context.pages[0].wait_for_timeout(100)
except Exception:
pass

Expand Down Expand Up @@ -435,7 +436,7 @@ def close(self) -> Path:
time.sleep(1)

status.update(" [dim]saving har file...[/dim]")
self._context.close()
self._context.close()

if self.har_path.exists():
har_size = self.har_path.stat().st_size
Expand Down Expand Up @@ -675,69 +676,42 @@ async def _run_with_har_capture(self, cdp_url: str = None) -> dict:
async def _run_with_browser_use(self) -> dict:
"""Run agent with HAR recording via browser-use's built-in HAR capture."""
import logging
import os

# Custom handler to capture memory logs
class MemoryLogHandler(logging.Handler):
def __init__(self, console_instance):
super().__init__()
self.console = console_instance

def emit(self, record):
try:
msg = record.getMessage()

if "🧠 Memory:" in msg or ("Memory:" in msg and "🧠" not in msg):
if "🧠 Memory:" in msg:
memory_text = msg.split("🧠 Memory:", 1)[-1].strip()
elif "Memory:" in msg:
memory_text = msg.split("Memory:", 1)[-1].strip()
else:
memory_text = msg

memory_text = " ".join(memory_text.split())

if memory_text:
self.console.print(f" [dim]{memory_text}[/dim]")
except Exception:
pass # Silently ignore handler errors

# Suppress all browser-use logs completely
# Keep INFO level for memory capture, but prevent propagation to parent handlers
null_handler = logging.NullHandler()

browser_use_logger = logging.getLogger("browser_use")
browser_use_logger.setLevel(logging.INFO)
browser_use_logger.handlers.clear() # Remove existing handlers
browser_use_logger.propagate = False

agent_logger = logging.getLogger("Agent")
agent_logger.setLevel(logging.INFO)
agent_logger.handlers.clear() # Remove existing handlers
agent_logger.propagate = False

# Suppress these loggers completely
for logger_name in ["BrowserSession", "service", "tools"]:
logger = logging.getLogger(logger_name)
logger.setLevel(logging.CRITICAL)
logger.handlers.clear()
logger.addHandler(null_handler)
logger.propagate = False
# Set browser-use logging level to warning to suppress INFO logs
os.environ['BROWSER_USE_LOGGING_LEVEL'] = 'WARNING'
Comment thread
kalil0321 marked this conversation as resolved.

try:
from browser_use import Agent, Browser
from browser_use import ChatBrowserUse

# Suppress all browser-use loggers after import
def suppress_browser_use_logs():
for logger_name in [
"browser_use",
"Agent",
"BrowserSession",
"service",
"tools",
]:
logger = logging.getLogger(logger_name)
logger.setLevel(logging.CRITICAL)
logger.handlers.clear()
logger.addHandler(logging.NullHandler())
logger.propagate = False

suppress_browser_use_logs()
except ImportError:
result = {
"success": False,
"message": None,
"error": "browser-use is required for agent mode. Install it with: pip install 'reverse-api-engineer[agent]' or pip install browser-use",
"error": "browser-use is required for agent mode. Install it with: pip install git+https://github.com/browser-use/browser-use.git@49a345fb19e9f12befc5cc1658e0033873892455",
}
console.print(f" [red]error:[/red] {result['error']}")
return result

result = {"success": False, "message": None, "error": None}
browser = None
memory_handler = None

try:
# Parse agent model and validate API key
Expand Down Expand Up @@ -786,60 +760,40 @@ def emit(self, record):
console.print(f" [red]error:[/red] {result['error']}")
return result

suppress_browser_use_logs()

console.print(f" [dim]starting browser with har...[/dim]")
browser = Browser(
record_har_path=str(self.har_path),
record_har_mode="full",
record_har_content="attach",
)

suppress_browser_use_logs()

await browser.start()

suppress_browser_use_logs()

console.print(f" [dim]browser started[/dim]")

task = self.prompt

# Set up memory log handler
memory_handler = MemoryLogHandler(console)
memory_handler.setLevel(logging.INFO)
browser_use_logger.addHandler(memory_handler)
agent_logger.addHandler(memory_handler)

agent = Agent(task=task, llm=llm, browser=browser)
agent_result = await agent.run()

if memory_handler:
browser_use_logger.removeHandler(memory_handler)
agent_logger.removeHandler(memory_handler)
suppress_browser_use_logs()
Comment thread
kalil0321 marked this conversation as resolved.

agent_result = await agent.run()

# Extract final result from agent_result
# Extract final result using browser-use's built-in method
final_message = None
if agent_result:
if hasattr(agent_result, "all_results") and agent_result.all_results:
last_result = agent_result.all_results[-1]
if hasattr(last_result, "text") and last_result.text:
final_message = last_result.text
elif hasattr(last_result, "result") and last_result.result:
final_message = str(last_result.result)
elif hasattr(agent_result, "result"):
final_message = str(agent_result.result)
elif hasattr(agent_result, "text"):
final_message = agent_result.text
else:
msg_str = str(agent_result)
if "Final Result:" in msg_str:
parts = msg_str.split("Final Result:")
if len(parts) > 1:
final_message = parts[-1].strip()
else:
final_message = msg_str
if agent_result and hasattr(agent_result, "final_result"):
final_message = agent_result.final_result()

result["success"] = True
result["message"] = final_message or "Task completed"

except Exception as e:
if memory_handler:
browser_use_logger.removeHandler(memory_handler)
agent_logger.removeHandler(memory_handler)
result["error"] = str(e)
console.print(f" [yellow]agent error: {e}[/yellow]")
finally:
Expand Down Expand Up @@ -908,7 +862,10 @@ async def _run_with_stagehand(self) -> dict:
empty_har = {
"log": {
"version": "1.2",
"creator": {"name": "reverse-api-engineer", "version": "0.2.0"},
"creator": {
"name": "reverse-api-engineer",
"version": __version__,
},
"pages": [],
"entries": [],
}
Expand Down Expand Up @@ -1062,11 +1019,11 @@ def start(self) -> Path:

if result.get("success"):
console.print(f" [green]agent task completed[/green]")
if result.get("message"):
if result.get("message") and result["message"] != "Task completed":
Comment thread
kalil0321 marked this conversation as resolved.
msg = result["message"]
if len(msg) > 500:
msg = msg[:500] + "..."
console.print(f" [dim]result:[/dim] [white]{msg}[/white]")
console.print(f" [dim]result:[/dim]\n [white]{msg}[/white]")
else:
error = result.get("error", "unknown error")
console.print(f" [yellow]agent error: {error}[/yellow]")
Expand Down
5 changes: 5 additions & 0 deletions src/reverse_api/engineer.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Reverse engineering module with SDK dispatch."""

import asyncio
import logging
from pathlib import Path
from typing import Optional, Dict, Any

Expand All @@ -16,6 +17,10 @@

from .base_engineer import BaseEngineer

# Suppress claude_agent_sdk logs
logging.getLogger("claude_agent_sdk").setLevel(logging.WARNING)
logging.getLogger("claude_agent_sdk._internal.transport.subprocess_cli").setLevel(logging.WARNING)


class ClaudeEngineer(BaseEngineer):
"""Uses Claude Agent SDK to analyze HAR files and generate Python API scripts."""
Expand Down
Loading