optexity 0.1.2__py3-none-any.whl → 0.1.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- optexity/cli.py +1 -1
- optexity/examples/__init__.py +0 -0
- optexity/examples/add_example.py +88 -0
- optexity/examples/download_pdf_url.py +29 -0
- optexity/examples/extract_price_stockanalysis.py +44 -0
- optexity/examples/file_upload.py +59 -0
- optexity/examples/i94.py +126 -0
- optexity/examples/i94_travel_history.py +126 -0
- optexity/examples/peachstate_medicaid.py +201 -0
- optexity/examples/supabase_login.py +75 -0
- optexity/inference/__init__.py +0 -0
- optexity/inference/agents/__init__.py +0 -0
- optexity/inference/agents/error_handler/__init__.py +0 -0
- optexity/inference/agents/error_handler/error_handler.py +39 -0
- optexity/inference/agents/error_handler/prompt.py +60 -0
- optexity/inference/agents/index_prediction/__init__.py +0 -0
- optexity/inference/agents/index_prediction/action_prediction_locator_axtree.py +45 -0
- optexity/inference/agents/index_prediction/prompt.py +14 -0
- optexity/inference/agents/select_value_prediction/__init__.py +0 -0
- optexity/inference/agents/select_value_prediction/prompt.py +20 -0
- optexity/inference/agents/select_value_prediction/select_value_prediction.py +39 -0
- optexity/inference/agents/two_fa_extraction/__init__.py +0 -0
- optexity/inference/agents/two_fa_extraction/prompt.py +23 -0
- optexity/inference/agents/two_fa_extraction/two_fa_extraction.py +47 -0
- optexity/inference/child_process.py +251 -0
- optexity/inference/core/__init__.py +0 -0
- optexity/inference/core/interaction/__init__.py +0 -0
- optexity/inference/core/interaction/handle_agentic_task.py +79 -0
- optexity/inference/core/interaction/handle_check.py +57 -0
- optexity/inference/core/interaction/handle_click.py +79 -0
- optexity/inference/core/interaction/handle_command.py +261 -0
- optexity/inference/core/interaction/handle_input.py +76 -0
- optexity/inference/core/interaction/handle_keypress.py +16 -0
- optexity/inference/core/interaction/handle_select.py +109 -0
- optexity/inference/core/interaction/handle_select_utils.py +132 -0
- optexity/inference/core/interaction/handle_upload.py +59 -0
- optexity/inference/core/interaction/utils.py +81 -0
- optexity/inference/core/logging.py +406 -0
- optexity/inference/core/run_assertion.py +55 -0
- optexity/inference/core/run_automation.py +463 -0
- optexity/inference/core/run_extraction.py +240 -0
- optexity/inference/core/run_interaction.py +254 -0
- optexity/inference/core/run_python_script.py +20 -0
- optexity/inference/core/run_two_fa.py +120 -0
- optexity/inference/core/two_factor_auth/__init__.py +0 -0
- optexity/inference/infra/__init__.py +0 -0
- optexity/inference/infra/browser.py +455 -0
- optexity/inference/infra/browser_extension.py +20 -0
- optexity/inference/models/__init__.py +22 -0
- optexity/inference/models/gemini.py +113 -0
- optexity/inference/models/human.py +20 -0
- optexity/inference/models/llm_model.py +210 -0
- optexity/inference/run_local.py +200 -0
- optexity/schema/__init__.py +0 -0
- optexity/schema/actions/__init__.py +0 -0
- optexity/schema/actions/assertion_action.py +66 -0
- optexity/schema/actions/extraction_action.py +143 -0
- optexity/schema/actions/interaction_action.py +330 -0
- optexity/schema/actions/misc_action.py +18 -0
- optexity/schema/actions/prompts.py +27 -0
- optexity/schema/actions/two_fa_action.py +24 -0
- optexity/schema/automation.py +432 -0
- optexity/schema/callback.py +16 -0
- optexity/schema/inference.py +87 -0
- optexity/schema/memory.py +100 -0
- optexity/schema/task.py +212 -0
- optexity/schema/token_usage.py +48 -0
- optexity/utils/__init__.py +0 -0
- optexity/utils/settings.py +54 -0
- optexity/utils/utils.py +76 -0
- {optexity-0.1.2.dist-info → optexity-0.1.4.dist-info}/METADATA +20 -36
- optexity-0.1.4.dist-info/RECORD +80 -0
- optexity-0.1.2.dist-info/RECORD +0 -11
- {optexity-0.1.2.dist-info → optexity-0.1.4.dist-info}/WHEEL +0 -0
- {optexity-0.1.2.dist-info → optexity-0.1.4.dist-info}/entry_points.txt +0 -0
- {optexity-0.1.2.dist-info → optexity-0.1.4.dist-info}/licenses/LICENSE +0 -0
- {optexity-0.1.2.dist-info → optexity-0.1.4.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import asyncio
|
|
3
|
+
import logging
|
|
4
|
+
from contextlib import asynccontextmanager
|
|
5
|
+
from datetime import datetime, timedelta, timezone
|
|
6
|
+
from urllib.parse import urljoin
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
from fastapi import Body, FastAPI
|
|
10
|
+
from fastapi.responses import JSONResponse
|
|
11
|
+
from pydantic import BaseModel
|
|
12
|
+
from uvicorn import run
|
|
13
|
+
|
|
14
|
+
from optexity.inference.core.run_automation import run_automation
|
|
15
|
+
from optexity.schema.inference import InferenceRequest
|
|
16
|
+
from optexity.schema.task import Task
|
|
17
|
+
from optexity.utils.settings import settings
|
|
18
|
+
|
|
19
|
+
logging.basicConfig(level=logging.INFO)
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ChildProcessIdRequest(BaseModel):
|
|
24
|
+
new_child_process_id: str
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
child_process_id = None
|
|
28
|
+
task_running = False
|
|
29
|
+
last_task_start_time = None
|
|
30
|
+
task_queue: asyncio.Queue[Task] = asyncio.Queue()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
async def task_processor():
|
|
34
|
+
"""Background worker that processes tasks from the queue one at a time."""
|
|
35
|
+
global task_running
|
|
36
|
+
global last_task_start_time
|
|
37
|
+
logger.info("Task processor started")
|
|
38
|
+
|
|
39
|
+
while True:
|
|
40
|
+
try:
|
|
41
|
+
# Get next task from queue (blocks until one is available)
|
|
42
|
+
task = await task_queue.get()
|
|
43
|
+
task_running = True
|
|
44
|
+
last_task_start_time = datetime.now()
|
|
45
|
+
await run_automation(task, child_process_id)
|
|
46
|
+
|
|
47
|
+
except asyncio.CancelledError:
|
|
48
|
+
logger.info("Task processor cancelled")
|
|
49
|
+
break
|
|
50
|
+
except Exception as e:
|
|
51
|
+
logger.error(f"Error in task processor: {e}")
|
|
52
|
+
finally:
|
|
53
|
+
|
|
54
|
+
task_running = False
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
async def register_with_master():
|
|
58
|
+
"""Register with master on startup (handles restarts automatically)."""
|
|
59
|
+
# Get my task metadata from ECS
|
|
60
|
+
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
61
|
+
response = await client.get("http://169.254.170.2/v3/task")
|
|
62
|
+
response.raise_for_status()
|
|
63
|
+
metadata = response.json()
|
|
64
|
+
|
|
65
|
+
my_task_arn = metadata["TaskARN"]
|
|
66
|
+
my_ip = metadata["Containers"][0]["Networks"][0]["IPv4Addresses"][0]
|
|
67
|
+
|
|
68
|
+
my_port = None
|
|
69
|
+
for binding in metadata["Containers"][0].get("NetworkBindings", []):
|
|
70
|
+
if binding["containerPort"] == settings.CHILD_PORT_OFFSET:
|
|
71
|
+
my_port = binding["hostPort"]
|
|
72
|
+
break
|
|
73
|
+
|
|
74
|
+
if not my_port:
|
|
75
|
+
logger.error("Could not find host port binding")
|
|
76
|
+
raise ValueError("Host port not found in metadata")
|
|
77
|
+
|
|
78
|
+
# Register with master
|
|
79
|
+
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
80
|
+
response = await client.post(
|
|
81
|
+
f"http://{settings.SERVER_URL}/register_child",
|
|
82
|
+
json={"task_arn": my_task_arn, "private_ip": my_ip, "port": my_port},
|
|
83
|
+
)
|
|
84
|
+
response.raise_for_status()
|
|
85
|
+
|
|
86
|
+
logger.info(f"Registered with master: {response.json()}")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def get_app_with_endpoints(is_aws: bool, child_id: int):
|
|
90
|
+
global child_process_id
|
|
91
|
+
child_process_id = child_id
|
|
92
|
+
|
|
93
|
+
@asynccontextmanager
|
|
94
|
+
async def lifespan(app: FastAPI):
|
|
95
|
+
"""Lifespan context manager for startup and shutdown."""
|
|
96
|
+
# Startup
|
|
97
|
+
|
|
98
|
+
if is_aws:
|
|
99
|
+
asyncio.create_task(register_with_master())
|
|
100
|
+
|
|
101
|
+
logger.info("Registered with master")
|
|
102
|
+
asyncio.create_task(task_processor())
|
|
103
|
+
logger.info("Task processor background task started")
|
|
104
|
+
yield
|
|
105
|
+
# Shutdown (if needed in the future)
|
|
106
|
+
logger.info("Shutting down task processor")
|
|
107
|
+
|
|
108
|
+
app = FastAPI(title="Optexity Inference", lifespan=lifespan)
|
|
109
|
+
|
|
110
|
+
@app.get("/is_task_running", tags=["info"])
|
|
111
|
+
async def is_task_running():
|
|
112
|
+
"""Is task running endpoint."""
|
|
113
|
+
return task_running
|
|
114
|
+
|
|
115
|
+
@app.get("/health", tags=["info"])
|
|
116
|
+
async def health():
|
|
117
|
+
"""Health check endpoint."""
|
|
118
|
+
global last_task_start_time
|
|
119
|
+
if (
|
|
120
|
+
task_running
|
|
121
|
+
and last_task_start_time
|
|
122
|
+
and datetime.now() - last_task_start_time > timedelta(minutes=15)
|
|
123
|
+
):
|
|
124
|
+
return JSONResponse(
|
|
125
|
+
status_code=503,
|
|
126
|
+
content={
|
|
127
|
+
"status": "unhealthy",
|
|
128
|
+
"message": "Task not finished in the last 15 minutes",
|
|
129
|
+
},
|
|
130
|
+
)
|
|
131
|
+
return JSONResponse(
|
|
132
|
+
status_code=200,
|
|
133
|
+
content={
|
|
134
|
+
"status": "healthy",
|
|
135
|
+
"task_running": task_running,
|
|
136
|
+
"queued_tasks": task_queue.qsize(),
|
|
137
|
+
},
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
@app.post("/set_child_process_id", tags=["info"])
|
|
141
|
+
async def set_child_process_id(request: ChildProcessIdRequest):
|
|
142
|
+
"""Set child process id endpoint."""
|
|
143
|
+
global child_process_id
|
|
144
|
+
child_process_id = int(request.new_child_process_id)
|
|
145
|
+
return JSONResponse(
|
|
146
|
+
content={"success": True, "message": "Child process id has been set"},
|
|
147
|
+
status_code=200,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
@app.post("/allocate_task")
|
|
151
|
+
async def allocate_task(task: Task = Body(...)):
|
|
152
|
+
"""Get details of a specific task."""
|
|
153
|
+
try:
|
|
154
|
+
|
|
155
|
+
await task_queue.put(task)
|
|
156
|
+
return JSONResponse(
|
|
157
|
+
content={"success": True, "message": "Task has been allocated"},
|
|
158
|
+
status_code=202,
|
|
159
|
+
)
|
|
160
|
+
except Exception as e:
|
|
161
|
+
logger.error(f"Error allocating task {task.task_id}: {e}")
|
|
162
|
+
return JSONResponse(
|
|
163
|
+
content={"success": False, "message": str(e)}, status_code=500
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
if not is_aws:
|
|
167
|
+
|
|
168
|
+
@app.post("/inference")
|
|
169
|
+
async def inference(inference_request: InferenceRequest = Body(...)):
|
|
170
|
+
response_data: dict | None = None
|
|
171
|
+
try:
|
|
172
|
+
|
|
173
|
+
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
174
|
+
url = urljoin(settings.SERVER_URL, settings.INFERENCE_ENDPOINT)
|
|
175
|
+
headers = {"x-api-key": settings.API_KEY}
|
|
176
|
+
response = await client.post(
|
|
177
|
+
url, json=inference_request.model_dump(), headers=headers
|
|
178
|
+
)
|
|
179
|
+
response_data = response.json()
|
|
180
|
+
response.raise_for_status()
|
|
181
|
+
|
|
182
|
+
task_data = response_data["task"]
|
|
183
|
+
|
|
184
|
+
task = Task.model_validate_json(task_data)
|
|
185
|
+
if task.use_proxy and settings.PROXY_URL is None:
|
|
186
|
+
raise ValueError(
|
|
187
|
+
"PROXY_URL is not set and is required when use_proxy is True"
|
|
188
|
+
)
|
|
189
|
+
task.allocated_at = datetime.now(timezone.utc)
|
|
190
|
+
await task_queue.put(task)
|
|
191
|
+
|
|
192
|
+
return JSONResponse(
|
|
193
|
+
content={
|
|
194
|
+
"success": True,
|
|
195
|
+
"message": "Task has been allocated",
|
|
196
|
+
"task_id": task.task_id,
|
|
197
|
+
},
|
|
198
|
+
status_code=202,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
except Exception as e:
|
|
202
|
+
error = str(e)
|
|
203
|
+
if response_data is not None:
|
|
204
|
+
error = response_data.get("error", str(e))
|
|
205
|
+
|
|
206
|
+
logger.error(f"❌ Error fetching recordings: {error}")
|
|
207
|
+
return JSONResponse({"success": False, "error": error}, status_code=500)
|
|
208
|
+
|
|
209
|
+
return app
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def main():
|
|
213
|
+
"""Main function to run the server."""
|
|
214
|
+
parser = argparse.ArgumentParser(
|
|
215
|
+
description="Dynamic API endpoint generator for Optexity recordings"
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
parser.add_argument(
|
|
219
|
+
"--host",
|
|
220
|
+
type=str,
|
|
221
|
+
default="0.0.0.0",
|
|
222
|
+
help="Host to bind the server to (default: 0.0.0.0)",
|
|
223
|
+
)
|
|
224
|
+
parser.add_argument(
|
|
225
|
+
"--port",
|
|
226
|
+
type=int,
|
|
227
|
+
help="Port to run the server ",
|
|
228
|
+
)
|
|
229
|
+
parser.add_argument(
|
|
230
|
+
"--child_process_id",
|
|
231
|
+
type=int,
|
|
232
|
+
help="Child process ID",
|
|
233
|
+
)
|
|
234
|
+
parser.add_argument(
|
|
235
|
+
"--is_aws",
|
|
236
|
+
action="store_true",
|
|
237
|
+
help="Is child process",
|
|
238
|
+
default=False,
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
args = parser.parse_args()
|
|
242
|
+
|
|
243
|
+
app = get_app_with_endpoints(is_aws=args.is_aws, child_id=args.child_process_id)
|
|
244
|
+
|
|
245
|
+
# Start the server (this is blocking and manages its own event loop)
|
|
246
|
+
logger.info(f"Starting server on {args.host}:{args.port}")
|
|
247
|
+
run(app, host=args.host, port=args.port)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
if __name__ == "__main__":
|
|
251
|
+
main()
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from browser_use import Agent, BrowserSession, ChatGoogle, Tools
|
|
4
|
+
|
|
5
|
+
from optexity.inference.infra.browser import Browser
|
|
6
|
+
from optexity.schema.actions.interaction_action import (
|
|
7
|
+
AgenticTask,
|
|
8
|
+
CloseOverlayPopupAction,
|
|
9
|
+
)
|
|
10
|
+
from optexity.schema.memory import Memory
|
|
11
|
+
from optexity.schema.task import Task
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
async def handle_agentic_task(
|
|
17
|
+
agentic_task_action: AgenticTask | CloseOverlayPopupAction,
|
|
18
|
+
task: Task,
|
|
19
|
+
memory: Memory,
|
|
20
|
+
browser: Browser,
|
|
21
|
+
):
|
|
22
|
+
|
|
23
|
+
if agentic_task_action.backend == "browser_use":
|
|
24
|
+
|
|
25
|
+
if isinstance(agentic_task_action, CloseOverlayPopupAction):
|
|
26
|
+
tools = Tools(
|
|
27
|
+
exclude_actions=[
|
|
28
|
+
"search",
|
|
29
|
+
"navigate",
|
|
30
|
+
"go_back",
|
|
31
|
+
"upload_file",
|
|
32
|
+
"scroll",
|
|
33
|
+
"find_text",
|
|
34
|
+
"send_keys",
|
|
35
|
+
"evaluate",
|
|
36
|
+
"switch",
|
|
37
|
+
"close",
|
|
38
|
+
"extract",
|
|
39
|
+
"dropdown_options",
|
|
40
|
+
"select_dropdown",
|
|
41
|
+
"write_file",
|
|
42
|
+
"read_file",
|
|
43
|
+
"replace_file",
|
|
44
|
+
]
|
|
45
|
+
)
|
|
46
|
+
else:
|
|
47
|
+
tools = Tools()
|
|
48
|
+
llm = ChatGoogle(model="gemini-flash-latest")
|
|
49
|
+
browser_session = BrowserSession(
|
|
50
|
+
cdp_url=browser.cdp_url, keep_alive=agentic_task_action.keep_alive
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
step_directory = (
|
|
54
|
+
task.logs_directory / f"step_{str(memory.automation_state.step_index)}"
|
|
55
|
+
)
|
|
56
|
+
step_directory.mkdir(parents=True, exist_ok=True)
|
|
57
|
+
|
|
58
|
+
agent = Agent(
|
|
59
|
+
task=agentic_task_action.task,
|
|
60
|
+
llm=llm,
|
|
61
|
+
browser_session=browser_session,
|
|
62
|
+
use_vision=agentic_task_action.use_vision,
|
|
63
|
+
tools=tools,
|
|
64
|
+
calculate_cost=True,
|
|
65
|
+
save_conversation_path=step_directory,
|
|
66
|
+
)
|
|
67
|
+
logger.debug(f"Starting browser session for agentic task {browser.cdp_url} ")
|
|
68
|
+
await agent.browser_session.start()
|
|
69
|
+
logger.debug(f"Finally running agentic task on browser_use {browser.cdp_url} ")
|
|
70
|
+
await agent.run(max_steps=agentic_task_action.max_steps)
|
|
71
|
+
logger.debug(f"Agentic task completed on browser_use {browser.cdp_url} ")
|
|
72
|
+
|
|
73
|
+
agent.stop()
|
|
74
|
+
if agent.browser_session:
|
|
75
|
+
await agent.browser_session.stop()
|
|
76
|
+
await agent.browser_session.reset()
|
|
77
|
+
|
|
78
|
+
elif agentic_task_action.backend == "browserbase":
|
|
79
|
+
raise NotImplementedError("Browserbase is not supported yet")
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from optexity.inference.core.interaction.handle_command import (
|
|
4
|
+
command_based_action_with_retry,
|
|
5
|
+
)
|
|
6
|
+
from optexity.inference.infra.browser import Browser
|
|
7
|
+
from optexity.schema.actions.interaction_action import CheckAction, UncheckAction
|
|
8
|
+
from optexity.schema.memory import Memory
|
|
9
|
+
from optexity.schema.task import Task
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
async def handle_check_element(
|
|
15
|
+
check_element_action: CheckAction,
|
|
16
|
+
task: Task,
|
|
17
|
+
memory: Memory,
|
|
18
|
+
browser: Browser,
|
|
19
|
+
max_timeout_seconds_per_try: float,
|
|
20
|
+
max_tries: int,
|
|
21
|
+
):
|
|
22
|
+
|
|
23
|
+
if check_element_action.command and not check_element_action.skip_command:
|
|
24
|
+
last_error = await command_based_action_with_retry(
|
|
25
|
+
check_element_action,
|
|
26
|
+
browser,
|
|
27
|
+
memory,
|
|
28
|
+
task,
|
|
29
|
+
max_tries,
|
|
30
|
+
max_timeout_seconds_per_try,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
if last_error is None:
|
|
34
|
+
return
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
async def handle_uncheck_element(
|
|
38
|
+
uncheck_element_action: UncheckAction,
|
|
39
|
+
task: Task,
|
|
40
|
+
memory: Memory,
|
|
41
|
+
browser: Browser,
|
|
42
|
+
max_timeout_seconds_per_try: float,
|
|
43
|
+
max_tries: int,
|
|
44
|
+
):
|
|
45
|
+
|
|
46
|
+
if uncheck_element_action.command and not uncheck_element_action.skip_command:
|
|
47
|
+
last_error = await command_based_action_with_retry(
|
|
48
|
+
uncheck_element_action,
|
|
49
|
+
browser,
|
|
50
|
+
memory,
|
|
51
|
+
task,
|
|
52
|
+
max_tries,
|
|
53
|
+
max_timeout_seconds_per_try,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
if last_error is None:
|
|
57
|
+
return
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from optexity.inference.core.interaction.handle_command import (
|
|
4
|
+
command_based_action_with_retry,
|
|
5
|
+
)
|
|
6
|
+
from optexity.inference.core.interaction.utils import (
|
|
7
|
+
get_index_from_prompt,
|
|
8
|
+
handle_download,
|
|
9
|
+
)
|
|
10
|
+
from optexity.inference.infra.browser import Browser
|
|
11
|
+
from optexity.schema.actions.interaction_action import ClickElementAction
|
|
12
|
+
from optexity.schema.memory import Memory
|
|
13
|
+
from optexity.schema.task import Task
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
async def handle_click_element(
|
|
19
|
+
click_element_action: ClickElementAction,
|
|
20
|
+
task: Task,
|
|
21
|
+
memory: Memory,
|
|
22
|
+
browser: Browser,
|
|
23
|
+
max_timeout_seconds_per_try: float,
|
|
24
|
+
max_tries: int,
|
|
25
|
+
):
|
|
26
|
+
|
|
27
|
+
if click_element_action.command and not click_element_action.skip_command:
|
|
28
|
+
last_error = await command_based_action_with_retry(
|
|
29
|
+
click_element_action,
|
|
30
|
+
browser,
|
|
31
|
+
memory,
|
|
32
|
+
task,
|
|
33
|
+
max_tries,
|
|
34
|
+
max_timeout_seconds_per_try,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
if last_error is None:
|
|
38
|
+
return
|
|
39
|
+
|
|
40
|
+
if not click_element_action.skip_prompt:
|
|
41
|
+
logger.debug(
|
|
42
|
+
f"Executing prompt-based action: {click_element_action.__class__.__name__}"
|
|
43
|
+
)
|
|
44
|
+
await click_element_index(click_element_action, browser, memory, task)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
async def click_element_index(
|
|
48
|
+
click_element_action: ClickElementAction,
|
|
49
|
+
browser: Browser,
|
|
50
|
+
memory: Memory,
|
|
51
|
+
task: Task,
|
|
52
|
+
):
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
index = await get_index_from_prompt(
|
|
56
|
+
memory, click_element_action.prompt_instructions, browser
|
|
57
|
+
)
|
|
58
|
+
if index is None:
|
|
59
|
+
return
|
|
60
|
+
|
|
61
|
+
async def _actual_click_element():
|
|
62
|
+
action_model = browser.backend_agent.ActionModel(
|
|
63
|
+
**{"click": {"index": index}}
|
|
64
|
+
)
|
|
65
|
+
await browser.backend_agent.multi_act([action_model])
|
|
66
|
+
|
|
67
|
+
if click_element_action.expect_download:
|
|
68
|
+
await handle_download(
|
|
69
|
+
_actual_click_element,
|
|
70
|
+
memory,
|
|
71
|
+
browser,
|
|
72
|
+
task,
|
|
73
|
+
click_element_action.download_filename,
|
|
74
|
+
)
|
|
75
|
+
else:
|
|
76
|
+
await _actual_click_element()
|
|
77
|
+
except Exception as e:
|
|
78
|
+
logger.error(f"Error in click_element_index: {e}")
|
|
79
|
+
return
|