optexity 0.1.5.1__tar.gz → 0.1.5.3__tar.gz

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 (88) hide show
  1. {optexity-0.1.5.1 → optexity-0.1.5.3}/PKG-INFO +1 -1
  2. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/inference/core/interaction/handle_click.py +4 -1
  3. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/inference/core/interaction/handle_command.py +24 -3
  4. optexity-0.1.5.3/optexity/inference/core/interaction/handle_hover.py +83 -0
  5. optexity-0.1.5.3/optexity/inference/core/interaction/handle_keypress.py +42 -0
  6. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/inference/core/interaction/handle_select_utils.py +11 -1
  7. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/inference/core/logging.py +8 -1
  8. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/inference/core/run_automation.py +6 -3
  9. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/inference/core/run_extraction.py +37 -0
  10. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/inference/core/run_interaction.py +10 -0
  11. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/inference/core/run_two_fa.py +12 -7
  12. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/inference/infra/browser.py +187 -52
  13. optexity-0.1.5.3/optexity/inference/infra/utils.py +98 -0
  14. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/schema/actions/extraction_action.py +9 -3
  15. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/schema/actions/interaction_action.py +38 -4
  16. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/schema/actions/two_fa_action.py +1 -1
  17. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/schema/automation.py +2 -7
  18. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/schema/task.py +7 -2
  19. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/utils/utils.py +14 -0
  20. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity.egg-info/PKG-INFO +1 -1
  21. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity.egg-info/SOURCES.txt +2 -0
  22. {optexity-0.1.5.1 → optexity-0.1.5.3}/pyproject.toml +1 -1
  23. optexity-0.1.5.1/optexity/inference/core/interaction/handle_keypress.py +0 -16
  24. {optexity-0.1.5.1 → optexity-0.1.5.3}/LICENSE +0 -0
  25. {optexity-0.1.5.1 → optexity-0.1.5.3}/README.md +0 -0
  26. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/__init__.py +0 -0
  27. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/cli.py +0 -0
  28. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/examples/__init__.py +0 -0
  29. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/examples/add_example.py +0 -0
  30. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/examples/download_pdf_url.py +0 -0
  31. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/examples/extract_price_stockanalysis.py +0 -0
  32. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/examples/file_upload.py +0 -0
  33. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/examples/i94.py +0 -0
  34. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/examples/i94_travel_history.py +0 -0
  35. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/examples/peachstate_medicaid.py +0 -0
  36. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/examples/supabase_login.py +0 -0
  37. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/exceptions.py +0 -0
  38. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/inference/__init__.py +0 -0
  39. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/inference/agents/__init__.py +0 -0
  40. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/inference/agents/error_handler/__init__.py +0 -0
  41. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/inference/agents/error_handler/error_handler.py +0 -0
  42. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/inference/agents/error_handler/prompt.py +0 -0
  43. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/inference/agents/index_prediction/__init__.py +0 -0
  44. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/inference/agents/index_prediction/action_prediction_locator_axtree.py +0 -0
  45. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/inference/agents/index_prediction/prompt.py +0 -0
  46. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/inference/agents/select_value_prediction/__init__.py +0 -0
  47. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/inference/agents/select_value_prediction/prompt.py +0 -0
  48. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/inference/agents/select_value_prediction/select_value_prediction.py +0 -0
  49. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/inference/agents/two_fa_extraction/__init__.py +0 -0
  50. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/inference/agents/two_fa_extraction/prompt.py +0 -0
  51. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/inference/agents/two_fa_extraction/two_fa_extraction.py +0 -0
  52. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/inference/child_process.py +0 -0
  53. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/inference/core/__init__.py +0 -0
  54. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/inference/core/interaction/__init__.py +0 -0
  55. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/inference/core/interaction/handle_agentic_task.py +0 -0
  56. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/inference/core/interaction/handle_check.py +0 -0
  57. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/inference/core/interaction/handle_input.py +0 -0
  58. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/inference/core/interaction/handle_select.py +0 -0
  59. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/inference/core/interaction/handle_upload.py +0 -0
  60. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/inference/core/interaction/utils.py +0 -0
  61. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/inference/core/run_assertion.py +0 -0
  62. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/inference/core/run_python_script.py +0 -0
  63. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/inference/core/two_factor_auth/__init__.py +0 -0
  64. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/inference/infra/__init__.py +0 -0
  65. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/inference/infra/browser_extension.py +0 -0
  66. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/inference/models/__init__.py +0 -0
  67. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/inference/models/gemini.py +0 -0
  68. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/inference/models/human.py +0 -0
  69. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/inference/models/llm_model.py +0 -0
  70. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/inference/run_local.py +0 -0
  71. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/onepassword_integration.py +0 -0
  72. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/schema/__init__.py +0 -0
  73. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/schema/actions/__init__.py +0 -0
  74. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/schema/actions/assertion_action.py +0 -0
  75. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/schema/actions/misc_action.py +0 -0
  76. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/schema/actions/prompts.py +0 -0
  77. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/schema/callback.py +0 -0
  78. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/schema/inference.py +0 -0
  79. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/schema/memory.py +0 -0
  80. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/schema/token_usage.py +0 -0
  81. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/test.py +0 -0
  82. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/utils/__init__.py +0 -0
  83. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity/utils/settings.py +0 -0
  84. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity.egg-info/dependency_links.txt +0 -0
  85. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity.egg-info/entry_points.txt +0 -0
  86. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity.egg-info/requires.txt +0 -0
  87. {optexity-0.1.5.1 → optexity-0.1.5.3}/optexity.egg-info/top_level.txt +0 -0
  88. {optexity-0.1.5.1 → optexity-0.1.5.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: optexity
3
- Version: 0.1.5.1
3
+ Version: 0.1.5.3
4
4
  Summary: Optexity is a platform for building and running browser and computer agents.
5
5
  Author-email: Optexity <founders@optexity.com>
6
6
  Requires-Python: >=3.11
@@ -59,8 +59,11 @@ async def click_element_index(
59
59
  return
60
60
 
61
61
  async def _actual_click_element():
62
+ print(
63
+ f"Clicking element with index: {index} and button: {click_element_action.button}"
64
+ )
62
65
  action_model = browser.backend_agent.ActionModel(
63
- **{"click": {"index": index}}
66
+ **{"click": {"index": index, "button": click_element_action.button}}
64
67
  )
65
68
  await browser.backend_agent.multi_act([action_model])
66
69
 
@@ -13,6 +13,7 @@ from optexity.inference.infra.browser import Browser
13
13
  from optexity.schema.actions.interaction_action import (
14
14
  CheckAction,
15
15
  ClickElementAction,
16
+ HoverAction,
16
17
  InputTextAction,
17
18
  SelectOptionAction,
18
19
  UncheckAction,
@@ -32,6 +33,7 @@ async def command_based_action_with_retry(
32
33
  | CheckAction
33
34
  | UploadFileAction
34
35
  | UncheckAction
36
+ | HoverAction
35
37
  ),
36
38
  browser: Browser,
37
39
  memory: Memory,
@@ -83,7 +85,7 @@ async def command_based_action_with_retry(
83
85
  )
84
86
  elif isinstance(action, InputTextAction):
85
87
  await input_text_locator(
86
- action, locator, max_timeout_seconds_per_try
88
+ action, locator, browser, max_timeout_seconds_per_try
87
89
  )
88
90
  elif isinstance(action, SelectOptionAction):
89
91
  await select_option_locator(
@@ -102,6 +104,8 @@ async def command_based_action_with_retry(
102
104
  await uncheck_locator(
103
105
  action, locator, max_timeout_seconds_per_try, browser
104
106
  )
107
+ elif isinstance(action, HoverAction):
108
+ await hover_locator(locator, max_timeout_seconds_per_try)
105
109
  elif isinstance(action, UploadFileAction):
106
110
  await upload_file_locator(action, locator)
107
111
  logger.debug(
@@ -149,7 +153,9 @@ async def click_locator(
149
153
  )
150
154
  else:
151
155
  await locator.click(
152
- no_wait_after=True, timeout=max_timeout_seconds_per_try * 1000
156
+ button=click_element_action.button,
157
+ no_wait_after=True,
158
+ timeout=max_timeout_seconds_per_try * 1000,
153
159
  )
154
160
 
155
161
  if click_element_action.expect_download:
@@ -163,6 +169,7 @@ async def click_locator(
163
169
  async def input_text_locator(
164
170
  input_text_action: InputTextAction,
165
171
  locator: Locator,
172
+ browser: Browser,
166
173
  max_timeout_seconds_per_try: float,
167
174
  ):
168
175
 
@@ -172,12 +179,19 @@ async def input_text_locator(
172
179
  no_wait_after=True,
173
180
  timeout=max_timeout_seconds_per_try * 1000,
174
181
  )
175
- else:
182
+ elif input_text_action.fill_or_type == "type":
176
183
  await locator.type(
177
184
  input_text_action.input_text,
178
185
  no_wait_after=True,
179
186
  timeout=max_timeout_seconds_per_try * 1000,
180
187
  )
188
+ else:
189
+ page = await browser.get_current_page()
190
+ if page is None:
191
+ return
192
+ for char in input_text_action.input_text:
193
+ await page.keyboard.press(char)
194
+ await asyncio.sleep(0.1)
181
195
 
182
196
  if input_text_action.press_enter:
183
197
  await locator.press("Enter")
@@ -211,6 +225,13 @@ async def uncheck_locator(
211
225
  )
212
226
 
213
227
 
228
+ async def hover_locator(
229
+ locator: Locator,
230
+ max_timeout_seconds_per_try: float,
231
+ ):
232
+ await locator.hover(no_wait_after=True, timeout=max_timeout_seconds_per_try * 1000)
233
+
234
+
214
235
  async def upload_file_locator(upload_file_action: UploadFileAction, locator: Locator):
215
236
  await locator.set_input_files(upload_file_action.file_path)
216
237
 
@@ -0,0 +1,83 @@
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 get_index_from_prompt
7
+ from optexity.inference.infra.browser import Browser
8
+ from optexity.schema.actions.interaction_action import HoverAction
9
+ from optexity.schema.memory import Memory
10
+ from optexity.schema.task import Task
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ async def handle_hover_element(
16
+ hover_element_action: HoverAction,
17
+ task: Task,
18
+ memory: Memory,
19
+ browser: Browser,
20
+ max_timeout_seconds_per_try: float,
21
+ max_tries: int,
22
+ ):
23
+
24
+ if hover_element_action.command and not hover_element_action.skip_command:
25
+ last_error = await command_based_action_with_retry(
26
+ hover_element_action,
27
+ browser,
28
+ memory,
29
+ task,
30
+ max_tries,
31
+ max_timeout_seconds_per_try,
32
+ )
33
+
34
+ if last_error is None:
35
+ return
36
+
37
+ if not hover_element_action.skip_prompt:
38
+ logger.debug(
39
+ f"Executing prompt-based action: {hover_element_action.__class__.__name__}"
40
+ )
41
+ await hover_element_index(hover_element_action, browser, memory, task)
42
+
43
+
44
+ async def hover_element_index(
45
+ hover_element_action: HoverAction,
46
+ browser: Browser,
47
+ memory: Memory,
48
+ task: Task,
49
+ ):
50
+
51
+ try:
52
+ index = await get_index_from_prompt(
53
+ memory, hover_element_action.prompt_instructions, browser, task
54
+ )
55
+ if index is None:
56
+ return
57
+
58
+ print(f"Hovering element with index: {index}")
59
+
60
+ async def _actual_hover_element():
61
+ try:
62
+ action_model = browser.backend_agent.ActionModel(
63
+ **{"hover": {"index": index}}
64
+ )
65
+ await browser.backend_agent.multi_act([action_model])
66
+ except Exception as e:
67
+ logger.error(f"Error in hover_element_index: {e} trying right click")
68
+ node = await browser.backend_agent.browser_session.get_element_by_index(
69
+ index
70
+ )
71
+ if node is None:
72
+ return
73
+
74
+ backend_page = (
75
+ await browser.backend_agent.browser_session.get_current_page()
76
+ )
77
+ element = await backend_page.get_element(node.backend_node_id)
78
+ await element.click(button="right")
79
+
80
+ await _actual_hover_element()
81
+ except Exception as e:
82
+ logger.error(f"Error in hover_element_index: {e}")
83
+ return
@@ -0,0 +1,42 @@
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")
17
+ if keypress_action.type == KeyPressType.TAB:
18
+ await page.keyboard.press("Tab")
19
+ if keypress_action.type == KeyPressType.ZERO:
20
+ await page.keyboard.press("0")
21
+ if keypress_action.type == KeyPressType.ONE:
22
+ await page.keyboard.press("1")
23
+ if keypress_action.type == KeyPressType.TWO:
24
+ await page.keyboard.press("2")
25
+ if keypress_action.type == KeyPressType.THREE:
26
+ await page.keyboard.press("3")
27
+ if keypress_action.type == KeyPressType.FOUR:
28
+ await page.keyboard.press("4")
29
+ if keypress_action.type == KeyPressType.FIVE:
30
+ await page.keyboard.press("5")
31
+ if keypress_action.type == KeyPressType.SIX:
32
+ await page.keyboard.press("6")
33
+ if keypress_action.type == KeyPressType.SEVEN:
34
+ await page.keyboard.press("7")
35
+ if keypress_action.type == KeyPressType.EIGHT:
36
+ await page.keyboard.press("8")
37
+ if keypress_action.type == KeyPressType.NINE:
38
+ await page.keyboard.press("9")
39
+ if keypress_action.type == KeyPressType.SLASH:
40
+ await page.keyboard.press("/")
41
+ if keypress_action.type == KeyPressType.SPACE:
42
+ await page.keyboard.press("Space")
@@ -57,9 +57,19 @@ async def smart_select(
57
57
  options: list[SelectOptionValue], patterns: list[str], memory: Memory
58
58
  ):
59
59
  # Get all options from the <select>
60
-
60
+ ## TODO: remove this once we have a better way to handle select one
61
61
  matched_values = []
62
62
 
63
+ if len(options) == 0:
64
+ return []
65
+ if len(options) == 1:
66
+ return [options[0].value]
67
+ if len(options) == 2 and "Select One" in [o.value for o in options]:
68
+ if options[0].value == "Select One":
69
+ return [options[1].value]
70
+ else:
71
+ return [options[0].value]
72
+
63
73
  for p in patterns:
64
74
  # If pattern contains regex characters, treat as regex
65
75
  is_regex = p.startswith("^") or p.endswith("$") or ".*" in p
@@ -364,7 +364,14 @@ async def save_latest_memory_state_locally(
364
364
  await f.write(json.dumps(task.input_parameters, indent=4))
365
365
 
366
366
  async with aiofiles.open(step_directory / "secure_parameters.json", "w") as f:
367
- await f.write(json.dumps(task.secure_parameters, indent=4))
367
+ secure_parameters = {
368
+ key: [
369
+ a.model_dump(exclude_none=True, exclude_defaults=True)
370
+ for a in value
371
+ ]
372
+ for key, value in task.secure_parameters.items()
373
+ }
374
+ await f.write(json.dumps(secure_parameters, indent=4))
368
375
 
369
376
  async with aiofiles.open(step_directory / "generated_variables.json", "w") as f:
370
377
  await f.write(json.dumps(memory.variables.generated_variables, indent=4))
@@ -25,7 +25,6 @@ from optexity.inference.core.run_interaction import (
25
25
  run_interaction_action,
26
26
  )
27
27
  from optexity.inference.core.run_python_script import run_python_script_action
28
- from optexity.inference.core.run_two_fa import run_two_fa_action
29
28
  from optexity.inference.infra.browser import Browser
30
29
  from optexity.schema.actions.interaction_action import DownloadUrlAsPdfAction
31
30
  from optexity.schema.automation import ActionNode, ForLoopNode, IfElseNode
@@ -55,11 +54,14 @@ async def run_automation(task: Task, child_process_id: int):
55
54
  logger.info(f"Task {task.task_id} started running")
56
55
  memory = None
57
56
  browser = None
57
+
58
58
  try:
59
59
  await start_task_in_server(task)
60
60
  memory = Memory()
61
+
61
62
  browser = Browser(
62
63
  memory=memory,
64
+ user_data_dir=f"/tmp/userdata_{task.task_id}",
63
65
  headless=False,
64
66
  channel=task.automation.browser_channel,
65
67
  debug_port=9222 + child_process_id,
@@ -67,9 +69,12 @@ async def run_automation(task: Task, child_process_id: int):
67
69
  proxy_session_id=task.proxy_session_id(
68
70
  settings.PROXY_PROVIDER if task.use_proxy else None
69
71
  ),
72
+ is_dedicated=task.is_dedicated,
70
73
  )
71
74
  await browser.start()
72
75
 
76
+ browser.memory = memory
77
+
73
78
  automation = task.automation
74
79
 
75
80
  memory.automation_state.step_index = -1
@@ -274,8 +279,6 @@ async def run_action_node(
274
279
  await run_extraction_action(
275
280
  action_node.extraction_action, memory, browser, task
276
281
  )
277
- elif action_node.two_fa_action:
278
- await run_two_fa_action(action_node.two_fa_action, memory)
279
282
  elif action_node.python_script_action:
280
283
  await run_python_script_action(
281
284
  action_node.python_script_action, memory, browser
@@ -4,12 +4,14 @@ import traceback
4
4
  import aiofiles
5
5
  import httpx
6
6
 
7
+ from optexity.inference.core.run_two_fa import run_two_fa_action
7
8
  from optexity.inference.infra.browser import Browser
8
9
  from optexity.inference.models import GeminiModels, get_llm_model
9
10
  from optexity.schema.actions.extraction_action import (
10
11
  ExtractionAction,
11
12
  LLMExtraction,
12
13
  NetworkCallExtraction,
14
+ PythonScriptExtraction,
13
15
  ScreenshotExtraction,
14
16
  StateExtraction,
15
17
  )
@@ -51,6 +53,14 @@ async def run_extraction_action(
51
53
  task,
52
54
  extraction_action.unique_identifier,
53
55
  )
56
+ elif extraction_action.python_script:
57
+ await handle_python_script_extraction(
58
+ extraction_action.python_script,
59
+ memory,
60
+ browser,
61
+ task,
62
+ extraction_action.unique_identifier,
63
+ )
54
64
  elif extraction_action.screenshot:
55
65
  await handle_screenshot_extraction(
56
66
  extraction_action.screenshot,
@@ -65,6 +75,8 @@ async def run_extraction_action(
65
75
  browser,
66
76
  extraction_action.unique_identifier,
67
77
  )
78
+ elif extraction_action.two_fa_action:
79
+ await run_two_fa_action(extraction_action.two_fa_action, memory)
68
80
 
69
81
 
70
82
  async def handle_state_extraction(
@@ -225,6 +237,31 @@ async def handle_network_call_extraction(
225
237
  )
226
238
 
227
239
 
240
+ async def handle_python_script_extraction(
241
+ python_script_extraction: PythonScriptExtraction,
242
+ memory: Memory,
243
+ browser: Browser,
244
+ task: Task,
245
+ unique_identifier: str | None = None,
246
+ ):
247
+ local_vars = {}
248
+ exec(python_script_extraction.script, {}, local_vars)
249
+ code_fn = local_vars["code_fn"]
250
+ axtree = memory.browser_states[-1].axtree
251
+ result = await code_fn(axtree)
252
+ if result is not None:
253
+ memory.variables.output_data.append(
254
+ OutputData(
255
+ unique_identifier=unique_identifier,
256
+ json_data=result,
257
+ )
258
+ )
259
+ else:
260
+ logger.warning(
261
+ f"No result from Python script extraction: {python_script_extraction.script}"
262
+ )
263
+
264
+
228
265
  async def download_request(
229
266
  network_call: NetworkRequest, download_filename: str, task: Task, memory: Memory
230
267
  ):
@@ -12,6 +12,7 @@ from optexity.inference.core.interaction.handle_check import (
12
12
  handle_uncheck_element,
13
13
  )
14
14
  from optexity.inference.core.interaction.handle_click import handle_click_element
15
+ from optexity.inference.core.interaction.handle_hover import handle_hover_element
15
16
  from optexity.inference.core.interaction.handle_input import handle_input_text
16
17
  from optexity.inference.core.interaction.handle_keypress import handle_key_press
17
18
  from optexity.inference.core.interaction.handle_select import handle_select_option
@@ -95,6 +96,15 @@ async def run_interaction_action(
95
96
  interaction_action.max_timeout_seconds_per_try,
96
97
  interaction_action.max_tries,
97
98
  )
99
+ elif interaction_action.hover:
100
+ await handle_hover_element(
101
+ interaction_action.hover,
102
+ task,
103
+ memory,
104
+ browser,
105
+ interaction_action.max_timeout_seconds_per_try,
106
+ interaction_action.max_tries,
107
+ )
98
108
  elif interaction_action.go_back:
99
109
  await handle_go_back(interaction_action.go_back, memory, browser)
100
110
  elif interaction_action.download_url_as_pdf:
@@ -33,6 +33,7 @@ async def run_two_fa_action(two_fa_action: TwoFAAction, memory: Memory):
33
33
 
34
34
  elapsed = 0
35
35
  messages = None
36
+ code = None
36
37
 
37
38
  while elapsed < two_fa_action.max_wait_time:
38
39
  messages = await fetch_messages(
@@ -109,12 +110,16 @@ async def fetch_messages(
109
110
  end_2fa_time=end_2fa_time,
110
111
  )
111
112
 
112
- async with httpx.AsyncClient(timeout=30.0) as client:
113
+ try:
114
+ async with httpx.AsyncClient(timeout=30.0) as client:
113
115
 
114
- response = await client.post(
115
- url, json=body.model_dump(mode="json"), headers=headers
116
- )
117
- response.raise_for_status()
118
- response_data = FetchMessagesResponse.model_validate(response.json())
116
+ response = await client.post(
117
+ url, json=body.model_dump(mode="json"), headers=headers
118
+ )
119
+ response.raise_for_status()
120
+ response_data = FetchMessagesResponse.model_validate(response.json())
119
121
 
120
- return response_data.messages
122
+ return response_data.messages
123
+ except Exception as e:
124
+ logger.error(f"Error fetching messages: {e}")
125
+ return []