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,330 @@
1
+ from enum import Enum, unique
2
+ from typing import Literal
3
+ from uuid import uuid4
4
+
5
+ from pydantic import BaseModel, Field, model_validator
6
+
7
+ from optexity.schema.actions.prompts import overlay_popup_prompt
8
+
9
+
10
+ class Locator(BaseModel):
11
+ regex_options: list[str] | None = None
12
+ locator_class: str
13
+ first_arg: str | int | None = None
14
+ options: dict | None = None
15
+
16
+
17
+ class DialogAction(BaseModel):
18
+ action: Literal["accept", "reject"]
19
+ prompt_instructions: str
20
+
21
+
22
+ class BaseAction(BaseModel):
23
+ xpath: str | None = None
24
+ command: str | None = None
25
+ prompt_instructions: str
26
+ skip_command: bool = False
27
+ skip_prompt: bool = False
28
+ assert_locator_presence: bool = False
29
+
30
+ @model_validator(mode="after")
31
+ def validate_one_extraction(cls, model: "BaseAction"):
32
+ """Ensure exactly one of the extraction types is set and matches the type."""
33
+
34
+ provided = {
35
+ "xpath": model.xpath,
36
+ "command": model.command,
37
+ }
38
+ non_null = [k for k, v in provided.items() if v is not None]
39
+
40
+ if len(non_null) > 1:
41
+ raise ValueError("Exactly one of xpath, command must be provided")
42
+
43
+ if model.assert_locator_presence:
44
+ assert (
45
+ model.command is not None
46
+ ), "command is required when assert_locator_presence is True"
47
+
48
+ if model.command is not None and model.command.strip() == "":
49
+ model.command = None
50
+
51
+ return model
52
+
53
+ def replace(self, pattern: str, replacement: str):
54
+ if self.prompt_instructions:
55
+ self.prompt_instructions = self.prompt_instructions.replace(
56
+ pattern, replacement
57
+ )
58
+ if self.xpath:
59
+ self.xpath = self.xpath.replace(pattern, replacement)
60
+ if self.command:
61
+ self.command = self.command.replace(pattern, replacement).strip('"')
62
+
63
+
64
+ class CheckAction(BaseAction):
65
+ pass
66
+
67
+
68
+ class UncheckAction(BaseAction):
69
+ pass
70
+
71
+
72
+ class SelectOptionAction(BaseAction):
73
+ select_values: list[str]
74
+ expect_download: bool = False
75
+ download_filename: str | None = None
76
+
77
+ @model_validator(mode="after")
78
+ def set_download_filename(cls, model: "SelectOptionAction"):
79
+
80
+ if model.expect_download and model.download_filename is None:
81
+ model.download_filename = str(uuid4())
82
+
83
+ return model
84
+
85
+ def replace(self, pattern: str, replacement: str):
86
+ super().replace(pattern, replacement)
87
+ if self.select_values:
88
+ self.select_values = [
89
+ value.replace(pattern, replacement).strip('"')
90
+ for value in self.select_values
91
+ ]
92
+ if self.download_filename:
93
+ self.download_filename = self.download_filename.replace(
94
+ pattern, replacement
95
+ ).strip('"')
96
+ return self
97
+
98
+
99
+ class ClickElementAction(BaseAction):
100
+ double_click: bool = False
101
+ expect_download: bool = False
102
+ download_filename: str | None = None
103
+
104
+ @model_validator(mode="after")
105
+ def set_download_filename(cls, model: "ClickElementAction"):
106
+
107
+ if model.expect_download and model.download_filename is None:
108
+ model.download_filename = str(uuid4())
109
+
110
+ return model
111
+
112
+ def replace(self, pattern: str, replacement: str):
113
+ super().replace(pattern, replacement)
114
+ if self.download_filename:
115
+ self.download_filename = self.download_filename.replace(
116
+ pattern, replacement
117
+ ).strip('"')
118
+ return self
119
+
120
+
121
+ class InputTextAction(BaseAction):
122
+ input_text: str | None = None
123
+ is_slider: bool = False
124
+ fill_or_type: Literal["fill", "type"] = "fill"
125
+ press_enter: bool = False
126
+
127
+ @model_validator(mode="after")
128
+ def validate_press_enter(self):
129
+ if self.press_enter and self.command is None:
130
+ raise ValueError("command is required when press_enter is True")
131
+ return self
132
+
133
+ def replace(self, pattern: str, replacement: str):
134
+ super().replace(pattern, replacement)
135
+ if self.input_text:
136
+ self.input_text = self.input_text.replace(pattern, replacement).strip('"')
137
+ return self
138
+
139
+
140
+ class DownloadUrlAsPdfAction(BaseModel):
141
+ # Used when the current page is a PDF and we want to download it
142
+ download_filename: str = Field(default_factory=lambda: str(uuid4()))
143
+ url: str | None = None
144
+
145
+ def replace(self, pattern: str, replacement: str):
146
+ if self.download_filename:
147
+ self.download_filename = self.download_filename.replace(
148
+ pattern, replacement
149
+ ).strip('"')
150
+ return self
151
+
152
+
153
+ class ScrollAction(BaseModel):
154
+ down: bool # True to scroll down, False to scroll up
155
+
156
+
157
+ class UploadFileAction(BaseAction):
158
+ file_path: str
159
+
160
+ def replace(self, pattern: str, replacement: str):
161
+ if self.file_path:
162
+ self.file_path = self.file_path.replace(pattern, replacement).strip('"')
163
+ return self
164
+
165
+
166
+ class GoToUrlAction(BaseModel):
167
+ url: str
168
+ new_tab: bool = False # True to open in new tab, False to navigate in current tab
169
+
170
+ def replace(self, pattern: str, replacement: str):
171
+ if self.url:
172
+ self.url = self.url.replace(pattern, replacement).strip('"')
173
+ return self
174
+
175
+
176
+ class GoBackAction(BaseModel):
177
+ pass
178
+
179
+
180
+ class SwitchTabAction(BaseModel):
181
+ tab_index: int
182
+
183
+
184
+ class CloseCurrentTabAction(BaseModel):
185
+ pass
186
+
187
+
188
+ class CloseAllButLastTabAction(BaseModel):
189
+ pass
190
+
191
+
192
+ class CloseTabsUntil(BaseModel):
193
+ matching_url: str | None = None
194
+ tab_index: int | None = None
195
+
196
+ @model_validator(mode="after")
197
+ def validate_one_of_matching_url_or_tab_index(self):
198
+ non_null = [k for k, v in self.model_dump().items() if v is not None]
199
+ if len(non_null) != 1:
200
+ raise ValueError(
201
+ "Exactly one of matching_url or tab_index must be provided"
202
+ )
203
+ return self
204
+
205
+ def replace(self, pattern: str, replacement: str):
206
+ if self.matching_url:
207
+ self.matching_url = self.matching_url.replace(pattern, replacement).strip(
208
+ '"'
209
+ )
210
+ return self
211
+
212
+
213
+ @unique
214
+ class KeyPressType(str, Enum):
215
+ ENTER = "Enter"
216
+ TAB = "Tab"
217
+ DELETE = "Delete"
218
+ BACKSPACE = "Backspace"
219
+ ESCAPE = "Escape"
220
+
221
+
222
+ class KeyPressAction(BaseModel):
223
+ type: KeyPressType
224
+
225
+
226
+ class AgenticTask(BaseModel):
227
+ task: str
228
+ max_steps: int
229
+ backend: Literal["browser_use", "browserbase"]
230
+ use_vision: bool = False
231
+ keep_alive: bool = True
232
+
233
+ def replace(self, pattern: str, replacement: str):
234
+ if self.task:
235
+ self.task = self.task.replace(pattern, replacement).strip('"')
236
+ return self
237
+
238
+
239
+ class CloseOverlayPopupAction(AgenticTask):
240
+ task: str = Field(default=overlay_popup_prompt)
241
+ max_steps: int = Field(default=5)
242
+ backend: Literal["browser_use", "browserbase"] = Field(default="browser_use")
243
+ use_vision: bool = Field(default=True)
244
+ keep_alive: bool = Field(default=True)
245
+
246
+
247
+ class InteractionAction(BaseModel):
248
+ max_tries: int = 10
249
+ max_timeout_seconds_per_try: float = 1.0
250
+ click_element: ClickElementAction | None = None
251
+ input_text: InputTextAction | None = None
252
+ select_option: SelectOptionAction | None = None
253
+ check: CheckAction | None = None
254
+ uncheck: UncheckAction | None = None
255
+ download_url_as_pdf: DownloadUrlAsPdfAction | None = None
256
+ scroll: ScrollAction | None = None
257
+ upload_file: UploadFileAction | None = None
258
+ go_to_url: GoToUrlAction | None = None
259
+ go_back: GoBackAction | None = None
260
+ switch_tab: SwitchTabAction | None = None
261
+ close_current_tab: CloseCurrentTabAction | None = None
262
+ close_all_but_last_tab: CloseAllButLastTabAction | None = None
263
+ close_tabs_until: CloseTabsUntil | None = None
264
+ agentic_task: AgenticTask | None = None
265
+ close_overlay_popup: CloseOverlayPopupAction | None = None
266
+ key_press: KeyPressAction | None = None
267
+
268
+ @model_validator(mode="after")
269
+ def validate_one_interaction(cls, model: "InteractionAction"):
270
+ """Ensure exactly one of the interaction types is set and matches the type."""
271
+ provided = {
272
+ "click_element": model.click_element,
273
+ "input_text": model.input_text,
274
+ "select_option": model.select_option,
275
+ "check": model.check,
276
+ "uncheck": model.uncheck,
277
+ "download_url_as_pdf": model.download_url_as_pdf,
278
+ "scroll": model.scroll,
279
+ "upload_file": model.upload_file,
280
+ "go_to_url": model.go_to_url,
281
+ "go_back": model.go_back,
282
+ "switch_tab": model.switch_tab,
283
+ "close_current_tab": model.close_current_tab,
284
+ "close_all_but_last_tab": model.close_all_but_last_tab,
285
+ "close_tabs_until": model.close_tabs_until,
286
+ "agentic_task": model.agentic_task,
287
+ "close_overlay_popup": model.close_overlay_popup,
288
+ "key_press": model.key_press,
289
+ }
290
+ non_null = [k for k, v in provided.items() if v is not None]
291
+
292
+ if len(non_null) != 1:
293
+ 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"
295
+ )
296
+
297
+ if (
298
+ (model.click_element and model.click_element.skip_prompt)
299
+ or (model.input_text and model.input_text.skip_prompt)
300
+ or (model.select_option and model.select_option.skip_prompt)
301
+ ):
302
+ model.max_tries = 5
303
+
304
+ return model
305
+
306
+ def replace(self, pattern: str, replacement: str):
307
+ if self.click_element:
308
+ self.click_element.replace(pattern, replacement)
309
+ if self.input_text:
310
+ self.input_text.replace(pattern, replacement)
311
+ if self.select_option:
312
+ self.select_option.replace(pattern, replacement)
313
+ if self.check:
314
+ self.check.replace(pattern, replacement)
315
+ if self.uncheck:
316
+ self.uncheck.replace(pattern, replacement)
317
+ if self.download_url_as_pdf:
318
+ self.download_url_as_pdf.replace(pattern, replacement)
319
+ if self.close_tabs_until:
320
+ self.close_tabs_until.replace(pattern, replacement)
321
+ if self.agentic_task:
322
+ self.agentic_task.replace(pattern, replacement)
323
+ if self.close_overlay_popup:
324
+ self.close_overlay_popup.replace(pattern, replacement)
325
+ if self.go_to_url:
326
+ self.go_to_url.replace(pattern, replacement)
327
+ if self.upload_file:
328
+ self.upload_file.replace(pattern, replacement)
329
+
330
+ return self
@@ -0,0 +1,18 @@
1
+ from pydantic import BaseModel
2
+
3
+
4
+ class PythonScriptAction(BaseModel):
5
+ execution_code: str
6
+
7
+
8
+ ## State Jump Actions
9
+ class StateJumpAction(BaseModel):
10
+ next_state_index: int
11
+
12
+
13
+ # class RestartAction(StateJumpAction):
14
+ # next_state_index: 0
15
+
16
+
17
+ # class StopAction(StateJumpAction):
18
+ # next_state_index: -1
@@ -0,0 +1,27 @@
1
+ overlay_popup_prompt = """
2
+ The primary goal of this task is to **automatically dismiss obstructing overlay popups** to enable a human-like, unobstructed view and interaction with the main website content.
3
+
4
+ ---
5
+
6
+ ### 🎯 Goal
7
+ Clear the entire viewport of any modal, overlay, or blocking element that prevents access to the underlying webpage content.
8
+
9
+ ### 📜 Scope of Target Overlays
10
+ Target elements include, but are not limited to, the following common types of overlays:
11
+ * Cookie Consent Banners/Modals
12
+ * Privacy Policy Notices
13
+ * Email/Newsletter Sign-up Prompts
14
+ * Age Verification Gates
15
+ * Blocking Promotional Offers
16
+
17
+ ### ⚙️ Action Priority and Rules
18
+
19
+ The agent must only dismiss overlays that a typical human user would close to proceed with the site. The actions must follow these specific rules in order of priority:
20
+
21
+ 1. **Cookie Consent:** When encountering a cookie or privacy consent overlay, **always accept** or agree to the policy. Click buttons labeled "Accept," "Agree," "Got it," "Allow All," or similar positive confirmation phrases.
22
+ 2. **General Dismissal:** For all other overlays (sign-ups, promotions, etc.), prioritize clicking **dismissive buttons** that close the popup without requiring user input. Look for labels like "Close," "X" (close icon), "No Thanks," "Maybe Later," "Skip," or "Continue to site."
23
+ 3. **Avoidance:** Do **not** input text, or click buttons like "Sign Up," "Learn More," or links that navigate away from the current page (e.g., "Read Full Policy"). The goal is solely to dismiss the current obstruction.
24
+
25
+ ### 🛑 Completion State
26
+ The task is considered complete when the main body of the webpage is fully visible and ready for a user to interact with, meaning **no active overlays** are obstructing the content.
27
+ """
@@ -0,0 +1,24 @@
1
+ from typing import Literal
2
+
3
+ from pydantic import BaseModel
4
+
5
+
6
+ class EmailTwoFAAction(BaseModel):
7
+ type: Literal["email_two_fa_action"]
8
+ receiver_email_address: str
9
+ sender_email_address: str
10
+
11
+
12
+ class SlackTwoFAAction(BaseModel):
13
+ type: Literal["slack_two_fa_action"]
14
+ slack_workspace_domain: str
15
+ channel_name: str
16
+ sender_name: str
17
+
18
+
19
+ class TwoFAAction(BaseModel):
20
+ action: EmailTwoFAAction | SlackTwoFAAction
21
+ instructions: str | None = None
22
+ output_variable_name: str
23
+ max_wait_time: float = 300.0
24
+ check_interval: float = 10.0