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.
Files changed (76) hide show
  1. optexity/examples/__init__.py +0 -0
  2. optexity/examples/add_example.py +88 -0
  3. optexity/examples/download_pdf_url.py +29 -0
  4. optexity/examples/extract_price_stockanalysis.py +44 -0
  5. optexity/examples/file_upload.py +59 -0
  6. optexity/examples/i94.py +126 -0
  7. optexity/examples/i94_travel_history.py +126 -0
  8. optexity/examples/peachstate_medicaid.py +201 -0
  9. optexity/examples/supabase_login.py +75 -0
  10. optexity/inference/__init__.py +0 -0
  11. optexity/inference/agents/__init__.py +0 -0
  12. optexity/inference/agents/error_handler/__init__.py +0 -0
  13. optexity/inference/agents/error_handler/error_handler.py +39 -0
  14. optexity/inference/agents/error_handler/prompt.py +60 -0
  15. optexity/inference/agents/index_prediction/__init__.py +0 -0
  16. optexity/inference/agents/index_prediction/action_prediction_locator_axtree.py +45 -0
  17. optexity/inference/agents/index_prediction/prompt.py +14 -0
  18. optexity/inference/agents/select_value_prediction/__init__.py +0 -0
  19. optexity/inference/agents/select_value_prediction/prompt.py +20 -0
  20. optexity/inference/agents/select_value_prediction/select_value_prediction.py +39 -0
  21. optexity/inference/agents/two_fa_extraction/__init__.py +0 -0
  22. optexity/inference/agents/two_fa_extraction/prompt.py +23 -0
  23. optexity/inference/agents/two_fa_extraction/two_fa_extraction.py +47 -0
  24. optexity/inference/child_process.py +251 -0
  25. optexity/inference/core/__init__.py +0 -0
  26. optexity/inference/core/interaction/__init__.py +0 -0
  27. optexity/inference/core/interaction/handle_agentic_task.py +79 -0
  28. optexity/inference/core/interaction/handle_check.py +57 -0
  29. optexity/inference/core/interaction/handle_click.py +79 -0
  30. optexity/inference/core/interaction/handle_command.py +261 -0
  31. optexity/inference/core/interaction/handle_input.py +76 -0
  32. optexity/inference/core/interaction/handle_keypress.py +16 -0
  33. optexity/inference/core/interaction/handle_select.py +109 -0
  34. optexity/inference/core/interaction/handle_select_utils.py +132 -0
  35. optexity/inference/core/interaction/handle_upload.py +59 -0
  36. optexity/inference/core/interaction/utils.py +81 -0
  37. optexity/inference/core/logging.py +406 -0
  38. optexity/inference/core/run_assertion.py +55 -0
  39. optexity/inference/core/run_automation.py +463 -0
  40. optexity/inference/core/run_extraction.py +240 -0
  41. optexity/inference/core/run_interaction.py +254 -0
  42. optexity/inference/core/run_python_script.py +20 -0
  43. optexity/inference/core/run_two_fa.py +120 -0
  44. optexity/inference/core/two_factor_auth/__init__.py +0 -0
  45. optexity/inference/infra/__init__.py +0 -0
  46. optexity/inference/infra/browser.py +455 -0
  47. optexity/inference/infra/browser_extension.py +20 -0
  48. optexity/inference/models/__init__.py +22 -0
  49. optexity/inference/models/gemini.py +113 -0
  50. optexity/inference/models/human.py +20 -0
  51. optexity/inference/models/llm_model.py +210 -0
  52. optexity/inference/run_local.py +200 -0
  53. optexity/schema/__init__.py +0 -0
  54. optexity/schema/actions/__init__.py +0 -0
  55. optexity/schema/actions/assertion_action.py +66 -0
  56. optexity/schema/actions/extraction_action.py +143 -0
  57. optexity/schema/actions/interaction_action.py +330 -0
  58. optexity/schema/actions/misc_action.py +18 -0
  59. optexity/schema/actions/prompts.py +27 -0
  60. optexity/schema/actions/two_fa_action.py +24 -0
  61. optexity/schema/automation.py +432 -0
  62. optexity/schema/callback.py +16 -0
  63. optexity/schema/inference.py +87 -0
  64. optexity/schema/memory.py +100 -0
  65. optexity/schema/task.py +212 -0
  66. optexity/schema/token_usage.py +48 -0
  67. optexity/utils/__init__.py +0 -0
  68. optexity/utils/settings.py +54 -0
  69. optexity/utils/utils.py +76 -0
  70. {optexity-0.1.2.dist-info → optexity-0.1.3.dist-info}/METADATA +1 -1
  71. optexity-0.1.3.dist-info/RECORD +80 -0
  72. optexity-0.1.2.dist-info/RECORD +0 -11
  73. {optexity-0.1.2.dist-info → optexity-0.1.3.dist-info}/WHEEL +0 -0
  74. {optexity-0.1.2.dist-info → optexity-0.1.3.dist-info}/entry_points.txt +0 -0
  75. {optexity-0.1.2.dist-info → optexity-0.1.3.dist-info}/licenses/LICENSE +0 -0
  76. {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