optexity 0.1.5__py3-none-any.whl → 0.1.5.2__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 (28) hide show
  1. optexity/__init__.py +6 -0
  2. optexity/examples/extract_price_stockanalysis.py +3 -2
  3. optexity/inference/child_process.py +5 -2
  4. optexity/inference/core/interaction/handle_click.py +5 -2
  5. optexity/inference/core/interaction/handle_command.py +27 -4
  6. optexity/inference/core/interaction/handle_hover.py +83 -0
  7. optexity/inference/core/interaction/handle_input.py +6 -3
  8. optexity/inference/core/interaction/handle_keypress.py +26 -0
  9. optexity/inference/core/interaction/handle_select.py +1 -1
  10. optexity/inference/core/interaction/handle_select_utils.py +11 -1
  11. optexity/inference/core/interaction/handle_upload.py +3 -3
  12. optexity/inference/core/interaction/utils.py +4 -2
  13. optexity/inference/core/run_assertion.py +8 -4
  14. optexity/inference/core/run_automation.py +6 -5
  15. optexity/inference/core/run_extraction.py +50 -4
  16. optexity/inference/core/run_interaction.py +13 -1
  17. optexity/inference/core/run_two_fa.py +12 -7
  18. optexity/schema/actions/extraction_action.py +9 -3
  19. optexity/schema/actions/interaction_action.py +38 -4
  20. optexity/schema/actions/two_fa_action.py +1 -1
  21. optexity/schema/automation.py +3 -7
  22. optexity/schema/task.py +6 -2
  23. {optexity-0.1.5.dist-info → optexity-0.1.5.2.dist-info}/METADATA +3 -9
  24. {optexity-0.1.5.dist-info → optexity-0.1.5.2.dist-info}/RECORD +28 -27
  25. {optexity-0.1.5.dist-info → optexity-0.1.5.2.dist-info}/WHEEL +1 -1
  26. {optexity-0.1.5.dist-info → optexity-0.1.5.2.dist-info}/entry_points.txt +0 -0
  27. {optexity-0.1.5.dist-info → optexity-0.1.5.2.dist-info}/licenses/LICENSE +0 -0
  28. {optexity-0.1.5.dist-info → optexity-0.1.5.2.dist-info}/top_level.txt +0 -0
optexity/__init__.py CHANGED
@@ -1,7 +1,13 @@
1
1
  import logging
2
2
  import sys
3
+ from importlib.metadata import PackageNotFoundError, version
3
4
  from pathlib import Path
4
5
 
6
+ try:
7
+ __version__ = version("optexity")
8
+ except PackageNotFoundError:
9
+ __version__ = "0.0.0"
10
+
5
11
  logging.basicConfig(
6
12
  level=logging.WARNING, # Default level for root logger
7
13
  format="%(asctime)s [%(levelname)s] %(name)s.%(funcName)s: %(message)s",
@@ -23,12 +23,13 @@ automation_json = {
23
23
  "click_element": {
24
24
  "prompt_instructions": "Click on the link with the name of the stock equivalent for {stock_ticker[0]}."
25
25
  }
26
- }
26
+ },
27
+ "before_sleep_time": 1,
27
28
  },
