Spaces:
Running
Running
import gevent.monkey | |
gevent.monkey.patch_all(asyncio=True) # Keep this at the very top | |
import asyncio # Keep this | |
from flask import Flask, request, jsonify | |
from proxy_lite import Runner, RunnerConfig | |
import os | |
import logging | |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') | |
logger = logging.getLogger(__name__) | |
app = Flask(__name__) | |
_runner = None | |
async def initialize_runner(): | |
global _runner | |
if _runner is None: | |
logger.info("Initializing Proxy-lite Runner...") | |
hf_api_token = os.environ.get("HF_API_TOKEN") | |
if not hf_api_token: | |
logger.error("HF_API_TOKEN environment variable not set. Cannot initialize Runner.") | |
raise ValueError("HF_API_TOKEN environment variable not set. Please set it as a Space secret.") | |
config = RunnerConfig.from_dict({ | |
"environment": { | |
"name": "webbrowser", | |
# Set homepage to Salesforce's generic login URL to avoid premature waits for target page elements. | |
"homepage": "https://login.salesforce.com/", | |
"headless": False, # Keep this False for local testing | |
"launch_args": ["--no-sandbox", "--disable-setuid-sandbox"], | |
"screenshot_delay": 0.5, # Reduced for faster debugging cycles | |
"include_html": True, | |
"include_poi_text": True, | |
}, | |
"solver": { | |
"name": "simple", | |
"agent": { | |
"name": "proxy_lite", | |
"client": { | |
"name": "convergence", | |
"model_id": "convergence-ai/proxy-lite-3b", | |
"api_base": "https://convergence-ai-demo-api.hf.space/v1", | |
"api_key": hf_api_token | |
} | |
} | |
}, | |
"environment_timeout": 1800.0, | |
"action_timeout": 1800.0, | |
"task_timeout": 18000.0, | |
"max_steps": 150, | |
"logger_level": "DEBUG", | |
}) | |
logger.info(f"DEBUG: app.py - Initializing Runner with environment_timeout: {config.environment_timeout} seconds") | |
logger.info(f"DEBUG: app.py - Full config used: {config.model_dump_json(indent=2)}") | |
_runner = Runner(config=config) | |
logger.info("Proxy-lite Runner initialized successfully.") | |
return _runner | |
async def run_proxy_task_endpoint(): | |
data = request.json | |
request_task_instruction = data.get('task') | |
if not request_task_instruction: | |
logger.warning("Received request without 'task' field. Returning 400.") | |
return jsonify({"error": "No 'task' provided in request body"}), 400 | |
logger.info(f"Received user request task: '{request_task_instruction}'") | |
salesforce_username = os.environ.get("SALESFORCE_USERNAME") | |
salesforce_password = os.environ.get("SALESFORCE_PASSWORD") | |
if not salesforce_username or not salesforce_password: | |
logger.error("Salesforce credentials (SALESFORCE_USERNAME, SALESFORCE_PASSWORD) environment variables not set.") | |
return jsonify({"error": "Salesforce credentials not configured. Please set SALESFORCE_USERNAME and SALESFORCE_PASSWORD as Space secrets."}), 500 | |
# Define the specific Account Forecast Settings URL | |
account_forecast_url = "https://dwd000006jia1mae.lightning.force.com/lightning/setup/AccountForecastSettings/home" | |
# Define the tool code block to open a new tab and navigate after login | |
# Using a raw f-string for multiline tool code block | |
tool_code_block_new_tab = fr""" | |
<tool_code> | |
await browser.open_new_tab_and_go_to(url='{account_forecast_url}') | |
</tool_code> | |
""" | |
# Refined agent_task instruction to be sequential and robust to Salesforce redirects | |
agent_task = f""" | |
**Task Instructions for Proxy Lite Agent:** | |
1. **Start on Login Page:** Navigate to the Salesforce login page. | |
2. **Perform Login:** Log in to Salesforce using the provided username '{salesforce_username}' and password '{salesforce_password}'. Ensure all login fields are filled and the 'Log In' button is clicked. | |
3. **Handle Post-Login Redirect:** After clicking the 'Log In' button: | |
* Observe the current URL. If the URL has changed from the initial login domain (e.g., from `login.salesforce.com` or `my.salesforce.com`) **immediately execute the following tool code block to open a new tab and navigate directly to the Account Forecast Settings page (`{account_forecast_url}`) to bypass any persistent loading issues or internal redirects:** | |
{tool_code_block_new_tab.strip()} | |
4. **Confirm Target Page Load:** After successfully navigating to '{account_forecast_url}' (either directly after login or via the new tab strategy), ensure the page is fully loaded and stable. This means no loading spinners should be visible, and the main content for 'Account Forecast Settings' (like a clear heading, relevant toggles, or data tables) should be present and interactive. | |
5. **Execute Main Task:** Once the Account Forecast Settings page is confirmed loaded and stable, proceed with the original user request: {request_task_instruction}. | |
6. **Report Final Status:** Report the final status of the requested action, confirming both successful login and complete page load of the Account Forecast Settings. | |
""" | |
logger.info(f"Executing agent task (truncated for log): '{agent_task[:500]}...'") | |
try: | |
runner = await initialize_runner() | |
result = await runner.run(agent_task) | |
logger.info(f"Proxy-lite task completed. Output (truncated for log): {result[:500]}...") | |
return jsonify({"output": result}) | |
except Exception as e: | |
logger.exception(f"Error processing Salesforce task: {e}") | |
return jsonify({"error": f"An error occurred: {str(e)}. Check logs for details."}), 500 | |
def root(): | |
logger.info("Root endpoint accessed.") | |
return "Proxy-lite API is running. Send POST requests to /run_proxy_task with a 'task' in JSON body." | |
if __name__ == '__main__': | |
# It is crucial to set HF_API_TOKEN as an environment variable (e.g., in a .env file or directly) | |
# for local testing as well, otherwise initialize_runner will fail. | |
if not os.environ.get("HF_API_TOKEN"): | |
logger.error("HF_API_TOKEN environment variable is not set. Please set it for local testing.") | |
# Removed exit(1) to allow the Flask app to start for basic connectivity checks, | |
# but runner initialization will still fail if token is missing. | |
# For full functionality, the token is essential. | |
logger.info("Starting Flask development server on 0.0.0.0:7860...") | |
app.run(host='0.0.0.0', port=7860, debug=True) |