feat: bump version to 1.28.0
feat: update project structure and imports refactor: update test coverage to reflect project structure refactor: update linting and formatting to reflect project structure fix: improve autonomous mode logging fix: handle json parsing errors in http client fix: improve error handling in http client feat: add support for advanced input in assistant feat: add collaboration agents command feat: add list models api call feat: improve background monitor functionality feat: update config loader feat: update context management feat: update enhanced assistant functionality feat: update http client functionality feat: update logging configuration feat: update session management feat: update usage tracking feat: update validation logic feat: add implode functionality feat: update input handler
This commit is contained in:
parent
a289a8e402
commit
9438496b72
10
Makefile
10
Makefile
@ -27,16 +27,16 @@ test:
|
|||||||
pytest tests/ -v --tb=long --full-trace -l --maxfail=10
|
pytest tests/ -v --tb=long --full-trace -l --maxfail=10
|
||||||
|
|
||||||
test-cov:
|
test-cov:
|
||||||
pytest --cov=pr --cov-report=html --cov-report=term-missing
|
pytest --cov=rp --cov-report=html --cov-report=term-missing
|
||||||
@echo "Coverage report generated in htmlcov/index.html"
|
@echo "Coverage report generated in htmlcov/index.html"
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
flake8 pr tests --max-line-length=100 --ignore=E203,W503
|
flake8 rp tests --max-line-length=100 --ignore=E203,W503
|
||||||
mypy pr --ignore-missing-imports
|
mypy rp --ignore-missing-imports
|
||||||
|
|
||||||
format:
|
format:
|
||||||
black pr tests
|
black rp tests
|
||||||
isort pr tests --profile black
|
isort rp tests --profile black
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf build/
|
rm -rf build/
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import argparse
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from rp import __version__
|
from rp import __version__
|
||||||
from rp.core import Assistant
|
from rp.core import Assistant
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import time
|
|||||||
import uuid
|
import uuid
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import Any, Callable, Dict, List, Optional
|
from typing import Any, Callable, Dict, List, Optional
|
||||||
|
|
||||||
from ..memory.knowledge_store import KnowledgeStore
|
from ..memory.knowledge_store import KnowledgeStore
|
||||||
from .agent_communication import AgentCommunicationBus, AgentMessage, MessageType
|
from .agent_communication import AgentCommunicationBus, AgentMessage, MessageType
|
||||||
from .agent_roles import AgentRole, get_agent_role
|
from .agent_roles import AgentRole, get_agent_role
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from rp.autonomous.detection import is_task_complete
|
from rp.autonomous.detection import is_task_complete
|
||||||
from rp.core.api import call_api
|
from rp.core.api import call_api
|
||||||
from rp.core.context import truncate_tool_result
|
from rp.core.context import truncate_tool_result
|
||||||
@ -13,7 +14,7 @@ logger = logging.getLogger("rp")
|
|||||||
def run_autonomous_mode(assistant, task):
|
def run_autonomous_mode(assistant, task):
|
||||||
assistant.autonomous_mode = True
|
assistant.autonomous_mode = True
|
||||||
assistant.autonomous_iterations = 0
|
assistant.autonomous_iterations = 0
|
||||||
logger.debug(f"=== AUTONOMOUS MODE START ===")
|
logger.debug("=== AUTONOMOUS MODE START ===")
|
||||||
logger.debug(f"Task: {task}")
|
logger.debug(f"Task: {task}")
|
||||||
from rp.core.knowledge_context import inject_knowledge_context
|
from rp.core.knowledge_context import inject_knowledge_context
|
||||||
|
|
||||||
@ -125,6 +126,9 @@ def execute_single_tool(assistant, func_name, arguments):
|
|||||||
db_get,
|
db_get,
|
||||||
db_query,
|
db_query,
|
||||||
db_set,
|
db_set,
|
||||||
|
editor_insert_text,
|
||||||
|
editor_replace_text,
|
||||||
|
editor_search,
|
||||||
getpwd,
|
getpwd,
|
||||||
http_fetch,
|
http_fetch,
|
||||||
index_source_directory,
|
index_source_directory,
|
||||||
|
|||||||
@ -1,12 +1,13 @@
|
|||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
from rp.commands.multiplexer_commands import MULTIPLEXER_COMMANDS
|
|
||||||
from rp.autonomous import run_autonomous_mode
|
from rp.autonomous import run_autonomous_mode
|
||||||
|
from rp.commands.multiplexer_commands import MULTIPLEXER_COMMANDS
|
||||||
from rp.core.api import list_models
|
from rp.core.api import list_models
|
||||||
|
from rp.editor import RPEditor
|
||||||
from rp.tools import read_file
|
from rp.tools import read_file
|
||||||
from rp.tools.base import get_tools_definition
|
from rp.tools.base import get_tools_definition
|
||||||
from rp.ui import Colors
|
from rp.ui import Colors
|
||||||
from rp.editor import RPEditor
|
|
||||||
|
|
||||||
|
|
||||||
def handle_command(assistant, command):
|
def handle_command(assistant, command):
|
||||||
@ -263,7 +264,7 @@ def collaborate_agents_command(assistant, task):
|
|||||||
roles = ["coding", "research", "planning"]
|
roles = ["coding", "research", "planning"]
|
||||||
result = assistant.enhanced.collaborate_agents(task, roles)
|
result = assistant.enhanced.collaborate_agents(task, roles)
|
||||||
print(f"\n{Colors.GREEN}Collaboration completed{Colors.RESET}")
|
print(f"\n{Colors.GREEN}Collaboration completed{Colors.RESET}")
|
||||||
print(f"\nOrchestrator response:")
|
print("\nOrchestrator response:")
|
||||||
if "orchestrator" in result and "response" in result["orchestrator"]:
|
if "orchestrator" in result and "response" in result["orchestrator"]:
|
||||||
print(result["orchestrator"]["response"])
|
print(result["orchestrator"]["response"])
|
||||||
if result.get("agents"):
|
if result.get("agents"):
|
||||||
@ -295,6 +296,7 @@ def store_knowledge(assistant, content):
|
|||||||
return
|
return
|
||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from rp.memory import KnowledgeEntry
|
from rp.memory import KnowledgeEntry
|
||||||
|
|
||||||
categories = assistant.enhanced.fact_extractor.categorize_content(content)
|
categories = assistant.enhanced.fact_extractor.categorize_content(content)
|
||||||
|
|||||||
@ -97,7 +97,8 @@ def list_models(model_list_url, api_key):
|
|||||||
response = http_client.get(model_list_url, headers=headers, db_conn=None)
|
response = http_client.get(model_list_url, headers=headers, db_conn=None)
|
||||||
if response.get("error"):
|
if response.get("error"):
|
||||||
return {"error": response.get("text", "HTTP error")}
|
return {"error": response.get("text", "HTTP error")}
|
||||||
data = json.loads(response["text"])
|
response_data = response["text"]
|
||||||
|
data = json.loads(response_data)
|
||||||
return data.get("data", [])
|
return data.get("data", [])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return {"error": str(e)}
|
return {"error": str(e)}
|
||||||
|
|||||||
@ -8,8 +8,8 @@ import sqlite3
|
|||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
|
|
||||||
from rp.commands import handle_command
|
from rp.commands import handle_command
|
||||||
from rp.input_handler import get_advanced_input
|
|
||||||
from rp.config import (
|
from rp.config import (
|
||||||
DB_PATH,
|
DB_PATH,
|
||||||
DEFAULT_API_URL,
|
DEFAULT_API_URL,
|
||||||
@ -23,6 +23,7 @@ from rp.core.autonomous_interactions import start_global_autonomous, stop_global
|
|||||||
from rp.core.background_monitor import get_global_monitor, start_global_monitor, stop_global_monitor
|
from rp.core.background_monitor import get_global_monitor, start_global_monitor, stop_global_monitor
|
||||||
from rp.core.context import init_system_message, truncate_tool_result
|
from rp.core.context import init_system_message, truncate_tool_result
|
||||||
from rp.core.usage_tracker import UsageTracker
|
from rp.core.usage_tracker import UsageTracker
|
||||||
|
from rp.input_handler import get_advanced_input
|
||||||
from rp.tools import get_tools_definition
|
from rp.tools import get_tools_definition
|
||||||
from rp.tools.agents import (
|
from rp.tools.agents import (
|
||||||
collaborate_agents,
|
collaborate_agents,
|
||||||
@ -398,7 +399,7 @@ class Assistant:
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
prompt += f">{Colors.RESET} "
|
prompt += f">{Colors.RESET} "
|
||||||
user_input = get_advanced_input(prompt)
|
user_input = get_advanced_input(prompt) or ""
|
||||||
user_input = user_input.strip()
|
user_input = user_input.strip()
|
||||||
if not user_input:
|
if not user_input:
|
||||||
continue
|
continue
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from rp.tools.interactive_control import (
|
from rp.tools.interactive_control import (
|
||||||
get_session_status,
|
get_session_status,
|
||||||
list_active_sessions,
|
list_active_sessions,
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import queue
|
import queue
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from rp.multiplexer import get_all_multiplexer_states, get_multiplexer
|
from rp.multiplexer import get_all_multiplexer_states, get_multiplexer
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import configparser
|
import configparser
|
||||||
import os
|
import os
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict
|
||||||
|
|
||||||
from rp.core.logging import get_logger
|
from rp.core.logging import get_logger
|
||||||
|
|
||||||
logger = get_logger("config")
|
logger = get_logger("config")
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import pathlib
|
import pathlib
|
||||||
|
|
||||||
from rp.config import (
|
from rp.config import (
|
||||||
CHARS_PER_TOKEN,
|
CHARS_PER_TOKEN,
|
||||||
CONTENT_TRIM_LENGTH,
|
CONTENT_TRIM_LENGTH,
|
||||||
@ -9,10 +10,10 @@ from rp.config import (
|
|||||||
CONTEXT_FILE,
|
CONTEXT_FILE,
|
||||||
EMERGENCY_MESSAGES_TO_KEEP,
|
EMERGENCY_MESSAGES_TO_KEEP,
|
||||||
GLOBAL_CONTEXT_FILE,
|
GLOBAL_CONTEXT_FILE,
|
||||||
|
KNOWLEDGE_PATH,
|
||||||
MAX_TOKENS_LIMIT,
|
MAX_TOKENS_LIMIT,
|
||||||
MAX_TOOL_RESULT_LENGTH,
|
MAX_TOOL_RESULT_LENGTH,
|
||||||
RECENT_MESSAGES_TO_KEEP,
|
RECENT_MESSAGES_TO_KEEP,
|
||||||
KNOWLEDGE_PATH,
|
|
||||||
)
|
)
|
||||||
from rp.ui import Colors
|
from rp.ui import Colors
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
import uuid
|
import uuid
|
||||||
from typing import Any, Dict, List, Optional
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
from rp.agents import AgentManager
|
from rp.agents import AgentManager
|
||||||
from rp.cache import APICache, ToolCache
|
from rp.cache import APICache, ToolCache
|
||||||
from rp.config import (
|
from rp.config import (
|
||||||
@ -132,6 +133,7 @@ class EnhancedAssistant:
|
|||||||
for fact in facts[:5]:
|
for fact in facts[:5]:
|
||||||
entry_id = str(uuid.uuid4())[:16]
|
entry_id = str(uuid.uuid4())[:16]
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from rp.memory import KnowledgeEntry
|
from rp.memory import KnowledgeEntry
|
||||||
|
|
||||||
categories = self.fact_extractor.categorize_content(fact["text"])
|
categories = self.fact_extractor.categorize_content(fact["text"])
|
||||||
|
|||||||
@ -1,12 +1,10 @@
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import random
|
import random
|
||||||
import time
|
|
||||||
import urllib.error
|
|
||||||
import urllib.parse
|
|
||||||
import urllib.request
|
|
||||||
from typing import Dict, Any, Optional
|
from typing import Dict, Any, Optional
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
logger = logging.getLogger("rp")
|
logger = logging.getLogger("rp")
|
||||||
|
|
||||||
# Realistic User-Agents and headers
|
# Realistic User-Agents and headers
|
||||||
@ -23,6 +21,7 @@ USER_AGENTS = [
|
|||||||
"Mozilla/5.0 (Android 11; Mobile; rv:68.0) Gecko/68.0 Firefox/88.0",
|
"Mozilla/5.0 (Android 11; Mobile; rv:68.0) Gecko/68.0 Firefox/88.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def get_realistic_headers(additional_headers=None):
|
def get_realistic_headers(additional_headers=None):
|
||||||
"""Generate realistic HTTP headers with random User-Agent and variations."""
|
"""Generate realistic HTTP headers with random User-Agent and variations."""
|
||||||
accept_languages = [
|
accept_languages = [
|
||||||
@ -71,89 +70,75 @@ class SyncHTTPClient:
|
|||||||
else:
|
else:
|
||||||
headers = get_realistic_headers(headers)
|
headers = get_realistic_headers(headers)
|
||||||
|
|
||||||
# Handle JSON data
|
request_body_for_log = ""
|
||||||
if json_data is not None:
|
if json_data is not None:
|
||||||
data = json.dumps(json_data).encode('utf-8')
|
request_body_for_log = json.dumps(json_data)
|
||||||
headers["Content-Type"] = "application/json"
|
|
||||||
|
|
||||||
# Prepare request body for logging
|
|
||||||
if json_data is not None:
|
|
||||||
request_body = json.dumps(json_data)
|
|
||||||
elif data is not None:
|
elif data is not None:
|
||||||
request_body = data.decode("utf-8") if isinstance(data, bytes) else str(data)
|
request_body_for_log = data.decode("utf-8") if isinstance(data, bytes) else str(data)
|
||||||
else:
|
|
||||||
request_body = ""
|
|
||||||
|
|
||||||
"""Make a sync HTTP request using urllib with retry logic."""
|
try:
|
||||||
attempt = 0
|
response = requests.request(
|
||||||
start_time = time.time()
|
method,
|
||||||
while True:
|
url,
|
||||||
attempt += 1
|
headers=headers,
|
||||||
try:
|
data=data,
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
json=json_data,
|
||||||
with urllib.request.urlopen(req, timeout=timeout) as response:
|
timeout=timeout,
|
||||||
response_data = response.read().decode('utf-8')
|
allow_redirects=True,
|
||||||
response_headers = dict(response.headers)
|
)
|
||||||
|
response.raise_for_status() # Raise HTTPError for bad responses (4xx or 5xx)
|
||||||
|
|
||||||
# Create json method
|
response_data = response.text
|
||||||
def json_method():
|
response_headers = dict(response.headers)
|
||||||
return json.loads(response_data)
|
|
||||||
|
|
||||||
# Log the request
|
if db_conn:
|
||||||
if db_conn:
|
from rp.tools.database import log_http_request
|
||||||
from rp.tools.database import log_http_request
|
|
||||||
|
|
||||||
log_result = log_http_request(
|
log_result = log_http_request(
|
||||||
method, url, request_body, response_data, response.status, db_conn
|
method,
|
||||||
)
|
url,
|
||||||
if log_result.get("status") != "success":
|
request_body_for_log,
|
||||||
logger.warning(f"Failed to log HTTP request: {log_result.get('error')}")
|
response_data,
|
||||||
|
response.status_code,
|
||||||
|
db_conn,
|
||||||
|
)
|
||||||
|
if log_result.get("status") != "success":
|
||||||
|
logger.warning(f"Failed to log HTTP request: {log_result.get('error')}")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"status": response.status,
|
"status": response.status_code,
|
||||||
"headers": response_headers,
|
"headers": response_headers,
|
||||||
"text": response_data,
|
"text": response_data,
|
||||||
"json": json_method,
|
"json": response.json,
|
||||||
}
|
}
|
||||||
except urllib.error.HTTPError as e:
|
except requests.exceptions.HTTPError as e:
|
||||||
# For HTTP errors, still try to read the response
|
response_data = e.response.text if e.response else ""
|
||||||
try:
|
response_headers = dict(e.response.headers) if e.response else {}
|
||||||
response_data = e.read().decode('utf-8')
|
status_code = e.response.status_code if e.response else 0
|
||||||
except:
|
|
||||||
response_data = ""
|
|
||||||
|
|
||||||
# Log the request even on error
|
if db_conn:
|
||||||
if db_conn:
|
from rp.tools.database import log_http_request
|
||||||
from rp.tools.database import log_http_request
|
|
||||||
|
|
||||||
log_result = log_http_request(
|
log_result = log_http_request(
|
||||||
method, url, request_body, response_data, e.code, db_conn
|
method,
|
||||||
)
|
url,
|
||||||
if log_result.get("status") != "success":
|
request_body_for_log,
|
||||||
logger.warning(f"Failed to log HTTP request: {log_result.get('error')}")
|
response_data,
|
||||||
|
status_code,
|
||||||
|
db_conn,
|
||||||
|
)
|
||||||
|
if log_result.get("status") != "success":
|
||||||
|
logger.warning(f"Failed to log HTTP request: {log_result.get('error')}")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"status": e.code,
|
"status": status_code,
|
||||||
"headers": dict(e.headers) if e.headers else {},
|
"headers": response_headers,
|
||||||
"text": response_data,
|
"text": response_data,
|
||||||
"json": lambda: json.loads(response_data) if response_data else None,
|
"json": lambda: e.response.json() if e.response and response_data else None,
|
||||||
}
|
}
|
||||||
except urllib.error.URLError as e:
|
except requests.exceptions.RequestException as e:
|
||||||
if isinstance(e.reason, TimeoutError) or "timeout" in str(e.reason).lower():
|
logger.error(f"Request failed: {e}")
|
||||||
elapsed = time.time() - start_time
|
return {"error": True, "exception": str(e), "status": 0, "text": ""}
|
||||||
elapsed_minutes = int(elapsed // 60)
|
|
||||||
elapsed_seconds = elapsed % 60
|
|
||||||
duration_str = (
|
|
||||||
f"{elapsed_minutes}m {elapsed_seconds:.1f}s"
|
|
||||||
if elapsed_minutes > 0
|
|
||||||
else f"{elapsed_seconds:.1f}s"
|
|
||||||
)
|
|
||||||
logger.warning(
|
|
||||||
f"Request timed out (attempt {attempt}, duration: {duration_str}). Retrying in {attempt} second(s)..."
|
|
||||||
)
|
|
||||||
time.sleep(attempt)
|
|
||||||
else:
|
|
||||||
return {"error": True, "exception": str(e)}
|
|
||||||
|
|
||||||
def get(
|
def get(
|
||||||
self,
|
self,
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
from logging.handlers import RotatingFileHandler
|
from logging.handlers import RotatingFileHandler
|
||||||
|
|
||||||
from rp.config import LOG_FILE
|
from rp.config import LOG_FILE
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import json
|
|||||||
import os
|
import os
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Dict, List, Optional
|
from typing import Dict, List, Optional
|
||||||
|
|
||||||
from rp.core.logging import get_logger
|
from rp.core.logging import get_logger
|
||||||
|
|
||||||
logger = get_logger("session")
|
logger = get_logger("session")
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import json
|
|||||||
import os
|
import os
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Dict, Optional
|
from typing import Dict, Optional
|
||||||
|
|
||||||
from rp.core.logging import get_logger
|
from rp.core.logging import get_logger
|
||||||
|
|
||||||
logger = get_logger("usage")
|
logger = get_logger("usage")
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
from rp.core.exceptions import ValidationError
|
from rp.core.exceptions import ValidationError
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -6,13 +6,13 @@ It intelligently resolves local imports, hoists external dependencies to the top
|
|||||||
and preserves the core logic, using AST for safe transformations.
|
and preserves the core logic, using AST for safe transformations.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import ast
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import ast
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
import py_compile
|
import py_compile
|
||||||
from typing import Set, Dict, Optional, TextIO
|
import sys
|
||||||
|
from typing import Dict, Optional, Set, TextIO
|
||||||
|
|
||||||
logger = logging.getLogger("impLODE")
|
logger = logging.getLogger("impLODE")
|
||||||
|
|
||||||
@ -250,10 +250,10 @@ class Imploder:
|
|||||||
self.processed_files.clear()
|
self.processed_files.clear()
|
||||||
try:
|
try:
|
||||||
with open(output_file_path, "w", encoding="utf-8") as f_out:
|
with open(output_file_path, "w", encoding="utf-8") as f_out:
|
||||||
f_out.write(f"#!/usr/bin/env python3\n")
|
f_out.write("#!/usr/bin/env python3\n")
|
||||||
f_out.write(f"# -*- coding: utf-8 -*-\n")
|
f_out.write("# -*- coding: utf-8 -*-\n")
|
||||||
f_out.write(f"import logging\n")
|
f_out.write("import logging\n")
|
||||||
f_out.write(f"\n# --- IMPLODED FILE: Generated by impLODE --- #\n")
|
f_out.write("\n# --- IMPLODED FILE: Generated by impLODE --- #\n")
|
||||||
f_out.write(
|
f_out.write(
|
||||||
f"# --- Original main file: {os.path.relpath(main_file_abs_path, self.root_dir)} --- #\n"
|
f"# --- Original main file: {os.path.relpath(main_file_abs_path, self.root_dir)} --- #\n"
|
||||||
)
|
)
|
||||||
|
|||||||
@ -100,11 +100,16 @@ class AdvancedInputHandler:
|
|||||||
path = Path(filename).expanduser().resolve()
|
path = Path(filename).expanduser().resolve()
|
||||||
if path.exists() and path.is_file():
|
if path.exists() and path.is_file():
|
||||||
mime_type, _ = mimetypes.guess_type(str(path))
|
mime_type, _ = mimetypes.guess_type(str(path))
|
||||||
if mime_type and (mime_type.startswith("text/") or mime_type in ["application/json", "application/xml"]):
|
if mime_type and (
|
||||||
|
mime_type.startswith("text/")
|
||||||
|
or mime_type in ["application/json", "application/xml"]
|
||||||
|
):
|
||||||
with open(path, encoding="utf-8", errors="replace") as f:
|
with open(path, encoding="utf-8", errors="replace") as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
return f"\n--- File: {filename} ---\n{content}\n--- End of {filename} ---\n"
|
return f"\n--- File: {filename} ---\n{content}\n--- End of {filename} ---\n"
|
||||||
elif mime_type and not mime_type.startswith("image/"): # Handle other binary files
|
elif mime_type and not mime_type.startswith(
|
||||||
|
"image/"
|
||||||
|
): # Handle other binary files
|
||||||
with open(path, "rb") as f:
|
with open(path, "rb") as f:
|
||||||
binary_data = base64.b64encode(f.read()).decode("utf-8")
|
binary_data = base64.b64encode(f.read()).decode("utf-8")
|
||||||
return f"\n--- Binary File: {filename} ({mime_type}) ---\ndata:{mime_type};base64,{binary_data}\n--- End of {filename} ---\n"
|
return f"\n--- Binary File: {filename} ({mime_type}) ---\ndata:{mime_type};base64,{binary_data}\n--- End of {filename} ---\n"
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import threading
|
|||||||
import time
|
import time
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any, Dict, List, Optional, Tuple
|
from typing import Any, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
from .semantic_index import SemanticIndex
|
from .semantic_index import SemanticIndex
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from rp.tools.process_handlers import detect_process_type, get_handler_for_process
|
from rp.tools.process_handlers import detect_process_type, get_handler_for_process
|
||||||
from rp.tools.prompt_detection import get_global_detector
|
from rp.tools.prompt_detection import get_global_detector
|
||||||
from rp.ui import Colors
|
from rp.ui import Colors
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import importlib.util
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from typing import Callable, Dict, List
|
from typing import Callable, Dict, List
|
||||||
|
|
||||||
from rp.core.logging import get_logger
|
from rp.core.logging import get_logger
|
||||||
|
|
||||||
logger = get_logger("plugins")
|
logger = get_logger("plugins")
|
||||||
|
|||||||
2
rp/rp.py
2
rp/rp.py
@ -1,8 +1,8 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
# Trigger build
|
# Trigger build
|
||||||
import sys
|
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
# Add current directory to path to ensure imports work
|
# Add current directory to path to ensure imports work
|
||||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|||||||
@ -6,7 +6,6 @@ from rp.tools.agents import (
|
|||||||
remove_agent,
|
remove_agent,
|
||||||
)
|
)
|
||||||
from rp.tools.base import get_tools_definition
|
from rp.tools.base import get_tools_definition
|
||||||
from rp.tools.vision import post_image
|
|
||||||
from rp.tools.command import kill_process, run_command, run_command_interactive, tail_process
|
from rp.tools.command import kill_process, run_command, run_command_interactive, tail_process
|
||||||
from rp.tools.database import db_get, db_query, db_set
|
from rp.tools.database import db_get, db_query, db_set
|
||||||
from rp.tools.editor import (
|
from rp.tools.editor import (
|
||||||
@ -17,27 +16,27 @@ from rp.tools.editor import (
|
|||||||
open_editor,
|
open_editor,
|
||||||
)
|
)
|
||||||
from rp.tools.filesystem import (
|
from rp.tools.filesystem import (
|
||||||
get_uid,
|
|
||||||
read_specific_lines,
|
|
||||||
replace_specific_line,
|
|
||||||
insert_line_at_position,
|
|
||||||
delete_specific_line,
|
|
||||||
read_file,
|
|
||||||
write_file,
|
|
||||||
list_directory,
|
|
||||||
mkdir,
|
|
||||||
chdir,
|
chdir,
|
||||||
getpwd,
|
clear_edit_tracker,
|
||||||
index_source_directory,
|
|
||||||
search_replace,
|
|
||||||
get_editor,
|
|
||||||
close_editor,
|
close_editor,
|
||||||
open_editor,
|
delete_specific_line,
|
||||||
editor_insert_text,
|
|
||||||
editor_replace_text,
|
|
||||||
display_edit_summary,
|
display_edit_summary,
|
||||||
display_edit_timeline,
|
display_edit_timeline,
|
||||||
clear_edit_tracker,
|
editor_insert_text,
|
||||||
|
editor_replace_text,
|
||||||
|
get_editor,
|
||||||
|
get_uid,
|
||||||
|
getpwd,
|
||||||
|
index_source_directory,
|
||||||
|
insert_line_at_position,
|
||||||
|
list_directory,
|
||||||
|
mkdir,
|
||||||
|
open_editor,
|
||||||
|
read_file,
|
||||||
|
read_specific_lines,
|
||||||
|
replace_specific_line,
|
||||||
|
search_replace,
|
||||||
|
write_file,
|
||||||
)
|
)
|
||||||
from rp.tools.lsp import get_diagnostics
|
from rp.tools.lsp import get_diagnostics
|
||||||
from rp.tools.memory import (
|
from rp.tools.memory import (
|
||||||
@ -52,7 +51,8 @@ from rp.tools.memory import (
|
|||||||
from rp.tools.patch import apply_patch, create_diff
|
from rp.tools.patch import apply_patch, create_diff
|
||||||
from rp.tools.python_exec import python_exec
|
from rp.tools.python_exec import python_exec
|
||||||
from rp.tools.search import glob_files, grep
|
from rp.tools.search import glob_files, grep
|
||||||
from rp.tools.web import http_fetch, web_search, web_search_news, download_to_file
|
from rp.tools.vision import post_image
|
||||||
|
from rp.tools.web import download_to_file, http_fetch, web_search, web_search_news
|
||||||
|
|
||||||
# Aliases for user-requested tool names
|
# Aliases for user-requested tool names
|
||||||
view = read_file
|
view = read_file
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
import os
|
import os
|
||||||
from typing import Any, Dict, List
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
from rp.agents.agent_manager import AgentManager
|
from rp.agents.agent_manager import AgentManager
|
||||||
|
from rp.config import DEFAULT_API_URL, DEFAULT_MODEL
|
||||||
from rp.core.api import call_api
|
from rp.core.api import call_api
|
||||||
from rp.config import DEFAULT_MODEL, DEFAULT_API_URL
|
|
||||||
from rp.tools.base import get_tools_definition
|
from rp.tools.base import get_tools_definition
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import inspect
|
import inspect
|
||||||
|
from typing import get_args, get_origin, get_type_hints
|
||||||
|
|
||||||
import rp.tools
|
import rp.tools
|
||||||
from typing import get_type_hints, get_origin, get_args
|
|
||||||
|
|
||||||
|
|
||||||
def _type_to_json_schema(py_type):
|
def _type_to_json_schema(py_type):
|
||||||
|
|||||||
@ -1,15 +1,15 @@
|
|||||||
import sys
|
|
||||||
import os
|
|
||||||
import ast
|
import ast
|
||||||
import inspect
|
|
||||||
import time
|
|
||||||
import threading
|
|
||||||
import gc
|
import gc
|
||||||
import weakref
|
import inspect
|
||||||
import linecache
|
|
||||||
import re
|
|
||||||
import json
|
import json
|
||||||
|
import linecache
|
||||||
|
import os
|
||||||
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
import weakref
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
import os
|
import os
|
||||||
import os.path
|
import os.path
|
||||||
|
|
||||||
from rp.editor import RPEditor
|
from rp.editor import RPEditor
|
||||||
|
|
||||||
from ..tools.patch import display_content_diff
|
from ..tools.patch import display_content_diff
|
||||||
from ..ui.edit_feedback import track_edit, tracker
|
from ..ui.edit_feedback import track_edit, tracker
|
||||||
|
|
||||||
|
|||||||
@ -3,8 +3,10 @@ import hashlib
|
|||||||
import mimetypes
|
import mimetypes
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
from typing import Optional, Any
|
from typing import Any, Optional
|
||||||
|
|
||||||
from rp.editor import RPEditor
|
from rp.editor import RPEditor
|
||||||
|
|
||||||
from ..tools.patch import display_content_diff
|
from ..tools.patch import display_content_diff
|
||||||
from ..ui.diff_display import get_diff_stats
|
from ..ui.diff_display import get_diff_stats
|
||||||
from ..ui.edit_feedback import track_edit, tracker
|
from ..ui.edit_feedback import track_edit, tracker
|
||||||
@ -281,7 +283,9 @@ def read_file(filepath: str, db_conn: Optional[Any] = None) -> dict:
|
|||||||
try:
|
try:
|
||||||
path = os.path.expanduser(filepath)
|
path = os.path.expanduser(filepath)
|
||||||
mime_type, _ = mimetypes.guess_type(str(path))
|
mime_type, _ = mimetypes.guess_type(str(path))
|
||||||
if mime_type and (mime_type.startswith("text/") or mime_type in ["application/json", "application/xml"]):
|
if mime_type and (
|
||||||
|
mime_type.startswith("text/") or mime_type in ["application/json", "application/xml"]
|
||||||
|
):
|
||||||
with open(path, encoding="utf-8", errors="replace") as f:
|
with open(path, encoding="utf-8", errors="replace") as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
else:
|
else:
|
||||||
@ -342,7 +346,7 @@ def write_file(
|
|||||||
write_mode = "wb"
|
write_mode = "wb"
|
||||||
write_encoding = None
|
write_encoding = None
|
||||||
except Exception:
|
except Exception:
|
||||||
pass # Not a valid base64, treat as plain text
|
pass # Not a valid base64, treat as plain text
|
||||||
|
|
||||||
if not is_new_file:
|
if not is_new_file:
|
||||||
if write_mode == "wb":
|
if write_mode == "wb":
|
||||||
@ -355,7 +359,7 @@ def write_file(
|
|||||||
operation = track_edit("WRITE", filepath, content=content, old_content=old_content)
|
operation = track_edit("WRITE", filepath, content=content, old_content=old_content)
|
||||||
tracker.mark_in_progress(operation)
|
tracker.mark_in_progress(operation)
|
||||||
|
|
||||||
if show_diff and (not is_new_file) and write_mode == "w": # Only show diff for text files
|
if show_diff and (not is_new_file) and write_mode == "w": # Only show diff for text files
|
||||||
diff_result = display_content_diff(old_content, content, filepath)
|
diff_result = display_content_diff(old_content, content, filepath)
|
||||||
if diff_result["status"] == "success":
|
if diff_result["status"] == "success":
|
||||||
print(diff_result["visual_diff"])
|
print(diff_result["visual_diff"])
|
||||||
@ -370,7 +374,9 @@ def write_file(
|
|||||||
if os.path.exists(path) and db_conn:
|
if os.path.exists(path) and db_conn:
|
||||||
try:
|
try:
|
||||||
cursor = db_conn.cursor()
|
cursor = db_conn.cursor()
|
||||||
file_hash = hashlib.md5(old_content.encode() if isinstance(old_content, str) else old_content).hexdigest()
|
file_hash = hashlib.md5(
|
||||||
|
old_content.encode() if isinstance(old_content, str) else old_content
|
||||||
|
).hexdigest()
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"SELECT MAX(version) FROM file_versions WHERE filepath = ?", (filepath,)
|
"SELECT MAX(version) FROM file_versions WHERE filepath = ?", (filepath,)
|
||||||
)
|
)
|
||||||
@ -378,7 +384,17 @@ def write_file(
|
|||||||
version = result[0] + 1 if result[0] else 1
|
version = result[0] + 1 if result[0] else 1
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"INSERT INTO file_versions (filepath, content, hash, timestamp, version)\n VALUES (?, ?, ?, ?, ?)",
|
"INSERT INTO file_versions (filepath, content, hash, timestamp, version)\n VALUES (?, ?, ?, ?, ?)",
|
||||||
(filepath, old_content if isinstance(old_content, str) else old_content.decode('utf-8', errors='replace'), file_hash, time.time(), version),
|
(
|
||||||
|
filepath,
|
||||||
|
(
|
||||||
|
old_content
|
||||||
|
if isinstance(old_content, str)
|
||||||
|
else old_content.decode("utf-8", errors="replace")
|
||||||
|
),
|
||||||
|
file_hash,
|
||||||
|
time.time(),
|
||||||
|
version,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
db_conn.commit()
|
db_conn.commit()
|
||||||
except Exception:
|
except Exception:
|
||||||
@ -514,8 +530,17 @@ def search_replace(
|
|||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
return {"status": "error", "error": "File does not exist"}
|
return {"status": "error", "error": "File does not exist"}
|
||||||
mime_type, _ = mimetypes.guess_type(str(path))
|
mime_type, _ = mimetypes.guess_type(str(path))
|
||||||
if not (mime_type and (mime_type.startswith("text/") or mime_type in ["application/json", "application/xml"])):
|
if not (
|
||||||
return {"status": "error", "error": f"Cannot perform search and replace on binary file: {filepath}"}
|
mime_type
|
||||||
|
and (
|
||||||
|
mime_type.startswith("text/")
|
||||||
|
or mime_type in ["application/json", "application/xml"]
|
||||||
|
)
|
||||||
|
):
|
||||||
|
return {
|
||||||
|
"status": "error",
|
||||||
|
"error": f"Cannot perform search and replace on binary file: {filepath}",
|
||||||
|
}
|
||||||
if db_conn:
|
if db_conn:
|
||||||
from rp.tools.database import db_get
|
from rp.tools.database import db_get
|
||||||
|
|
||||||
@ -572,7 +597,13 @@ def editor_insert_text(filepath, text, line=None, col=None, show_diff=True, db_c
|
|||||||
try:
|
try:
|
||||||
path = os.path.expanduser(filepath)
|
path = os.path.expanduser(filepath)
|
||||||
mime_type, _ = mimetypes.guess_type(str(path))
|
mime_type, _ = mimetypes.guess_type(str(path))
|
||||||
if not (mime_type and (mime_type.startswith("text/") or mime_type in ["application/json", "application/xml"])):
|
if not (
|
||||||
|
mime_type
|
||||||
|
and (
|
||||||
|
mime_type.startswith("text/")
|
||||||
|
or mime_type in ["application/json", "application/xml"]
|
||||||
|
)
|
||||||
|
):
|
||||||
return {"status": "error", "error": f"Cannot insert text into binary file: {filepath}"}
|
return {"status": "error", "error": f"Cannot insert text into binary file: {filepath}"}
|
||||||
if db_conn:
|
if db_conn:
|
||||||
from rp.tools.database import db_get
|
from rp.tools.database import db_get
|
||||||
@ -616,7 +647,13 @@ def editor_replace_text(
|
|||||||
operation = None
|
operation = None
|
||||||
path = os.path.expanduser(filepath)
|
path = os.path.expanduser(filepath)
|
||||||
mime_type, _ = mimetypes.guess_type(str(path))
|
mime_type, _ = mimetypes.guess_type(str(path))
|
||||||
if not (mime_type and (mime_type.startswith("text/") or mime_type in ["application/json", "application/xml"])):
|
if not (
|
||||||
|
mime_type
|
||||||
|
and (
|
||||||
|
mime_type.startswith("text/")
|
||||||
|
or mime_type in ["application/json", "application/xml"]
|
||||||
|
)
|
||||||
|
):
|
||||||
return {"status": "error", "error": f"Cannot replace text in binary file: {filepath}"}
|
return {"status": "error", "error": f"Cannot replace text in binary file: {filepath}"}
|
||||||
if db_conn:
|
if db_conn:
|
||||||
from rp.tools.database import db_get
|
from rp.tools.database import db_get
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
|
import importlib
|
||||||
import subprocess
|
import subprocess
|
||||||
import threading
|
import threading
|
||||||
import importlib
|
|
||||||
|
|
||||||
|
|
||||||
def _get_multiplexer_functions():
|
def _get_multiplexer_functions():
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
from typing import Dict, Any
|
from typing import Any, Dict
|
||||||
|
|
||||||
|
|
||||||
def get_diagnostics(filepath: str) -> Dict[str, Any]:
|
def get_diagnostics(filepath: str) -> Dict[str, Any]:
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import os
|
|||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict
|
||||||
|
|
||||||
from rp.memory.knowledge_store import KnowledgeEntry, KnowledgeStore
|
from rp.memory.knowledge_store import KnowledgeEntry, KnowledgeStore
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import mimetypes
|
|||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
from ..ui.diff_display import display_diff, get_diff_stats
|
from ..ui.diff_display import display_diff, get_diff_stats
|
||||||
|
|
||||||
|
|
||||||
@ -64,9 +65,21 @@ def create_diff(
|
|||||||
path2 = os.path.expanduser(file2)
|
path2 = os.path.expanduser(file2)
|
||||||
mime_type1, _ = mimetypes.guess_type(str(path1))
|
mime_type1, _ = mimetypes.guess_type(str(path1))
|
||||||
mime_type2, _ = mimetypes.guess_type(str(path2))
|
mime_type2, _ = mimetypes.guess_type(str(path2))
|
||||||
if not (mime_type1 and (mime_type1.startswith("text/") or mime_type1 in ["application/json", "application/xml"])):
|
if not (
|
||||||
|
mime_type1
|
||||||
|
and (
|
||||||
|
mime_type1.startswith("text/")
|
||||||
|
or mime_type1 in ["application/json", "application/xml"]
|
||||||
|
)
|
||||||
|
):
|
||||||
return {"status": "error", "error": f"Cannot create diff for binary file: {file1}"}
|
return {"status": "error", "error": f"Cannot create diff for binary file: {file1}"}
|
||||||
if not (mime_type2 and (mime_type2.startswith("text/") or mime_type2 in ["application/json", "application/xml"])):
|
if not (
|
||||||
|
mime_type2
|
||||||
|
and (
|
||||||
|
mime_type2.startswith("text/")
|
||||||
|
or mime_type2 in ["application/json", "application/xml"]
|
||||||
|
)
|
||||||
|
):
|
||||||
return {"status": "error", "error": f"Cannot create diff for binary file: {file2}"}
|
return {"status": "error", "error": f"Cannot create diff for binary file: {file2}"}
|
||||||
with open(path1, encoding="utf-8", errors="replace") as f1, open(
|
with open(path1, encoding="utf-8", errors="replace") as f1, open(
|
||||||
path2, encoding="utf-8", errors="replace"
|
path2, encoding="utf-8", errors="replace"
|
||||||
@ -102,9 +115,21 @@ def display_file_diff(filepath1, filepath2, format_type="unified", context_lines
|
|||||||
path2 = os.path.expanduser(filepath2)
|
path2 = os.path.expanduser(filepath2)
|
||||||
mime_type1, _ = mimetypes.guess_type(str(path1))
|
mime_type1, _ = mimetypes.guess_type(str(path1))
|
||||||
mime_type2, _ = mimetypes.guess_type(str(path2))
|
mime_type2, _ = mimetypes.guess_type(str(path2))
|
||||||
if not (mime_type1 and (mime_type1.startswith("text/") or mime_type1 in ["application/json", "application/xml"])):
|
if not (
|
||||||
|
mime_type1
|
||||||
|
and (
|
||||||
|
mime_type1.startswith("text/")
|
||||||
|
or mime_type1 in ["application/json", "application/xml"]
|
||||||
|
)
|
||||||
|
):
|
||||||
return {"status": "error", "error": f"Cannot display diff for binary file: {filepath1}"}
|
return {"status": "error", "error": f"Cannot display diff for binary file: {filepath1}"}
|
||||||
if not (mime_type2 and (mime_type2.startswith("text/") or mime_type2 in ["application/json", "application/xml"])):
|
if not (
|
||||||
|
mime_type2
|
||||||
|
and (
|
||||||
|
mime_type2.startswith("text/")
|
||||||
|
or mime_type2 in ["application/json", "application/xml"]
|
||||||
|
)
|
||||||
|
):
|
||||||
return {"status": "error", "error": f"Cannot display diff for binary file: {filepath2}"}
|
return {"status": "error", "error": f"Cannot display diff for binary file: {filepath2}"}
|
||||||
with open(path1, encoding="utf-8", errors="replace") as f1:
|
with open(path1, encoding="utf-8", errors="replace") as f1:
|
||||||
old_content = f1.read()
|
old_content = f1.read()
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import glob
|
import glob
|
||||||
import os
|
import os
|
||||||
from typing import List
|
|
||||||
import re
|
import re
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
|
||||||
def glob_files(pattern: str, path: str = ".") -> dict:
|
def glob_files(pattern: str, path: str = ".") -> dict:
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
from rp.vision import post_image as vision_post_image
|
|
||||||
import functools
|
import functools
|
||||||
|
|
||||||
|
from rp.vision import post_image as vision_post_image
|
||||||
|
|
||||||
|
|
||||||
@functools.lru_cache()
|
@functools.lru_cache()
|
||||||
def post_image(path: str, prompt: str = None):
|
def post_image(path: str, prompt: str = None):
|
||||||
|
|||||||
118
rp/tools/web.py
118
rp/tools/web.py
@ -1,14 +1,7 @@
|
|||||||
import imghdr
|
import imghdr
|
||||||
import json
|
|
||||||
import random
|
import random
|
||||||
import urllib.error
|
import requests
|
||||||
import urllib.parse
|
from typing import Optional, Dict, Any
|
||||||
import urllib.request
|
|
||||||
|
|
||||||
|
|
||||||
import json
|
|
||||||
import urllib.parse
|
|
||||||
import urllib.request
|
|
||||||
|
|
||||||
# Realistic User-Agents
|
# Realistic User-Agents
|
||||||
USER_AGENTS = [
|
USER_AGENTS = [
|
||||||
@ -24,6 +17,7 @@ USER_AGENTS = [
|
|||||||
"Mozilla/5.0 (Android 11; Mobile; rv:68.0) Gecko/68.0 Firefox/88.0",
|
"Mozilla/5.0 (Android 11; Mobile; rv:68.0) Gecko/68.0 Firefox/88.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def get_default_headers():
|
def get_default_headers():
|
||||||
"""Get default realistic headers with variations."""
|
"""Get default realistic headers with variations."""
|
||||||
accept_languages = [
|
accept_languages = [
|
||||||
@ -50,7 +44,7 @@ def get_default_headers():
|
|||||||
return headers
|
return headers
|
||||||
|
|
||||||
|
|
||||||
def http_fetch(url, headers=None):
|
def http_fetch(url: str, headers: Optional[Dict[str, str]] = None) -> Dict[str, Any]:
|
||||||
"""Fetch content from an HTTP URL.
|
"""Fetch content from an HTTP URL.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -61,19 +55,27 @@ def http_fetch(url, headers=None):
|
|||||||
Dict with status and content.
|
Dict with status and content.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
request = urllib.request.Request(url)
|
|
||||||
default_headers = get_default_headers()
|
default_headers = get_default_headers()
|
||||||
if headers:
|
if headers:
|
||||||
default_headers.update(headers)
|
default_headers.update(headers)
|
||||||
for header_key, header_value in default_headers.items():
|
|
||||||
request.add_header(header_key, header_value)
|
|
||||||
with urllib.request.urlopen(request) as response:
|
|
||||||
content = response.read().decode("utf-8")
|
|
||||||
return {"status": "success", "content": content[:10000]}
|
|
||||||
except Exception as exception:
|
|
||||||
return {"status": "error", "error": str(exception)}
|
|
||||||
|
|
||||||
def download_to_file(source_url, destination_path, headers=None):
|
response = requests.get(url, headers=default_headers, timeout=30)
|
||||||
|
response.raise_for_status() # Raise HTTPError for bad responses (4xx or 5xx)
|
||||||
|
|
||||||
|
content_type = response.headers.get("Content-Type", "").lower()
|
||||||
|
if "text" in content_type or "json" in content_type or "xml" in content_type:
|
||||||
|
content = response.text
|
||||||
|
return {"status": "success", "content": content[:10000]}
|
||||||
|
else:
|
||||||
|
content = response.content
|
||||||
|
return {"status": "success", "content": content}
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
return {"status": "error", "error": str(e)}
|
||||||
|
|
||||||
|
|
||||||
|
def download_to_file(
|
||||||
|
source_url: str, destination_path: str, headers: Optional[Dict[str, str]] = None
|
||||||
|
) -> Dict[str, Any]:
|
||||||
"""Download content from an HTTP URL to a file.
|
"""Download content from an HTTP URL to a file.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -87,45 +89,63 @@ def download_to_file(source_url, destination_path, headers=None):
|
|||||||
This function can be used for binary files like images as well.
|
This function can be used for binary files like images as well.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
request = urllib.request.Request(source_url)
|
|
||||||
default_headers = get_default_headers()
|
default_headers = get_default_headers()
|
||||||
if headers:
|
if headers:
|
||||||
default_headers.update(headers)
|
default_headers.update(headers)
|
||||||
for header_key, header_value in default_headers.items():
|
|
||||||
request.add_header(header_key, header_value)
|
response = requests.get(source_url, headers=default_headers, stream=True, timeout=60)
|
||||||
with urllib.request.urlopen(request) as response:
|
response.raise_for_status() # Raise HTTPError for bad responses (4xx or 5xx)
|
||||||
content = response.read()
|
|
||||||
with open(destination_path, 'wb') as file:
|
with open(destination_path, "wb") as file:
|
||||||
file.write(content)
|
for chunk in response.iter_content(chunk_size=8192):
|
||||||
content_type = response.headers.get('Content-Type', '').lower()
|
file.write(chunk)
|
||||||
if content_type.startswith('image/'):
|
|
||||||
img_type = imghdr.what(destination_path)
|
content_type = response.headers.get("Content-Type", "").lower()
|
||||||
if img_type is None:
|
if content_type.startswith("image/"):
|
||||||
return {"status": "success", "downloaded_from": source_url, "downloaded_to": destination_path, "is_valid_image": False, "warning": "Downloaded content is not a valid image, consider finding a different source."}
|
img_type = imghdr.what(destination_path)
|
||||||
else:
|
if img_type is None:
|
||||||
return {"status": "success", "downloaded_from": source_url, "downloaded_to": destination_path, "is_valid_image": True}
|
return {
|
||||||
|
"status": "success",
|
||||||
|
"downloaded_from": source_url,
|
||||||
|
"downloaded_to": destination_path,
|
||||||
|
"is_valid_image": False,
|
||||||
|
"warning": "Downloaded content is not a valid image, consider finding a different source.",
|
||||||
|
}
|
||||||
else:
|
else:
|
||||||
return {"status": "success", "downloaded_from": source_url, "downloaded_to": destination_path}
|
return {
|
||||||
except Exception as exception:
|
"status": "success",
|
||||||
return {"status": "error", "error": str(exception)}
|
"downloaded_from": source_url,
|
||||||
|
"downloaded_to": destination_path,
|
||||||
|
"is_valid_image": True,
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
"status": "success",
|
||||||
|
"downloaded_from": source_url,
|
||||||
|
"downloaded_to": destination_path,
|
||||||
|
}
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
return {"status": "error", "error": str(e)}
|
||||||
|
|
||||||
|
|
||||||
def _perform_search(base_url, query, params=None):
|
def _perform_search(
|
||||||
|
base_url: str, query: str, params: Optional[Dict[str, str]] = None
|
||||||
|
) -> Dict[str, Any]:
|
||||||
try:
|
try:
|
||||||
encoded_query = urllib.parse.quote(query)
|
|
||||||
full_url = f"{base_url}?query={encoded_query}"
|
|
||||||
request = urllib.request.Request(full_url)
|
|
||||||
default_headers = get_default_headers()
|
default_headers = get_default_headers()
|
||||||
for header_key, header_value in default_headers.items():
|
search_params = {"query": query}
|
||||||
request.add_header(header_key, header_value)
|
if params:
|
||||||
with urllib.request.urlopen(request) as response:
|
search_params.update(params)
|
||||||
content = response.read().decode("utf-8")
|
|
||||||
return {"status": "success", "content": json.loads(content)}
|
response = requests.get(base_url, headers=default_headers, params=search_params, timeout=30)
|
||||||
except Exception as exception:
|
response.raise_for_status() # Raise HTTPError for bad responses (4xx or 5xx)
|
||||||
return {"status": "error", "error": str(exception)}
|
|
||||||
|
return {"status": "success", "content": response.json()}
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
return {"status": "error", "error": str(e)}
|
||||||
|
|
||||||
|
|
||||||
def web_search(query):
|
def web_search(query: str) -> Dict[str, Any]:
|
||||||
"""Perform a web search.
|
"""Perform a web search.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -138,7 +158,7 @@ def web_search(query):
|
|||||||
return _perform_search(base_url, query)
|
return _perform_search(base_url, query)
|
||||||
|
|
||||||
|
|
||||||
def web_search_news(query):
|
def web_search_news(query: str) -> Dict[str, Any]:
|
||||||
"""Perform a web search for news.
|
"""Perform a web search for news.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import time
|
|
||||||
import threading
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
class Colors:
|
class Colors:
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import difflib
|
import difflib
|
||||||
from typing import Dict, List, Optional, Tuple
|
from typing import Dict, List, Optional, Tuple
|
||||||
|
|
||||||
from .colors import Colors
|
from .colors import Colors
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Dict, List, Optional
|
from typing import Dict, List, Optional
|
||||||
|
|
||||||
from .colors import Colors
|
from .colors import Colors
|
||||||
from .progress import ProgressBar
|
from .progress import ProgressBar
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from rp.config import LANGUAGE_KEYWORDS
|
from rp.config import LANGUAGE_KEYWORDS
|
||||||
from rp.ui.colors import Colors
|
from rp.ui.colors import Colors
|
||||||
|
|
||||||
|
|||||||
29
rp/vision.py
29
rp/vision.py
@ -1,9 +1,7 @@
|
|||||||
import http.client
|
|
||||||
import argparse
|
import argparse
|
||||||
import base64
|
import base64
|
||||||
import json
|
|
||||||
import http.client
|
|
||||||
import pathlib
|
import pathlib
|
||||||
|
import requests
|
||||||
|
|
||||||
DEFAULT_URL = "https://static.molodetz.nl/rp.vision.cgi"
|
DEFAULT_URL = "https://static.molodetz.nl/rp.vision.cgi"
|
||||||
|
|
||||||
@ -12,25 +10,24 @@ def post_image(image_path: str, prompt: str = "", url: str = DEFAULT_URL):
|
|||||||
image_path = str(pathlib.Path(image_path).resolve().absolute())
|
image_path = str(pathlib.Path(image_path).resolve().absolute())
|
||||||
if not url:
|
if not url:
|
||||||
url = DEFAULT_URL
|
url = DEFAULT_URL
|
||||||
url_parts = url.split("/")
|
|
||||||
host = url_parts[2]
|
|
||||||
path = "/" + "/".join(url_parts[3:])
|
|
||||||
with open(image_path, "rb") as file:
|
with open(image_path, "rb") as file:
|
||||||
image_data = file.read()
|
image_data = file.read()
|
||||||
base64_data = base64.b64encode(image_data).decode("utf-8")
|
base64_data = base64.b64encode(image_data).decode("utf-8")
|
||||||
|
|
||||||
payload = {"data": base64_data, "path": image_path, "prompt": prompt}
|
payload = {"data": base64_data, "path": image_path, "prompt": prompt}
|
||||||
body = json.dumps(payload).encode("utf-8")
|
|
||||||
headers = {
|
headers = {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"Content-Length": str(len(body)),
|
"User-Agent": "Python requests",
|
||||||
"User-Agent": "Python http.client",
|
|
||||||
}
|
}
|
||||||
conn = http.client.HTTPSConnection(host)
|
|
||||||
conn.request("POST", path, body, headers)
|
try:
|
||||||
resp = conn.getresponse()
|
response = requests.post(url, json=payload, headers=headers, timeout=60)
|
||||||
data = resp.read()
|
response.raise_for_status() # Raise HTTPError for bad responses (4xx or 5xx)
|
||||||
print("Status:", resp.status, resp.reason)
|
print("Status:", response.status_code, response.reason)
|
||||||
print(data.decode())
|
print(response.text)
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
print(f"Error posting image: {e}")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
@ -39,4 +36,4 @@ if __name__ == "__main__":
|
|||||||
parser.add_argument("--prompt", default="")
|
parser.add_argument("--prompt", default="")
|
||||||
parser.add_argument("--url", default=DEFAULT_URL)
|
parser.add_argument("--url", default=DEFAULT_URL)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
post_image(args.url, args.image_path, args.prompt)
|
post_image(args.image_path, args.prompt, args.url)
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import re
|
|||||||
import time
|
import time
|
||||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||||
from typing import Any, Callable, Dict, List, Optional
|
from typing import Any, Callable, Dict, List, Optional
|
||||||
|
|
||||||
from .workflow_definition import ExecutionMode, Workflow, WorkflowStep
|
from .workflow_definition import ExecutionMode, Workflow, WorkflowStep
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import json
|
|||||||
import sqlite3
|
import sqlite3
|
||||||
import time
|
import time
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
from .workflow_definition import Workflow
|
from .workflow_definition import Workflow
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import unittest
|
|
||||||
import json
|
import json
|
||||||
|
import unittest
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from rp.core.api import call_api, list_models
|
from rp.core.api import call_api, list_models
|
||||||
|
|||||||
@ -1,28 +1,29 @@
|
|||||||
from unittest.mock import Mock, patch
|
from unittest.mock import Mock, patch
|
||||||
|
|
||||||
from rp.commands.handlers import (
|
from rp.commands.handlers import (
|
||||||
handle_command,
|
|
||||||
review_file,
|
|
||||||
refactor_file,
|
|
||||||
obfuscate_file,
|
|
||||||
show_workflows,
|
|
||||||
execute_workflow_command,
|
|
||||||
execute_agent_task,
|
|
||||||
show_agents,
|
|
||||||
collaborate_agents_command,
|
|
||||||
search_knowledge,
|
|
||||||
store_knowledge,
|
|
||||||
show_conversation_history,
|
|
||||||
show_cache_stats,
|
|
||||||
clear_caches,
|
clear_caches,
|
||||||
show_system_stats,
|
collaborate_agents_command,
|
||||||
|
execute_agent_task,
|
||||||
|
execute_workflow_command,
|
||||||
handle_background_command,
|
handle_background_command,
|
||||||
start_background_session,
|
handle_command,
|
||||||
list_background_sessions,
|
|
||||||
show_session_status,
|
|
||||||
show_session_output,
|
|
||||||
send_session_input,
|
|
||||||
kill_background_session,
|
kill_background_session,
|
||||||
|
list_background_sessions,
|
||||||
|
obfuscate_file,
|
||||||
|
refactor_file,
|
||||||
|
review_file,
|
||||||
|
search_knowledge,
|
||||||
|
send_session_input,
|
||||||
|
show_agents,
|
||||||
show_background_events,
|
show_background_events,
|
||||||
|
show_cache_stats,
|
||||||
|
show_conversation_history,
|
||||||
|
show_session_output,
|
||||||
|
show_session_status,
|
||||||
|
show_system_stats,
|
||||||
|
show_workflows,
|
||||||
|
start_background_session,
|
||||||
|
store_knowledge,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
|
import os
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import tempfile
|
import tempfile
|
||||||
import os
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from rp.memory.conversation_memory import ConversationMemory
|
from rp.memory.conversation_memory import ConversationMemory
|
||||||
@ -337,8 +337,8 @@ class TestConversationMemory:
|
|||||||
|
|
||||||
def test_thread_safety(self):
|
def test_thread_safety(self):
|
||||||
"""Test that the memory can handle concurrent access."""
|
"""Test that the memory can handle concurrent access."""
|
||||||
import threading
|
|
||||||
import queue
|
import queue
|
||||||
|
import threading
|
||||||
|
|
||||||
results = queue.Queue()
|
results = queue.Queue()
|
||||||
|
|
||||||
|
|||||||
@ -1,15 +1,16 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from rp.core.exceptions import (
|
from rp.core.exceptions import (
|
||||||
PRException,
|
|
||||||
APIException,
|
|
||||||
APIConnectionError,
|
APIConnectionError,
|
||||||
APITimeoutError,
|
APIException,
|
||||||
APIResponseError,
|
APIResponseError,
|
||||||
|
APITimeoutError,
|
||||||
ConfigurationError,
|
ConfigurationError,
|
||||||
ToolExecutionError,
|
|
||||||
FileSystemError,
|
|
||||||
SessionError,
|
|
||||||
ContextError,
|
ContextError,
|
||||||
|
FileSystemError,
|
||||||
|
PRException,
|
||||||
|
SessionError,
|
||||||
|
ToolExecutionError,
|
||||||
ValidationError,
|
ValidationError,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
from rp.commands.help_docs import (
|
from rp.commands.help_docs import (
|
||||||
get_workflow_help,
|
|
||||||
get_agent_help,
|
get_agent_help,
|
||||||
get_knowledge_help,
|
|
||||||
get_cache_help,
|
|
||||||
get_background_help,
|
get_background_help,
|
||||||
|
get_cache_help,
|
||||||
get_full_help,
|
get_full_help,
|
||||||
|
get_knowledge_help,
|
||||||
|
get_workflow_help,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
|
import os
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import tempfile
|
import tempfile
|
||||||
import os
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from rp.memory.knowledge_store import KnowledgeStore, KnowledgeEntry
|
from rp.memory.knowledge_store import KnowledgeEntry, KnowledgeStore
|
||||||
|
|
||||||
|
|
||||||
class TestKnowledgeStore:
|
class TestKnowledgeStore:
|
||||||
@ -284,8 +284,8 @@ class TestKnowledgeStore:
|
|||||||
|
|
||||||
def test_thread_safety(self):
|
def test_thread_safety(self):
|
||||||
"""Test that the store can handle concurrent access."""
|
"""Test that the store can handle concurrent access."""
|
||||||
import threading
|
|
||||||
import queue
|
import queue
|
||||||
|
import threading
|
||||||
|
|
||||||
results = queue.Queue()
|
results = queue.Queue()
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
from unittest.mock import patch, MagicMock
|
from unittest.mock import MagicMock, patch
|
||||||
from rp.core.logging import setup_logging, get_logger
|
|
||||||
|
from rp.core.logging import get_logger, setup_logging
|
||||||
|
|
||||||
|
|
||||||
class TestLogging:
|
class TestLogging:
|
||||||
|
|||||||
@ -1,13 +1,14 @@
|
|||||||
from unittest.mock import Mock, patch
|
from unittest.mock import Mock, patch
|
||||||
|
|
||||||
from rp.commands.multiplexer_commands import (
|
from rp.commands.multiplexer_commands import (
|
||||||
show_sessions,
|
|
||||||
attach_session,
|
attach_session,
|
||||||
detach_session,
|
detach_session,
|
||||||
kill_session,
|
kill_session,
|
||||||
|
list_waiting_sessions,
|
||||||
send_command,
|
send_command,
|
||||||
show_session_log,
|
show_session_log,
|
||||||
show_session_status,
|
show_session_status,
|
||||||
list_waiting_sessions,
|
show_sessions,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import math
|
import math
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from rp.memory.semantic_index import SemanticIndex
|
from rp.memory.semantic_index import SemanticIndex
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import json
|
import json
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from rp.ui.output import OutputFormatter
|
from rp.ui.output import OutputFormatter
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user