28
29
  {
29
30
  "extraction_action": {
30
31
  "llm": {
31
- "source": ["screenshot"],
32
+ "source": ["screenshot", "axtree"],
32
33
  "extraction_format": {
33
34
  "stock_name": "str",
34
35
  "stock_price": "str",
@@ -154,7 +154,10 @@ def get_app_with_endpoints(is_aws: bool, child_id: int):
154
154
 
155
155
  await task_queue.put(task)
156
156
  return JSONResponse(
157
- content={"success": True, "message": "Task has been allocated"},
157
+ content={
158
+ "success": True,
159
+ "message": "Task has been allocated. Check its status and output at https://dashboard.optexity.com/tasks",
160
+ },
158
161
  status_code=202,
159
162
  )
160
163
  except Exception as e:
@@ -192,7 +195,7 @@ def get_app_with_endpoints(is_aws: bool, child_id: int):
192
195
  return JSONResponse(
193
196
  content={
194
197
  "success": True,
195
- "message": "Task has been allocated",
198
+ "message": "Task has been allocated. Check its status and output at https://dashboard.optexity.com/tasks",
196
199
  "task_id": task.task_id,
197
200
  },
198
201
  status_code=202,
@@ -53,14 +53,17 @@ async def click_element_index(
53
53
 
54
54
  try:
55
55
  index = await get_index_from_prompt(
56
- memory, click_element_action.prompt_instructions, browser
56
+ memory, click_element_action.prompt_instructions, browser, task
57
57
  )
58
58
  if index is None:
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,
@@ -67,7 +69,9 @@ async def command_based_action_with_retry(
67
69
  url=browser_state_summary.url,
68
70
  screenshot=browser_state_summary.screenshot,
69
71
  title=browser_state_summary.title,
70
- axtree=browser_state_summary.dom_state.llm_representation(),
72
+ axtree=browser_state_summary.dom_state.llm_representation(
73
+ remove_empty_nodes=task.automation.remove_empty_nodes_in_axtree
74
+ ),
71
75
  )
72
76
 
73
77
  if isinstance(action, ClickElementAction):
@@ -81,7 +85,7 @@ async def command_based_action_with_retry(
81
85
  )
82
86
  elif isinstance(action, InputTextAction):
83
87
  await input_text_locator(
84
- action, locator, max_timeout_seconds_per_try
88
+ action, locator, browser, max_timeout_seconds_per_try
85
89
  )
86
90
  elif isinstance(action, SelectOptionAction):
87
91
  await select_option_locator(
@@ -100,6 +104,8 @@ async def command_based_action_with_retry(
100
104
  await uncheck_locator(
101
105
  action, locator, max_timeout_seconds_per_try, browser
102
106
  )
107
+ elif isinstance(action, HoverAction):
108
+ await hover_locator(locator, max_timeout_seconds_per_try)
103
109
  elif isinstance(action, UploadFileAction):
104
110
  await upload_file_locator(action, locator)
105
111
  logger.debug(
@@ -147,7 +153,9 @@ async def click_locator(
147
153
  )
148
154
  else:
149
155
  await locator.click(
150
- 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,
151
159
  )
152
160
 
153
161
  if click_element_action.expect_download:
@@ -161,6 +169,7 @@ async def click_locator(
161
169
  async def input_text_locator(
162
170
  input_text_action: InputTextAction,
163
171
  locator: Locator,
172
+ browser: Browser,
164
173
  max_timeout_seconds_per_try: float,
165
174
  ):
166
175
 
@@ -170,12 +179,19 @@ async def input_text_locator(
170
179
  no_wait_after=True,
171
180
  timeout=max_timeout_seconds_per_try * 1000,
172
181
  )
173
- else:
182
+ elif input_text_action.fill_or_type == "type":
174
183
  await locator.type(
175
184
  input_text_action.input_text,
176
185
  no_wait_after=True,
177
186
  timeout=max_timeout_seconds_per_try * 1000,
178
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)
179
195
 
180
196
  if input_text_action.press_enter:
181
197
  await locator.press("Enter")
@@ -209,6 +225,13 @@ async def uncheck_locator(
209
225
  )
210
226
 
211
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
+
212
235
  async def upload_file_locator(upload_file_action: UploadFileAction, locator: Locator):
213
236
  await locator.set_input_files(upload_file_action.file_path)
214
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
@@ -48,15 +48,18 @@ async def handle_input_text(
48
48
  logger.debug(
49
49
  f"Executing prompt-based action: {input_text_action.__class__.__name__}"
50
50
  )
51
- await input_text_index(input_text_action, browser, memory)
51
+ await input_text_index(input_text_action, browser, memory, task)
52
52
 
53
53
 
54
54
  async def input_text_index(
55
- input_text_action: InputTextAction, browser: Browser, memory: Memory
55
+ input_text_action: InputTextAction, browser: Browser, memory: Memory, task: Task
56
56
  ):
57
57
  try:
58
58
  index = await get_index_from_prompt(
59
- memory, input_text_action.prompt_instructions, browser
59
+ memory,
60
+ input_text_action.prompt_instructions,
61
+ browser,
62
+ task,
60
63
  )
61
64
  if index is None:
62
65
  return
@@ -14,3 +14,29 @@ async def handle_key_press(
14
14
 
15
15
  if keypress_action.type == KeyPressType.ENTER:
16
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")
@@ -60,7 +60,7 @@ async def select_option_index(
60
60
  try:
61
61
 
62
62
  index = await get_index_from_prompt(
63
- memory, select_option_action.prompt_instructions, browser
63
+ memory, select_option_action.prompt_instructions, browser, task
64
64
  )
65
65
  if index is None:
66
66
  return
@@ -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
@@ -36,16 +36,16 @@ async def handle_upload_file(
36
36
  logger.debug(
37
37
  f"Executing prompt-based action: {upload_file_action.__class__.__name__}"
38
38
  )
39
- await upload_file_index(upload_file_action, browser, memory)
39
+ await upload_file_index(upload_file_action, browser, memory, task)
40
40
 
41
41
 
42
42
  async def upload_file_index(
43
- upload_file_action: UploadFileAction, browser: Browser, memory: Memory
43
+ upload_file_action: UploadFileAction, browser: Browser, memory: Memory, task: Task
44
44
  ):
45
45
 
46
46
  try:
47
47
  index = await get_index_from_prompt(
48
- memory, upload_file_action.prompt_instructions, browser
48
+ memory, upload_file_action.prompt_instructions, browser, task
49
49
  )
50
50
  if index is None:
51
51
  return
@@ -18,14 +18,16 @@ index_prediction_agent = ActionPredictionLocatorAxtree()
18
18
 
19
19
 
20
20
  async def get_index_from_prompt(
21
- memory: Memory, prompt_instructions: str, browser: Browser
21
+ memory: Memory, prompt_instructions: str, browser: Browser, task: Task
22
22
  ):
23
23
  browser_state_summary = await browser.get_browser_state_summary()
24
24
  memory.browser_states[-1] = BrowserState(
25
25
  url=browser_state_summary.url,
26
26
  screenshot=browser_state_summary.screenshot,
27
27
  title=browser_state_summary.title,
28
- axtree=browser_state_summary.dom_state.llm_representation(),
28
+ axtree=browser_state_summary.dom_state.llm_representation(
29
+ remove_empty_nodes=task.automation.remove_empty_nodes_in_axtree
30
+ ),
29
31
  )
30
32
 
31
33
  try:
@@ -6,6 +6,7 @@ from optexity.inference.infra.browser import Browser
6
6
  from optexity.inference.models import GeminiModels, get_llm_model
7
7
  from optexity.schema.actions.assertion_action import AssertionAction, LLMAssertion
8
8
  from optexity.schema.memory import Memory
9
+ from optexity.schema.task import Task
9
10
 
10
11
  logger = logging.getLogger(__name__)
11
12
 
@@ -13,14 +14,17 @@ llm_model = get_llm_model(GeminiModels.GEMINI_2_5_FLASH, True)
13
14
 
14
15
 
15
16
  async def run_assertion_action(
16
- assertion_action: AssertionAction, memory: Memory, browser: Browser
17
+ assertion_action: AssertionAction,
18
+ memory: Memory,
19
+ browser: Browser,
20
+ task: Task,
17
21
  ):
18
22
  logger.debug(
19
23
  f"---------Running assertion action {assertion_action.model_dump_json()}---------"
20
24
  )
21
25
 
22
26
  if assertion_action.llm:
23
- await handle_llm_assertion(assertion_action.llm, memory, browser)
27
+ await handle_llm_assertion(assertion_action.llm, memory, browser, task)
24
28
  elif assertion_action.network_call:
25
29
  raise ValueError("Network call assertions are not supported yet")
26
30
  # await handle_network_call_assertion(
@@ -34,7 +38,7 @@ async def run_assertion_action(
34
38
 
35
39
 
36
40
  async def handle_llm_assertion(
37
- llm_assertion: LLMAssertion, memory: Memory, browser: Browser
41
+ llm_assertion: LLMAssertion, memory: Memory, browser: Browser, task: Task
38
42
  ):
39
43
  extra_instruction = """You are a helpful assistant that verifies if the condition is met.
40
44
  Use the info supplied below to verify the condition.
@@ -45,7 +49,7 @@ async def handle_llm_assertion(
45
49
  llm_assertion_new.extraction_instructions = (
46
50
  extra_instruction + "\n" + llm_assertion_new.extraction_instructions
47
51
  )
48
- output_data = await handle_llm_extraction(llm_assertion_new, memory, browser)
52
+ output_data = await handle_llm_extraction(llm_assertion_new, memory, browser, task)
49
53
 
50
54
  if output_data.json_data["assertion_result"]:
51
55
  return True
@@ -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
@@ -209,7 +208,9 @@ async def run_final_logging(
209
208
  url=browser_state_summary.url,
210
209
  screenshot=browser_state_summary.screenshot,
211
210
  title=browser_state_summary.title,
212
- axtree=browser_state_summary.dom_state.llm_representation(),
211
+ axtree=browser_state_summary.dom_state.llm_representation(
212
+ remove_empty_nodes=task.automation.remove_empty_nodes_in_axtree
213
+ ),
213
214
  )
214
215
  )
215
216
 
@@ -272,14 +273,14 @@ async def run_action_node(
272
273
  await run_extraction_action(
273
274
  action_node.extraction_action, memory, browser, task
274
275
  )
275
- elif action_node.two_fa_action:
276
- await run_two_fa_action(action_node.two_fa_action, memory)
277
276
  elif action_node.python_script_action:
278
277
  await run_python_script_action(
279
278
  action_node.python_script_action, memory, browser
280
279
  )
281
280
  elif action_node.assertion_action:
282
- await run_assertion_action(action_node.assertion_action, memory, browser)
281
+ await run_assertion_action(
282
+ action_node.assertion_action, memory, browser, task
283
+ )
283
284
 
284
285
  except Exception as e:
285
286
  logger.error(f"Error running node {memory.automation_state.step_index}: {e}")
@@ -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
  )
@@ -37,7 +39,11 @@ async def run_extraction_action(
37
39
 
38
40
  if extraction_action.llm:
39
41
  await handle_llm_extraction(
40
- extraction_action.llm, memory, browser, extraction_action.unique_identifier
42
+ extraction_action.llm,
43
+ memory,
44
+ browser,
45
+ task,
46
+ extraction_action.unique_identifier,
41
47
  )
42
48
  elif extraction_action.network_call:
43
49
  await handle_network_call_extraction(
@@ -47,6 +53,14 @@ async def run_extraction_action(
47
53
  task,
48
54
  extraction_action.unique_identifier,
49
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
+ )
50
64
  elif extraction_action.screenshot:
51
65
  await handle_screenshot_extraction(
52
66
  extraction_action.screenshot,
@@ -61,6 +75,8 @@ async def run_extraction_action(
61
75
  browser,
62
76
  extraction_action.unique_identifier,
63
77
  )
78
+ elif extraction_action.two_fa_action:
79
+ await run_two_fa_action(extraction_action.two_fa_action, memory)
64
80
 
65
81
 
66
82
  async def handle_state_extraction(
@@ -108,6 +124,7 @@ async def handle_llm_extraction(
108
124
  llm_extraction: LLMExtraction,
109
125
  memory: Memory,
110
126
  browser: Browser,
127
+ task: Task,
111
128
  unique_identifier: str | None = None,
112
129
  ):
113
130
  browser_state_summary = await browser.get_browser_state_summary()
@@ -115,7 +132,9 @@ async def handle_llm_extraction(
115
132
  url=browser_state_summary.url,
116
133
  screenshot=browser_state_summary.screenshot,
117
134
  title=browser_state_summary.title,
118
- axtree=browser_state_summary.dom_state.llm_representation(),
135
+ axtree=browser_state_summary.dom_state.llm_representation(
136
+ remove_empty_nodes=task.automation.remove_empty_nodes_in_axtree
137
+ ),
119
138
  )
120
139
 
121
140
  # TODO: fix this double calling of screenshot and axtree
@@ -131,8 +150,8 @@ async def handle_llm_extraction(
131
150
 
132
151
  system_instruction = f"""
133
152
  You are an expert in extracting information from a website. You will be given an axtree of a webpage.
134
- Your task is to extract the information from the webpage and return it in the format specified by the instructions.
135
- {llm_extraction.extraction_instructions}
153
+ Your task is to extract the information from the webpage and return it in the format specified by the instructions. You will be first provided the instructions and then the axtree.
154
+ Instructions: {llm_extraction.extraction_instructions}
136
155
  """
137
156
 
138
157
  prompt = f"""
@@ -163,6 +182,8 @@ async def handle_llm_extraction(
163
182
  memory.token_usage += token_usage
164
183
  memory.variables.output_data.append(output_data)
165
184
 
185
+ memory.browser_states[-1].final_prompt = f"{system_instruction}\n{prompt}"
186
+
166
187
  if llm_extraction.output_variable_names is not None:
167
188
  for output_variable_name in llm_extraction.output_variable_names:
168
189
  v = response_dict[output_variable_name]
@@ -216,6 +237,31 @@ async def handle_network_call_extraction(
216
237
  )
217
238
 
218
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
+
219
265
  async def download_request(
220
266
  network_call: NetworkRequest, download_filename: str, task: Task, memory: Memory
221
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:
@@ -219,7 +229,9 @@ async def handle_assert_locator_presence_error(
219
229
  url=browser_state_summary.url,
220
230
  screenshot=browser_state_summary.screenshot,
221
231
  title=browser_state_summary.title,
222
- axtree=browser_state_summary.dom_state.llm_representation(),
232
+ axtree=browser_state_summary.dom_state.llm_representation(
233
+ remove_empty_nodes=task.automation.remove_empty_nodes_in_axtree
234
+ ),
223
235
  )
224
236
  final_prompt, response, token_usage = error_handler_agent.classify_error(
225
237
  error.command, memory.browser_states[-1].screenshot
@@ -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 []
@@ -3,6 +3,7 @@ from uuid import uuid4
3
3
 
4
4
  from pydantic import BaseModel, Field, field_validator, model_validator
5
5
 
6
+ from optexity.schema.actions.two_fa_action import TwoFAAction
6
7
  from optexity.utils.utils import build_model
7
8
 
8
9
 
@@ -53,13 +54,16 @@ class LLMExtraction(BaseModel):
53
54
  return self
54
55
 
55
56
  def replace(self, pattern: str, replacement: str):
57
+ self.extraction_instructions = self.extraction_instructions.replace(
58
+ pattern, replacement
59
+ )
56
60
  return self
57
61
 
58
62
 
59
63
  class NetworkCallExtraction(BaseModel):
60
64
  url_pattern: Optional[str] = None
61
- extract_from: None | Literal["request", "response"] = None
62
- download_from: None | Literal["request", "response"] = None
65
+ extract_from: None | Literal["request", "response"] = "response"
66
+ download_from: None | Literal["request", "response"] = "response"
63
67
  download_filename: str | None = None
64
68
 
65
69
  @model_validator(mode="before")
@@ -109,6 +113,7 @@ class ExtractionAction(BaseModel):
109
113
  python_script: Optional[PythonScriptExtraction] = None
110
114
  screenshot: Optional[ScreenshotExtraction] = None
111
115
  state: Optional[StateExtraction] = None
116
+ two_fa_action: TwoFAAction | None = None
112
117
 
113
118
  @model_validator(mode="after")
114
119
  def validate_one_extraction(cls, model: "ExtractionAction"):
@@ -119,12 +124,13 @@ class ExtractionAction(BaseModel):
119
124
  "python_script": model.python_script,
120
125
  "screenshot": model.screenshot,
121
126
  "state": model.state,
127
+ "two_fa_action": model.two_fa_action,
122
128
  }
123
129
  non_null = [k for k, v in provided.items() if v is not None]
124
130
 
125
131
  if len(non_null) != 1:
126
132
  raise ValueError(
127
- "Exactly one of llm, networkcall, python_script, or screenshot must be provided"
133
+ "Exactly one of llm, networkcall, python_script, screenshot, state, or two_fa_action must be provided"
128
134
  )
129
135
 
130
136
  return model
@@ -1,5 +1,5 @@
1
1
  from enum import Enum, unique
2
- from typing import Literal
2
+ from typing import Any, Literal
3
3
  from uuid import uuid4
4
4
 
5
5
  from pydantic import BaseModel, Field, model_validator
@@ -69,6 +69,10 @@ class UncheckAction(BaseAction):
69
69
  pass
70
70
 
71
71
 
72
+ class HoverAction(BaseAction):
73
+ pass
74
+
75
+
72
76
  class SelectOptionAction(BaseAction):
73
77
  select_values: list[str]
74
78
  expect_download: bool = False
@@ -100,6 +104,7 @@ class ClickElementAction(BaseAction):
100
104
  double_click: bool = False
101
105
  expect_download: bool = False
102
106
  download_filename: str | None = None
107
+ button: Literal["left", "right", "middle"] = "left"
103
108
 
104
109
  @model_validator(mode="after")
105
110
  def set_download_filename(cls, model: "ClickElementAction"):
@@ -121,7 +126,7 @@ class ClickElementAction(BaseAction):
121
126
  class InputTextAction(BaseAction):
122
127
  input_text: str | None = None
123
128
  is_slider: bool = False
124
- fill_or_type: Literal["fill", "type"] = "fill"
129
+ fill_or_type: Literal["fill", "type", "key_press"] = "fill"
125
130
  press_enter: bool = False
126
131
 
127
132
  @model_validator(mode="after")
@@ -217,10 +222,33 @@ class KeyPressType(str, Enum):
217
222
  DELETE = "Delete"
218
223
  BACKSPACE = "Backspace"
219
224
  ESCAPE = "Escape"
225
+ ZERO = "0"
226
+ ONE = "1"
227
+ TWO = "2"
228
+ THREE = "3"
229
+ FOUR = "4"
230
+ FIVE = "5"
231
+ SIX = "6"
232
+ SEVEN = "7"
233
+ EIGHT = "8"
234
+ NINE = "9"
235
+ SLASH = "/"
236
+ SPACE = "Space"
220
237
 
221
238
 
222
239
  class KeyPressAction(BaseModel):
223
- type: KeyPressType
240
+ type: KeyPressType | Any
241
+
242
+ @model_validator(mode="after")
243
+ def validate_type(self):
244
+ if self.type is None:
245
+ raise ValueError("type is required")
246
+ return self
247
+
248
+ def replace(self, pattern: str, replacement: str):
249
+ if self.type:
250
+ self.type = self.type.replace(pattern, replacement).strip('"')
251
+ return self
224
252
 
225
253
 
226
254
  class AgenticTask(BaseModel):
@@ -252,6 +280,7 @@ class InteractionAction(BaseModel):
252
280
  select_option: SelectOptionAction | None = None
253
281
  check: CheckAction | None = None
254
282
  uncheck: UncheckAction | None = None
283
+ hover: HoverAction | None = None
255
284
  download_url_as_pdf: DownloadUrlAsPdfAction | None = None
256
285
  scroll: ScrollAction | None = None
257
286
  upload_file: UploadFileAction | None = None
@@ -274,6 +303,7 @@ class InteractionAction(BaseModel):
274
303
  "select_option": model.select_option,
275
304
  "check": model.check,
276
305
  "uncheck": model.uncheck,
306
+ "hover": model.hover,
277
307
  "download_url_as_pdf": model.download_url_as_pdf,
278
308
  "scroll": model.scroll,
279
309
  "upload_file": model.upload_file,
@@ -291,7 +321,7 @@ class InteractionAction(BaseModel):
291
321
 
292
322
  if len(non_null) != 1:
293
323
  raise ValueError(
294
- "Exactly one of click_element, input_text, select_option, check, uncheck, download_url_as_pdf, scroll, upload_file, go_to_url, go_back, switch_tab, close_current_tab, close_all_but_last_tab, close_tabs_until, key_press, or agentic_task must be provided"
324
+ "Exactly one of click_element, input_text, select_option, check, uncheck, hover, download_url_as_pdf, scroll, upload_file, go_to_url, go_back, switch_tab, close_current_tab, close_all_but_last_tab, close_tabs_until, key_press, or agentic_task must be provided"
295
325
  )
296
326
 
297
327
  if (
@@ -314,6 +344,8 @@ class InteractionAction(BaseModel):
314
344
  self.check.replace(pattern, replacement)
315
345
  if self.uncheck:
316
346
  self.uncheck.replace(pattern, replacement)
347
+ if self.hover:
348
+ self.hover.replace(pattern, replacement)
317
349
  if self.download_url_as_pdf:
318
350
  self.download_url_as_pdf.replace(pattern, replacement)
319
351
  if self.close_tabs_until:
@@ -326,5 +358,7 @@ class InteractionAction(BaseModel):
326
358
  self.go_to_url.replace(pattern, replacement)
327
359
  if self.upload_file:
328
360
  self.upload_file.replace(pattern, replacement)
361
+ if self.key_press:
362
+ self.key_press.replace(pattern, replacement)
329
363
 
330
364
  return self
@@ -21,4 +21,4 @@ class TwoFAAction(BaseModel):
21
21
  instructions: str | None = None
22
22
  output_variable_name: str
23
23
  max_wait_time: float = 300.0
24
- check_interval: float = 10.0
24
+ check_interval: float = 30.0
@@ -7,7 +7,6 @@ from optexity.schema.actions.assertion_action import AssertionAction
7
7
  from optexity.schema.actions.extraction_action import ExtractionAction
8
8
  from optexity.schema.actions.interaction_action import InteractionAction
9
9
  from optexity.schema.actions.misc_action import PythonScriptAction
10
- from optexity.schema.actions.two_fa_action import TwoFAAction
11
10
  from optexity.utils.utils import get_onepassword_value, get_totp_code
12
11
 
13
12
  logger = logging.getLogger(__name__)
@@ -68,7 +67,6 @@ class ActionNode(BaseModel):
68
67
  assertion_action: AssertionAction | None = None
69
68
  extraction_action: ExtractionAction | None = None
70
69
  python_script_action: PythonScriptAction | None = None
71
- two_fa_action: TwoFAAction | None = None
72
70
  before_sleep_time: float = 0.0
73
71
  end_sleep_time: float = 5.0
74
72
  expect_new_tab: bool = False
@@ -83,13 +81,12 @@ class ActionNode(BaseModel):
83
81
  "assertion_action": model.assertion_action,
84
82
  "extraction_action": model.extraction_action,
85
83
  "python_script_action": model.python_script_action,
86
- "two_fa_action": model.two_fa_action,
87
84
  }
88
85
  non_null = [k for k, v in provided.items() if v is not None]
89
86
 
90
87
  if len(non_null) != 1:
91
88
  raise ValueError(
92
- "Exactly one of interaction_action, assertion_action, extraction_action, python_script_action, or two_fa_action must be provided"
89
+ "Exactly one of interaction_action, assertion_action, extraction_action, python_script_action must be provided"
93
90
  )
94
91
 
95
92
  assert (
@@ -104,7 +101,7 @@ class ActionNode(BaseModel):
104
101
  user_set = model.__pydantic_fields_set__
105
102
 
106
103
  if "end_sleep_time" not in user_set:
107
- if model.assertion_action or model.extraction_action or model.two_fa_action:
104
+ if model.assertion_action or model.extraction_action:
108
105
  model.end_sleep_time = 0.0
109
106
 
110
107
  if "before_sleep_time" not in user_set:
@@ -130,8 +127,6 @@ class ActionNode(BaseModel):
130
127
  self.extraction_action.replace(pattern, replacement)
131
128
  if self.python_script_action:
132
129
  pass
133
- if self.two_fa_action:
134
- pass
135
130
 
136
131
  return self
137
132
 
@@ -311,6 +306,7 @@ class Parameters(BaseModel):
311
306
  class Automation(BaseModel):
312
307
  browser_channel: Literal["chromium", "chrome"] = "chromium"
313
308
  expected_downloads: int = 0
309
+ remove_empty_nodes_in_axtree: bool = True
314
310
  url: str
315
311
  parameters: Parameters
316
312
  nodes: list[
optexity/schema/task.py CHANGED
@@ -60,7 +60,9 @@ class Task(BaseModel):
60
60
  started_at: Optional[datetime] = None
61
61
  completed_at: Optional[datetime] = None
62
62
  error: Optional[str] = None
63
- status: Literal["queued", "allocated", "running", "success", "failed", "cancelled"]
63
+ status: Literal[
64
+ "queued", "allocated", "running", "success", "failed", "cancelled", "killed"
65
+ ]
64
66
  is_cloud: bool = False
65
67
  save_directory: Path = Field(default=Path("/tmp/optexity"))
66
68
  use_proxy: bool = False
@@ -102,7 +104,9 @@ class Task(BaseModel):
102
104
  unique_parameter_name: self.input_parameters[unique_parameter_name]
103
105
  for unique_parameter_name in self.unique_parameter_names
104
106
  }
105
- self.dedup_key = json.dumps(self.unique_parameters, sort_keys=True)
107
+ self.dedup_key = (
108
+ json.dumps(self.unique_parameters, sort_keys=True) + self.user_id
109
+ )
106
110
 
107
111
  for a, b in [
108
112
  (self.automation.parameters.input_parameters, self.input_parameters),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: optexity
3
- Version: 0.1.5
3
+ Version: 0.1.5.2
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
@@ -83,6 +83,7 @@ Install Optexity directly from PyPI:
83
83
 
84
84
  ```bash
85
85
  pip install optexity
86
+ optexity install-browsers
86
87
  ```
87
88
 
88
89
  **OR**
@@ -95,6 +96,7 @@ If you want to clone and edit from source:
95
96
  git clone git@github.com:Optexity/optexity.git
96
97
  cd optexity
97
98
  pip install -e .
99
+ optexity install-browsers
98
100
  ```
99
101
 
100
102
  ## Set required environment variables:
@@ -107,14 +109,6 @@ DEPLOYMENT=dev # or "prod" in production
107
109
 
108
110
  You can get your free Google Gemini API key from the [Google AI Studio Console](https://aistudio.google.com).
109
111
 
110
- ## Install required browsers:
111
-
112
- Install playwright and patchright browsers:
113
-
114
- ```bash
115
- optexity install-browsers
116
- ```
117
-
118
112
  ## Recording Your First Automation
119
113
 
120
114
  The fastest way to create an automation is by recording your actions directly in the browser.
@@ -1,4 +1,4 @@
1
- optexity/__init__.py,sha256=dqh4tGEzruZnkvRLjPYKyc2-FVlXLx1Dj-JNy5fy7U8,459
1
+ optexity/__init__.py,sha256=rQAAQJLgccSNz-w3RTaFK8qdVlHHfcKyRNAQsqhz6l0,619
2
2
  optexity/cli.py,sha256=2g_p2Qh7jzFFG5T0aTDiZ7Celh-KycEMJ873whiXFXE,2237
3
3
  optexity/exceptions.py,sha256=j4QxbcnAl5RmEJPJ0MWZ0iM38HvW-r8xmxtDxZ1ceSY,269
4
4
  optexity/onepassword_integration.py,sha256=_1sQ8sRGVdDnA7384FXiXYRVntPB-ZQAu8W7ICX7_VQ,1047
@@ -6,14 +6,14 @@ optexity/test.py,sha256=pMSZwwA8tj6jAfFUJ3OUHGnTPriqkv4eDGGHqdAdrsA,2797
6
6
  optexity/examples/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
7
  optexity/examples/add_example.py,sha256=jVPd2pQ9lnmdwsVsld52CkRafYcF60SrQyDU3QFKTwY,2474
8
8
  optexity/examples/download_pdf_url.py,sha256=mB1Kp0UC4AuHeIiyxMt5tlNK5urNPUosHjmUvV67qdM,829
9
- optexity/examples/extract_price_stockanalysis.py,sha256=oUMAXW0TKFjJO6KCDIsThaMwNNC_Zx5_ZfkuYTo9xmE,1520
9
+ optexity/examples/extract_price_stockanalysis.py,sha256=RGc71h3xpKPpShTHwYDy-jz50jBtKiada-EvSbijiRc,1567
10
10
  optexity/examples/file_upload.py,sha256=Ep9wxjZFjlcWVufkhLt_RufZMbC8BJ8amASFO7uKGCk,2030
11
11
  optexity/examples/i94.py,sha256=QB-Vo6jB03wOovsNh6_TsdxvdJf3MbnxVeUlEoVtUhA,4534
12
12
  optexity/examples/i94_travel_history.py,sha256=PbU-xg3xVoEgRPaZQfC5JPNcHA6ClTu8X7650TK1_dc,4573
13
13
  optexity/examples/peachstate_medicaid.py,sha256=0PQN3SI5NGw0m95ODm-bHYgF0xwpA83_9xflnN8P4mM,8290
14
14
  optexity/examples/supabase_login.py,sha256=_j5VZi0HkqpWl0xguMoHc3gfkf3FiJWnQPs8D_QARN4,2349
15
15
  optexity/inference/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
- optexity/inference/child_process.py,sha256=0-HoM9yUtYf7BzfCeKS_uUO4NfrdAESryuHaKYRwEoY,8098
16
+ optexity/inference/child_process.py,sha256=0trTwuE9cr0hIaLS_1tKnfCVMWwbNTI2XsDKiXya9lY,8295
17
17
  optexity/inference/run_local.py,sha256=P-ghDcNhHESuG7Q6Qp6xdl6r0g2lAyDkIhKsyaLBcPs,6743
18
18
  optexity/inference/agents/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
19
  optexity/inference/agents/error_handler/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -30,23 +30,24 @@ optexity/inference/agents/two_fa_extraction/prompt.py,sha256=eAqz_InZeyTnFqPMeYm
30
30
  optexity/inference/agents/two_fa_extraction/two_fa_extraction.py,sha256=UcBo_Iyx6Kqas-fUZpJgos5R-t2hQ2PZUFjtHmO9Rh0,1444
31
31
  optexity/inference/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
32
  optexity/inference/core/logging.py,sha256=Zq2KL6XWlPIgsJjprxv-BLsnP5MAAKS2ciNMNkQrfBU,13870
33
- optexity/inference/core/run_assertion.py,sha256=8ydAkSA2E0VajwKZlY2z4Xxxyzo49UsFIZ7ej_l0Cy8,2224
34
- optexity/inference/core/run_automation.py,sha256=aXsEj3M7BN_VlpDZgZdScsQHB7jfukCAKGvyBSjUMjA,16708
35
- optexity/inference/core/run_extraction.py,sha256=TaUfFCcKxuMEJELDhcYMjpbyxgRrdVMHNIX6pjo7l5U,7576
36
- optexity/inference/core/run_interaction.py,sha256=Rs6Sa4cqTpR1jNNPh2IRYfj4sKcFgtWbjTaQN-wRICs,9248
33
+ optexity/inference/core/run_assertion.py,sha256=cHI_A_ffe-T7XeTfTqiF3i_KIRe9ioYKSEM1rKZmq0o,2311
34
+ optexity/inference/core/run_automation.py,sha256=Mq9Yubg8b9uvpXp26CzKBVKYB2fBH-wFjbcOvJmze7k,16677
35
+ optexity/inference/core/run_extraction.py,sha256=JLdMIUM0syc3yffjkrDQ3SM2VID-tZW-Zoi7ZD3uURM,9089
36
+ optexity/inference/core/run_interaction.py,sha256=R1llSQKAQUmll4n03x34TEBgxn5Q8jC2mborJ1EAszw,9739
37
37
  optexity/inference/core/run_python_script.py,sha256=WjnCmckZz7YmoLTGBLZeFWhhS1s_x0-kgyKYTM17JHI,547
38
- optexity/inference/core/run_two_fa.py,sha256=m1lxPefJHtXqMYBB0oONxd5f7XX8AI5eUkjjjs5y0Z0,4010
38
+ optexity/inference/core/run_two_fa.py,sha256=5B3YNtLBpQYwZ73oyD2wTL-GAZMbZdMGb1csR4ApQxc,4162
39
39
  optexity/inference/core/interaction/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
40
  optexity/inference/core/interaction/handle_agentic_task.py,sha256=MSURLDM-2Hw37sipa3lqUBh1NNFrGmx2DPGJsBGYTDg,2617
41
41
  optexity/inference/core/interaction/handle_check.py,sha256=_LEA5V6h4x8fozAvPsGrFUZUaO0uU34z2R9VzGIOdio,1489
42
- optexity/inference/core/interaction/handle_click.py,sha256=IhdYjj-oNGoJInN9XK6ppqW6JQgCNm7t2COymv5T9O8,2249
43
- optexity/inference/core/interaction/handle_command.py,sha256=nkteOZ8-at03s1MurVAYeGVIexMukbss8A4ooX81czE,8341
44
- optexity/inference/core/interaction/handle_input.py,sha256=UGALiQLUgjgg5Z-vj4lEoEs4_GXCirUBgONUulKs0oU,2256
45
- optexity/inference/core/interaction/handle_keypress.py,sha256=Ig-U7qoMQ1GIaxeS2TnSTe4N3Jj64WcVc1rwl7Z-0So,466
46
- optexity/inference/core/interaction/handle_select.py,sha256=1nTm-hyaG4xDnItdulW1--b0zj20FdJq7qks5X-aNKU,3236
47
- optexity/inference/core/interaction/handle_select_utils.py,sha256=gJnIvBNPFwIkFjYiaVSB-91k95leahrSnlq8fgDcWbQ,4114
48
- optexity/inference/core/interaction/handle_upload.py,sha256=cIe9lcgA8_jT1KMGcImfqq_PEEVY-Fy5YZjrX2GA8As,1818
49
- optexity/inference/core/interaction/utils.py,sha256=iWKfn7cF-8duQoMvGfmGV05oOuQEyKPgOdMJe_Hk4KM,2745
42
+ optexity/inference/core/interaction/handle_click.py,sha256=z8fvGcFY7IzMcns6Qq0br0OIW1zH66rzYtXBcAB-fP8,2425
43
+ optexity/inference/core/interaction/handle_command.py,sha256=OIExBFmfmWGpj_fmjTrWl7Fo5dZVr0_N_lweB5oAaW8,9175
44
+ optexity/inference/core/interaction/handle_hover.py,sha256=QpRwfDkK1P4mA5cbblgCAZpAN0v6K9XgRIe4zJ1AKRg,2591
45
+ optexity/inference/core/interaction/handle_input.py,sha256=-Yug3fBWumCp0xQt1UVX8dqCHKs_535VheMjOHMlv2Q,2317
46
+ optexity/inference/core/interaction/handle_keypress.py,sha256=81MkFvYzAdMWgG2UvpZQJB42WeMzUHoxqeJZU4v_-CQ,1630
47
+ optexity/inference/core/interaction/handle_select.py,sha256=z-HoFqcuvsfW_fxJrmsz9RASlqef8HA7zJUErBs-9_o,3242
48
+ optexity/inference/core/interaction/handle_select_utils.py,sha256=1-o7t49QuNj5nENB_e2j_53j7mbaywC5lWMn55QPUZk,4498
49
+ optexity/inference/core/interaction/handle_upload.py,sha256=Yrt9KZ-rmgJ-DK5IntIXWG-jcw7mbVYs5iSrZUMkRvc,1842
50
+ optexity/inference/core/interaction/utils.py,sha256=_TMt0XBIIJi2K7nVQGf4PMZ-c9SCDehcMIEGPQx1GS8,2842
50
51
  optexity/inference/core/two_factor_auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
51
52
  optexity/inference/infra/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
52
53
  optexity/inference/infra/browser.py,sha256=GpECOdxhpe5AbwU2FnRmR7jevKUiHHEZCq7PgpMI8t0,15955
@@ -56,25 +57,25 @@ optexity/inference/models/gemini.py,sha256=ToncY6Ft4kOgIm6qREBVsScT8FG-JCrdWsBxY
56
57
  optexity/inference/models/human.py,sha256=K2X6Ohg7xeTWDYkJY6lOAwS9T3nX7YST-cvd8nL1Ydw,394
57
58
  optexity/inference/models/llm_model.py,sha256=nZvcrQs4XDI07ckFUf6o-TzvyqW-I4WIbos7WEo4hz8,7211
58
59
  optexity/schema/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
59
- optexity/schema/automation.py,sha256=by9pcgCpG1yDEowBQGeVa30vQ3hO-GCtTiJdcSf4RKY,16121
60
+ optexity/schema/automation.py,sha256=NFy6jxjXNkv4LF5sa9TH1-_ti3QARnCnsww1dsONJfg,15921
60
61
  optexity/schema/callback.py,sha256=MlN41A6oKG7QX01_w0tsxyAFKWnoCVsu_mjpWzPMYuE,519
61
62
  optexity/schema/inference.py,sha256=8mP49IRU-cRxbsC4NnoGZhd5isvdocCuMVspPBOQV9o,2864
62
63
  optexity/schema/memory.py,sha256=e3AMDAivCF_KnKbeDugqVLib8UN_6dr3ksUCiWaeIGM,3072
63
- optexity/schema/task.py,sha256=qv41fXKSVhxVtS5zrS9ihv35ZVZAXlbcTFO2hVrjBlc,6794
64
+ optexity/schema/task.py,sha256=8rfg2ertmlyQMJcj28anvHc7r2loEDkz8P_Z10Z9UXk,6865
64
65
  optexity/schema/token_usage.py,sha256=iwZjUqTrNhrEBMeeZNMWqD4qs2toKDczRVoDTOLLNso,2114
65
66
  optexity/schema/actions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
66
67
  optexity/schema/actions/assertion_action.py,sha256=lcD6h7phacrEoB5j-It9qP9Ym3TKb1bkv1aa8tqwG-Q,1991
67
- optexity/schema/actions/extraction_action.py,sha256=-d6JaumXRZctiXNqndKE1QOWUR7fllglJ4rGuBpiVNw,4771
68
- optexity/schema/actions/interaction_action.py,sha256=99KuL6waCMnTB3iK6tyeatRjnIV8yhghBSLkY-_rSDo,10728
68
+ optexity/schema/actions/extraction_action.py,sha256=s3PeaogGJA3C34R-W2kfMk0DNvUpGS0BLAfH_xdNhYA,5082
69
+ optexity/schema/actions/interaction_action.py,sha256=9bm1FkBj2-iklClnR302Rir6d4jkgKjpZvTM2fAYn7E,11611
69
70
  optexity/schema/actions/misc_action.py,sha256=VZvaVemTlCoUwSomA42EX74L81-ICDYNWqJAuWNtyyE,312
70
71
  optexity/schema/actions/prompts.py,sha256=GZud5T2kQvQKhAXHmAnalVUP8iMcDz8be3jRp-vAInk,1772
71
- optexity/schema/actions/two_fa_action.py,sha256=OzzTDX3fZObWJiw8hvNgr96PBcvpDh1uONPfHrPFLf8,564
72
+ optexity/schema/actions/two_fa_action.py,sha256=fcnuxlM3B4RguPUDw18RvMUpzLdPBRVGVC3gM_e1wLE,564
72
73
  optexity/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
73
74
  optexity/utils/settings.py,sha256=h6StXzYslRgZf0c8k43-kOxoa77dOgDvSOvfQUi5yI8,1864
74
75
  optexity/utils/utils.py,sha256=QgVeKK3jAq-TLgP_RYiCXRAOEbuypFox0RxYEjruoTA,2565
75
- optexity-0.1.5.dist-info/licenses/LICENSE,sha256=WpSBqSAcwd68PmS3zRsfACJOz-u-UfTzftsEnzp4ZCY,1065
76
- optexity-0.1.5.dist-info/METADATA,sha256=B4OeiBwV8-TX1GvQJTl4scKzkaLHO97xyT40mx1tSiA,9887
77
- optexity-0.1.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
78
- optexity-0.1.5.dist-info/entry_points.txt,sha256=hcn77ooRr6a_N8fo0vij3Fpo6waqc9ijpaScQ7Kj35k,47
79
- optexity-0.1.5.dist-info/top_level.txt,sha256=OZEtBX8IabC8EnBrNW98z7NzdGQsjFhHleSthhjjEMM,9
80
- optexity-0.1.5.dist-info/RECORD,,
76
+ optexity-0.1.5.2.dist-info/licenses/LICENSE,sha256=WpSBqSAcwd68PmS3zRsfACJOz-u-UfTzftsEnzp4ZCY,1065
77
+ optexity-0.1.5.2.dist-info/METADATA,sha256=3rI6z_buRRYQweY4-w2Cdxjr8eCuYQcW8ei53IFwM4E,9826
78
+ optexity-0.1.5.2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
79
+ optexity-0.1.5.2.dist-info/entry_points.txt,sha256=hcn77ooRr6a_N8fo0vij3Fpo6waqc9ijpaScQ7Kj35k,47
80
+ optexity-0.1.5.2.dist-info/top_level.txt,sha256=OZEtBX8IabC8EnBrNW98z7NzdGQsjFhHleSthhjjEMM,9
81
+ optexity-0.1.5.2.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5