optexity 0.1.2__py3-none-any.whl → 0.1.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- optexity/cli.py +1 -1
- optexity/examples/__init__.py +0 -0
- optexity/examples/add_example.py +88 -0
- optexity/examples/download_pdf_url.py +29 -0
- optexity/examples/extract_price_stockanalysis.py +44 -0
- optexity/examples/file_upload.py +59 -0
- optexity/examples/i94.py +126 -0
- optexity/examples/i94_travel_history.py +126 -0
- optexity/examples/peachstate_medicaid.py +201 -0
- optexity/examples/supabase_login.py +75 -0
- optexity/inference/__init__.py +0 -0
- optexity/inference/agents/__init__.py +0 -0
- optexity/inference/agents/error_handler/__init__.py +0 -0
- optexity/inference/agents/error_handler/error_handler.py +39 -0
- optexity/inference/agents/error_handler/prompt.py +60 -0
- optexity/inference/agents/index_prediction/__init__.py +0 -0
- optexity/inference/agents/index_prediction/action_prediction_locator_axtree.py +45 -0
- optexity/inference/agents/index_prediction/prompt.py +14 -0
- optexity/inference/agents/select_value_prediction/__init__.py +0 -0
- optexity/inference/agents/select_value_prediction/prompt.py +20 -0
- optexity/inference/agents/select_value_prediction/select_value_prediction.py +39 -0
- optexity/inference/agents/two_fa_extraction/__init__.py +0 -0
- optexity/inference/agents/two_fa_extraction/prompt.py +23 -0
- optexity/inference/agents/two_fa_extraction/two_fa_extraction.py +47 -0
- optexity/inference/child_process.py +251 -0
- optexity/inference/core/__init__.py +0 -0
- optexity/inference/core/interaction/__init__.py +0 -0
- optexity/inference/core/interaction/handle_agentic_task.py +79 -0
- optexity/inference/core/interaction/handle_check.py +57 -0
- optexity/inference/core/interaction/handle_click.py +79 -0
- optexity/inference/core/interaction/handle_command.py +261 -0
- optexity/inference/core/interaction/handle_input.py +76 -0
- optexity/inference/core/interaction/handle_keypress.py +16 -0
- optexity/inference/core/interaction/handle_select.py +109 -0
- optexity/inference/core/interaction/handle_select_utils.py +132 -0
- optexity/inference/core/interaction/handle_upload.py +59 -0
- optexity/inference/core/interaction/utils.py +81 -0
- optexity/inference/core/logging.py +406 -0
- optexity/inference/core/run_assertion.py +55 -0
- optexity/inference/core/run_automation.py +463 -0
- optexity/inference/core/run_extraction.py +240 -0
- optexity/inference/core/run_interaction.py +254 -0
- optexity/inference/core/run_python_script.py +20 -0
- optexity/inference/core/run_two_fa.py +120 -0
- optexity/inference/core/two_factor_auth/__init__.py +0 -0
- optexity/inference/infra/__init__.py +0 -0
- optexity/inference/infra/browser.py +455 -0
- optexity/inference/infra/browser_extension.py +20 -0
- optexity/inference/models/__init__.py +22 -0
- optexity/inference/models/gemini.py +113 -0
- optexity/inference/models/human.py +20 -0
- optexity/inference/models/llm_model.py +210 -0
- optexity/inference/run_local.py +200 -0
- optexity/schema/__init__.py +0 -0
- optexity/schema/actions/__init__.py +0 -0
- optexity/schema/actions/assertion_action.py +66 -0
- optexity/schema/actions/extraction_action.py +143 -0
- optexity/schema/actions/interaction_action.py +330 -0
- optexity/schema/actions/misc_action.py +18 -0
- optexity/schema/actions/prompts.py +27 -0
- optexity/schema/actions/two_fa_action.py +24 -0
- optexity/schema/automation.py +432 -0
- optexity/schema/callback.py +16 -0
- optexity/schema/inference.py +87 -0
- optexity/schema/memory.py +100 -0
- optexity/schema/task.py +212 -0
- optexity/schema/token_usage.py +48 -0
- optexity/utils/__init__.py +0 -0
- optexity/utils/settings.py +54 -0
- optexity/utils/utils.py +76 -0
- {optexity-0.1.2.dist-info → optexity-0.1.4.dist-info}/METADATA +20 -36
- optexity-0.1.4.dist-info/RECORD +80 -0
- optexity-0.1.2.dist-info/RECORD +0 -11
- {optexity-0.1.2.dist-info → optexity-0.1.4.dist-info}/WHEEL +0 -0
- {optexity-0.1.2.dist-info → optexity-0.1.4.dist-info}/entry_points.txt +0 -0
- {optexity-0.1.2.dist-info → optexity-0.1.4.dist-info}/licenses/LICENSE +0 -0
- {optexity-0.1.2.dist-info → optexity-0.1.4.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
|