optexity 0.1.2__py3-none-any.whl → 0.1.3__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/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.3.dist-info}/METADATA +1 -1
- optexity-0.1.3.dist-info/RECORD +80 -0
- optexity-0.1.2.dist-info/RECORD +0 -11
- {optexity-0.1.2.dist-info → optexity-0.1.3.dist-info}/WHEEL +0 -0
- {optexity-0.1.2.dist-info → optexity-0.1.3.dist-info}/entry_points.txt +0 -0
- {optexity-0.1.2.dist-info → optexity-0.1.3.dist-info}/licenses/LICENSE +0 -0
- {optexity-0.1.2.dist-info → optexity-0.1.3.dist-info}/top_level.txt +0 -0
|
@@ -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
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
from playwright.async_api import Locator
|
|
5
|
+
|
|
6
|
+
from optexity.exceptions import AssertLocatorPresenceException
|
|
7
|
+
from optexity.inference.core.interaction.handle_select_utils import (
|
|
8
|
+
SelectOptionValue,
|
|
9
|
+
smart_select,
|
|
10
|
+
)
|
|
11
|
+
from optexity.inference.core.interaction.utils import handle_download
|
|
12
|
+
from optexity.inference.infra.browser import Browser
|
|
13
|
+
from optexity.schema.actions.interaction_action import (
|
|
14
|
+
CheckAction,
|
|
15
|
+
ClickElementAction,
|
|
16
|
+
InputTextAction,
|
|
17
|
+
SelectOptionAction,
|
|
18
|
+
UncheckAction,
|
|
19
|
+
UploadFileAction,
|
|
20
|
+
)
|
|
21
|
+
from optexity.schema.memory import BrowserState, Memory
|
|
22
|
+
from optexity.schema.task import Task
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
async def command_based_action_with_retry(
|
|
28
|
+
action: (
|
|
29
|
+
ClickElementAction
|
|
30
|
+
| InputTextAction
|
|
31
|
+
| SelectOptionAction
|
|
32
|
+
| CheckAction
|
|
33
|
+
| UploadFileAction
|
|
34
|
+
| UncheckAction
|
|
35
|
+
),
|
|
36
|
+
browser: Browser,
|
|
37
|
+
memory: Memory,
|
|
38
|
+
task: Task,
|
|
39
|
+
max_tries: int,
|
|
40
|
+
max_timeout_seconds_per_try: float,
|
|
41
|
+
):
|
|
42
|
+
|
|
43
|
+
if action.command is None or action.skip_command:
|
|
44
|
+
return
|
|
45
|
+
|
|
46
|
+
last_error = None
|
|
47
|
+
|
|
48
|
+
logger.debug(f"Executing command-based action: {action.__class__.__name__}")
|
|
49
|
+
|
|
50
|
+
for try_index in range(max_tries):
|
|
51
|
+
last_error = None
|
|
52
|
+
try:
|
|
53
|
+
# https://playwright.dev/docs/actionability
|
|
54
|
+
locator = await browser.get_locator_from_command(action.command)
|
|
55
|
+
if try_index == 0:
|
|
56
|
+
try:
|
|
57
|
+
await locator.wait_for(
|
|
58
|
+
state="visible", timeout=max_timeout_seconds_per_try * 1000
|
|
59
|
+
)
|
|
60
|
+
except Exception as e:
|
|
61
|
+
pass
|
|
62
|
+
is_visible = await locator.is_visible()
|
|
63
|
+
|
|
64
|
+
if is_visible:
|
|
65
|
+
browser_state_summary = await browser.get_browser_state_summary()
|
|
66
|
+
memory.browser_states[-1] = BrowserState(
|
|
67
|
+
url=browser_state_summary.url,
|
|
68
|
+
screenshot=browser_state_summary.screenshot,
|
|
69
|
+
title=browser_state_summary.title,
|
|
70
|
+
axtree=browser_state_summary.dom_state.llm_representation(),
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
if isinstance(action, ClickElementAction):
|
|
74
|
+
await click_locator(
|
|
75
|
+
action,
|
|
76
|
+
locator,
|
|
77
|
+
browser,
|
|
78
|
+
memory,
|
|
79
|
+
task,
|
|
80
|
+
max_timeout_seconds_per_try,
|
|
81
|
+
)
|
|
82
|
+
elif isinstance(action, InputTextAction):
|
|
83
|
+
await input_text_locator(
|
|
84
|
+
action, locator, max_timeout_seconds_per_try
|
|
85
|
+
)
|
|
86
|
+
elif isinstance(action, SelectOptionAction):
|
|
87
|
+
await select_option_locator(
|
|
88
|
+
action,
|
|
89
|
+
locator,
|
|
90
|
+
browser,
|
|
91
|
+
memory,
|
|
92
|
+
task,
|
|
93
|
+
max_timeout_seconds_per_try,
|
|
94
|
+
)
|
|
95
|
+
elif isinstance(action, CheckAction):
|
|
96
|
+
await check_locator(
|
|
97
|
+
action, locator, max_timeout_seconds_per_try, browser
|
|
98
|
+
)
|
|
99
|
+
elif isinstance(action, UncheckAction):
|
|
100
|
+
await uncheck_locator(
|
|
101
|
+
action, locator, max_timeout_seconds_per_try, browser
|
|
102
|
+
)
|
|
103
|
+
elif isinstance(action, UploadFileAction):
|
|
104
|
+
await upload_file_locator(action, locator)
|
|
105
|
+
logger.debug(
|
|
106
|
+
f"{action.__class__.__name__} successful on try {try_index + 1}"
|
|
107
|
+
)
|
|
108
|
+
return
|
|
109
|
+
else:
|
|
110
|
+
await asyncio.sleep(max_timeout_seconds_per_try)
|
|
111
|
+
last_error = f"error: locator not visible"
|
|
112
|
+
except Exception as e:
|
|
113
|
+
last_error = f"error: {e}"
|
|
114
|
+
await asyncio.sleep(max_timeout_seconds_per_try)
|
|
115
|
+
|
|
116
|
+
if last_error is None:
|
|
117
|
+
last_error = "error in executing command"
|
|
118
|
+
logger.debug(
|
|
119
|
+
f"{action.__class__.__name__} failed after {max_tries} tries: {last_error}"
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
if last_error and action.assert_locator_presence:
|
|
123
|
+
logger.debug(
|
|
124
|
+
f"Error in {action.__class__.__name__} with assert_locator_presence: {action.__class__.__name__}: {last_error}"
|
|
125
|
+
)
|
|
126
|
+
raise AssertLocatorPresenceException(
|
|
127
|
+
message=f"Error in {action.__class__.__name__} with assert_locator_presence: {action.__class__.__name__}",
|
|
128
|
+
original_error=last_error,
|
|
129
|
+
command=action.command,
|
|
130
|
+
)
|
|
131
|
+
return last_error
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
async def click_locator(
|
|
135
|
+
click_element_action: ClickElementAction,
|
|
136
|
+
locator: Locator,
|
|
137
|
+
browser: Browser,
|
|
138
|
+
memory: Memory,
|
|
139
|
+
task: Task,
|
|
140
|
+
max_timeout_seconds_per_try: float,
|
|
141
|
+
):
|
|
142
|
+
async def _actual_click():
|
|
143
|
+
|
|
144
|
+
if click_element_action.double_click:
|
|
145
|
+
await locator.dblclick(
|
|
146
|
+
no_wait_after=True, timeout=max_timeout_seconds_per_try * 1000
|
|
147
|
+
)
|
|
148
|
+
else:
|
|
149
|
+
await locator.click(
|
|
150
|
+
no_wait_after=True, timeout=max_timeout_seconds_per_try * 1000
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
if click_element_action.expect_download:
|
|
154
|
+
await handle_download(
|
|
155
|
+
_actual_click, memory, browser, task, click_element_action.download_filename
|
|
156
|
+
)
|
|
157
|
+
else:
|
|
158
|
+
await _actual_click()
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
async def input_text_locator(
|
|
162
|
+
input_text_action: InputTextAction,
|
|
163
|
+
locator: Locator,
|
|
164
|
+
max_timeout_seconds_per_try: float,
|
|
165
|
+
):
|
|
166
|
+
|
|
167
|
+
if input_text_action.fill_or_type == "fill":
|
|
168
|
+
await locator.fill(
|
|
169
|
+
input_text_action.input_text,
|
|
170
|
+
no_wait_after=True,
|
|
171
|
+
timeout=max_timeout_seconds_per_try * 1000,
|
|
172
|
+
)
|
|
173
|
+
else:
|
|
174
|
+
await locator.type(
|
|
175
|
+
input_text_action.input_text,
|
|
176
|
+
no_wait_after=True,
|
|
177
|
+
timeout=max_timeout_seconds_per_try * 1000,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
if input_text_action.press_enter:
|
|
181
|
+
await locator.press("Enter")
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
async def check_locator(
|
|
185
|
+
action: CheckAction,
|
|
186
|
+
locator: Locator,
|
|
187
|
+
max_timeout_seconds_per_try: float,
|
|
188
|
+
browser: Browser,
|
|
189
|
+
):
|
|
190
|
+
await locator.uncheck(
|
|
191
|
+
no_wait_after=True, timeout=max_timeout_seconds_per_try * 1000
|
|
192
|
+
)
|
|
193
|
+
await asyncio.sleep(1)
|
|
194
|
+
locator = await browser.get_locator_from_command(action.command)
|
|
195
|
+
await locator.check(no_wait_after=True, timeout=max_timeout_seconds_per_try * 1000)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
async def uncheck_locator(
|
|
199
|
+
action: UncheckAction,
|
|
200
|
+
locator: Locator,
|
|
201
|
+
max_timeout_seconds_per_try: float,
|
|
202
|
+
browser: Browser,
|
|
203
|
+
):
|
|
204
|
+
await locator.check(no_wait_after=True, timeout=max_timeout_seconds_per_try * 1000)
|
|
205
|
+
await asyncio.sleep(1)
|
|
206
|
+
locator = await browser.get_locator_from_command(action.command)
|
|
207
|
+
await locator.uncheck(
|
|
208
|
+
no_wait_after=True, timeout=max_timeout_seconds_per_try * 1000
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
async def upload_file_locator(upload_file_action: UploadFileAction, locator: Locator):
|
|
213
|
+
await locator.set_input_files(upload_file_action.file_path)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
async def select_option_locator(
|
|
217
|
+
select_option_action: SelectOptionAction,
|
|
218
|
+
locator: Locator,
|
|
219
|
+
browser: Browser,
|
|
220
|
+
memory: Memory,
|
|
221
|
+
task: Task,
|
|
222
|
+
max_timeout_seconds_per_try: float,
|
|
223
|
+
):
|
|
224
|
+
async def _actual_select_option():
|
|
225
|
+
options: list[dict[str, str]] = await locator.evaluate(
|
|
226
|
+
"""
|
|
227
|
+
sel => Array.from(sel.options).map(o => ({
|
|
228
|
+
value: o.value,
|
|
229
|
+
label: o.label || o.textContent
|
|
230
|
+
}))
|
|
231
|
+
"""
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
select_option_values = [
|
|
235
|
+
SelectOptionValue(value=o["value"], label=o["label"]) for o in options
|
|
236
|
+
]
|
|
237
|
+
|
|
238
|
+
matched_values = await smart_select(
|
|
239
|
+
select_option_values, options, select_option_action.select_values, memory
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
logger.debug(
|
|
243
|
+
f"Matched values for {select_option_action.command}: {matched_values}"
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
await locator.select_option(
|
|
247
|
+
matched_values,
|
|
248
|
+
no_wait_after=True,
|
|
249
|
+
timeout=max_timeout_seconds_per_try * 1000,
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
if select_option_action.expect_download:
|
|
253
|
+
await handle_download(
|
|
254
|
+
_actual_select_option,
|
|
255
|
+
memory,
|
|
256
|
+
browser,
|
|
257
|
+
task,
|
|
258
|
+
select_option_action.download_filename,
|
|
259
|
+
)
|
|
260
|
+
else:
|
|
261
|
+
await _actual_select_option()
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import re
|
|
3
|
+
|
|
4
|
+
from optexity.inference.core.interaction.handle_command import (
|
|
5
|
+
command_based_action_with_retry,
|
|
6
|
+
)
|
|
7
|
+
from optexity.inference.core.interaction.utils import get_index_from_prompt
|
|
8
|
+
from optexity.inference.infra.browser import Browser
|
|
9
|
+
from optexity.schema.actions.interaction_action import InputTextAction
|
|
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_input_text(
|
|
17
|
+
input_text_action: InputTextAction,
|
|
18
|
+
task: Task,
|
|
19
|
+
memory: Memory,
|
|
20
|
+
browser: Browser,
|
|
21
|
+
max_timeout_seconds_per_try: float,
|
|
22
|
+
max_tries: int,
|
|
23
|
+
):
|
|
24
|
+
|
|
25
|
+
# {some english chars [0]}
|
|
26
|
+
INT_INDEX_PATTERN = re.compile(r"^\{([A-Za-z_][A-Za-z0-9_]*)\[(\d+)\]\}$")
|
|
27
|
+
|
|
28
|
+
if INT_INDEX_PATTERN.match(input_text_action.input_text) is not None:
|
|
29
|
+
logger.debug(
|
|
30
|
+
"Skipping input text because input variable was not present for this step"
|
|
31
|
+
)
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
if input_text_action.command and not input_text_action.skip_command:
|
|
35
|
+
last_error = await command_based_action_with_retry(
|
|
36
|
+
input_text_action,
|
|
37
|
+
browser,
|
|
38
|
+
memory,
|
|
39
|
+
task,
|
|
40
|
+
max_tries,
|
|
41
|
+
max_timeout_seconds_per_try,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
if last_error is None:
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
if not input_text_action.skip_prompt:
|
|
48
|
+
logger.debug(
|
|
49
|
+
f"Executing prompt-based action: {input_text_action.__class__.__name__}"
|
|
50
|
+
)
|
|
51
|
+
await input_text_index(input_text_action, browser, memory)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
async def input_text_index(
|
|
55
|
+
input_text_action: InputTextAction, browser: Browser, memory: Memory
|
|
56
|
+
):
|
|
57
|
+
try:
|
|
58
|
+
index = await get_index_from_prompt(
|
|
59
|
+
memory, input_text_action.prompt_instructions, browser
|
|
60
|
+
)
|
|
61
|
+
if index is None:
|
|
62
|
+
return
|
|
63
|
+
|
|
64
|
+
action_model = browser.backend_agent.ActionModel(
|
|
65
|
+
**{
|
|
66
|
+
"input": {
|
|
67
|
+
"index": int(index),
|
|
68
|
+
"text": input_text_action.input_text,
|
|
69
|
+
"clear": True,
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
)
|
|
73
|
+
await browser.backend_agent.multi_act([action_model])
|
|
74
|
+
except Exception as e:
|
|
75
|
+
logger.error(f"Error in input_text_index: {e}")
|
|
76
|
+
return
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from optexity.inference.infra.browser import Browser
|
|
2
|
+
from optexity.schema.actions.interaction_action import KeyPressAction, KeyPressType
|
|
3
|
+
from optexity.schema.memory import Memory
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
async def handle_key_press(
|
|
7
|
+
keypress_action: KeyPressAction,
|
|
8
|
+
memory: Memory,
|
|
9
|
+
browser: Browser,
|
|
10
|
+
):
|
|
11
|
+
page = await browser.get_current_page()
|
|
12
|
+
if page is None:
|
|
13
|
+
return
|
|
14
|
+
|
|
15
|
+
if keypress_action.type == KeyPressType.ENTER:
|
|
16
|
+
await page.keyboard.press("Enter")
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from browser_use.dom.serializer.serializer import DOMTreeSerializer
|
|
4
|
+
|
|
5
|
+
from optexity.inference.core.interaction.handle_command import (
|
|
6
|
+
command_based_action_with_retry,
|
|
7
|
+
)
|
|
8
|
+
from optexity.inference.core.interaction.handle_select_utils import (
|
|
9
|
+
SelectOptionValue,
|
|
10
|
+
smart_select,
|
|
11
|
+
)
|
|
12
|
+
from optexity.inference.core.interaction.utils import (
|
|
13
|
+
get_index_from_prompt,
|
|
14
|
+
handle_download,
|
|
15
|
+
)
|
|
16
|
+
from optexity.inference.infra.browser import Browser
|
|
17
|
+
from optexity.schema.actions.interaction_action import SelectOptionAction
|
|
18
|
+
from optexity.schema.memory import Memory
|
|
19
|
+
from optexity.schema.task import Task
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
async def handle_select_option(
|
|
25
|
+
select_option_action: SelectOptionAction,
|
|
26
|
+
task: Task,
|
|
27
|
+
memory: Memory,
|
|
28
|
+
browser: Browser,
|
|
29
|
+
max_timeout_seconds_per_try: float,
|
|
30
|
+
max_tries: int,
|
|
31
|
+
):
|
|
32
|
+
|
|
33
|
+
if select_option_action.command and not select_option_action.skip_command:
|
|
34
|
+
last_error = await command_based_action_with_retry(
|
|
35
|
+
select_option_action,
|
|
36
|
+
browser,
|
|
37
|
+
memory,
|
|
38
|
+
task,
|
|
39
|
+
max_tries,
|
|
40
|
+
max_timeout_seconds_per_try,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
if last_error is None:
|
|
44
|
+
return
|
|
45
|
+
|
|
46
|
+
if not select_option_action.skip_prompt:
|
|
47
|
+
logger.debug(
|
|
48
|
+
f"Executing prompt-based action: {select_option_action.__class__.__name__}"
|
|
49
|
+
)
|
|
50
|
+
await select_option_index(select_option_action, browser, memory, task)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
async def select_option_index(
|
|
54
|
+
select_option_action: SelectOptionAction,
|
|
55
|
+
browser: Browser,
|
|
56
|
+
memory: Memory,
|
|
57
|
+
task: Task,
|
|
58
|
+
):
|
|
59
|
+
## TODO either perfect text match or agenic select value prediction
|
|
60
|
+
try:
|
|
61
|
+
|
|
62
|
+
index = await get_index_from_prompt(
|
|
63
|
+
memory, select_option_action.prompt_instructions, browser
|
|
64
|
+
)
|
|
65
|
+
if index is None:
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
node = await browser.backend_agent.browser_session.get_element_by_index(index)
|
|
69
|
+
if node is None:
|
|
70
|
+
return
|
|
71
|
+
|
|
72
|
+
select_option_values = DOMTreeSerializer(node)._extract_select_options(node)
|
|
73
|
+
if select_option_values is None:
|
|
74
|
+
return
|
|
75
|
+
|
|
76
|
+
all_options = select_option_values["all_options"]
|
|
77
|
+
|
|
78
|
+
all_options = [
|
|
79
|
+
SelectOptionValue(value=o["value"], label=o["text"]) for o in all_options
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
matched_values = await smart_select(
|
|
83
|
+
all_options, select_option_action.select_values, memory
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
async def _actual_select_option():
|
|
87
|
+
action_model = browser.backend_agent.ActionModel(
|
|
88
|
+
**{
|
|
89
|
+
"select_dropdown": {
|
|
90
|
+
"index": int(index),
|
|
91
|
+
"text": matched_values[0],
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
)
|
|
95
|
+
await browser.backend_agent.multi_act([action_model])
|
|
96
|
+
|
|
97
|
+
if select_option_action.expect_download:
|
|
98
|
+
await handle_download(
|
|
99
|
+
_actual_select_option,
|
|
100
|
+
memory,
|
|
101
|
+
browser,
|
|
102
|
+
task,
|
|
103
|
+
select_option_action.download_filename,
|
|
104
|
+
)
|
|
105
|
+
else:
|
|
106
|
+
await _actual_select_option()
|
|
107
|
+
except Exception as e:
|
|
108
|
+
logger.error(f"Error in select_option_index: {e}")
|
|
109
|
+
return
|