optexity 0.1.5.2__py3-none-any.whl → 0.1.5.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.
@@ -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))
@@ -54,11 +54,14 @@ async def run_automation(task: Task, child_process_id: int):
54
54
  logger.info(f"Task {task.task_id} started running")
55
55
  memory = None
56
56
  browser = None
57
+
57
58
  try:
58
59
  await start_task_in_server(task)
59
60
  memory = Memory()
61
+
60
62
  browser = Browser(
61
63
  memory=memory,
64
+ user_data_dir=f"/tmp/userdata_{task.task_id}",
62
65
  headless=False,
63
66
  channel=task.automation.browser_channel,
64
67
  debug_port=9222 + child_process_id,
@@ -66,9 +69,12 @@ async def run_automation(task: Task, child_process_id: int):
66
69
  proxy_session_id=task.proxy_session_id(
67
70
  settings.PROXY_PROVIDER if task.use_proxy else None
68
71
  ),
72
+ is_dedicated=task.is_dedicated,
69
73
  )
70
74
  await browser.start()
71
75
 
76
+ browser.memory = memory
77
+
72
78
  automation = task.automation
73
79
 
74
80
  memory.automation_state.step_index = -1
@@ -2,35 +2,48 @@ import asyncio
2
2
  import base64
3
3
  import json
4
4
  import logging
5
+ import pathlib
5
6
  import re
7
+ import shutil
6
8
  from typing import Literal
7
9
  from uuid import uuid4
8
10
 
11
+ import patchright.async_api
12
+ import playwright.async_api
9
13
  from browser_use import Agent, BrowserSession, ChatGoogle
10
14
  from browser_use.browser.views import BrowserStateSummary
11
15
  from patchright._impl._errors import TimeoutError as PatchrightTimeoutError
12
16
  from playwright._impl._errors import TimeoutError as PlaywrightTimeoutError
13
17
  from playwright.async_api import Download, Locator, Page, Request, Response
14
18
 
19
+ from optexity.inference.infra.utils import _download_extension, _extract_extension
15
20
  from optexity.schema.memory import Memory, NetworkRequest, NetworkResponse
16
21
  from optexity.utils.settings import settings
17
22
 
18
23
  logger = logging.getLogger(__name__)
19
24
 
25
+ _global_playwright: (
26
+ playwright.async_api.Playwright | patchright.async_api.Playwright | None
27
+ ) = None
28
+ _global_context: (
29
+ playwright.async_api.BrowserContext | patchright.async_api.BrowserContext | None
30
+ ) = None
31
+
20
32
 
21
33
  class Browser:
22
34
  def __init__(
23
35
  self,
24
36
  memory: Memory,
25
- user_data_dir: str = None,
37
+ user_data_dir: str,
26
38
  headless: bool = False,
27
- proxy: str = None,
39
+ proxy: str | None = None,
28
40
  stealth: bool = True,
29
41
  backend: Literal["browser-use", "browserbase"] = "browser-use",
30
42
  debug_port: int = 9222,
31
43
  channel: Literal["chromium", "chrome"] = "chromium",
32
44
  use_proxy: bool = False,
33
45
  proxy_session_id: str | None = None,
46
+ is_dedicated: bool = False,
34
47
  ):
35
48
 
36
49
  if proxy:
@@ -44,9 +57,15 @@ class Browser:
44
57
  self.debug_port = debug_port
45
58
  self.use_proxy = use_proxy
46
59
  self.proxy_session_id = proxy_session_id
47
- self.playwright = None
60
+ self.playwright: (
61
+ playwright.async_api.Playwright | patchright.async_api.Playwright | None
62
+ ) = None
48
63
  self.browser = None
49
- self.context = None
64
+ self.context: (
65
+ playwright.async_api.BrowserContext
66
+ | patchright.async_api.BrowserContext
67
+ | None
68
+ ) = None
50
69
  self.page = None
51
70
  self.cdp_url = f"http://localhost:{self.debug_port}"
52
71
  self.backend_agent = None
@@ -54,16 +73,108 @@ class Browser:
54
73
  self.memory = memory
55
74
  self.page_to_target_id = []
56
75
  self.previous_total_pages = 0
57
-
76
+ self.is_dedicated = is_dedicated
58
77
  self.active_downloads = 0
59
78
  self.all_active_downloads_done = asyncio.Event()
60
79
  self.all_active_downloads_done.set()
61
80
 
62
81
  self.network_calls: list[NetworkResponse | NetworkRequest] = []
63
82
 
83
+ self.extensions = [
84
+ # {
85
+ # "name": "optexity recorder",
86
+ # "id": "pbaganbicadeoacahamnbgohafchgakp",
87
+ # "url": "https://clients2.google.com/service/update2/crx?response=redirect&prodversion=133&acceptformat=crx3&x=id%3Dpbaganbicadeoacahamnbgohafchgakp%26uc",
88
+ # },
89
+ {
90
+ "name": "I still don't care about cookies",
91
+ "id": "edibdbjcniadpccecjdfdjjppcpchdlm",
92
+ "url": "https://clients2.google.com/service/update2/crx?response=redirect&prodversion=133&acceptformat=crx3&x=id%3Dedibdbjcniadpccecjdfdjjppcpchdlm%26uc",
93
+ },
94
+ # {
95
+ # "name": "popupoff",
96
+ # "id": "kiodaajmphnkcajieajajinghpejdjai",
97
+ # "url": "https://clients2.google.com/service/update2/crx?response=redirect&prodversion=133&acceptformat=crx3&x=id%3Dkiodaajmphnkcajieajajinghpejdjai%26uc",
98
+ # },
99
+ {
100
+ "name": "ublock origin",
101
+ "id": "ddkjiahejlhfcafbddmgiahcphecmpfh",
102
+ "url": "https://clients2.google.com/service/update2/crx?response=redirect&prodversion=133&acceptformat=crx3&x=id%3Dddkjiahejlhfcafbddmgiahcphecmpfh%26uc",
103
+ },
104
+ ]
105
+
64
106
  async def start(self):
107
+ global _global_playwright, _global_context
65
108
  logger.debug("Starting browser")
66
109
  try:
110
+ cache_dir = pathlib.Path("/tmp/extensions")
111
+ cache_dir.mkdir(parents=True, exist_ok=True)
112
+ extension_paths = []
113
+ loaded_extension_names = []
114
+ for ext in self.extensions:
115
+ ext_dir = cache_dir / ext["id"]
116
+ crx_file = cache_dir / f'{ext["id"]}.crx'
117
+
118
+ # Check if extension is already extracted
119
+ if ext_dir.exists() and (ext_dir / "manifest.json").exists():
120
+ logger.info(
121
+ f'✅ Using cached {ext["name"]} extension from {ext_dir}'
122
+ )
123
+ extension_paths.append(str(ext_dir))
124
+ loaded_extension_names.append(ext["name"])
125
+ continue
126
+
127
+ try:
128
+ # Download extension if not cached
129
+ if not crx_file.exists():
130
+ logger.info(f'📦 Downloading {ext["name"]} extension...')
131
+ _download_extension(ext["url"], crx_file)
132
+ else:
133
+ logger.info(f'📦 Found cached {ext["name"]} .crx file')
134
+
135
+ # Extract extension
136
+ logger.info(f'📂 Extracting {ext["name"]} extension...')
137
+ _extract_extension(crx_file, ext_dir)
138
+
139
+ extension_paths.append(str(ext_dir))
140
+ loaded_extension_names.append(ext["name"])
141
+ logger.info(f'✅ Successfully loaded {ext["name"]}')
142
+
143
+ except Exception as e:
144
+ logger.error(
145
+ f'❌ Failed to setup {ext["name"]} extension: {e}',
146
+ exc_info=True,
147
+ )
148
+ continue
149
+
150
+ if not extension_paths:
151
+ logger.error("⚠️ No extensions were loaded successfully!")
152
+
153
+ logger.info(f"Loaded extensions: {', '.join(loaded_extension_names)}")
154
+
155
+ args = [
156
+ "--disable-site-isolation-trials",
157
+ "--disable-web-security",
158
+ "--disable-features=IsolateOrigins,site-per-process",
159
+ "--allow-running-insecure-content",
160
+ "--ignore-certificate-errors",
161
+ "--ignore-ssl-errors",
162
+ "--ignore-certificate-errors-spki-list",
163
+ "--enable-extensions",
164
+ "--disable-extensions-file-access-check",
165
+ "--disable-extensions-http-throttling",
166
+ ]
167
+
168
+ if extension_paths:
169
+ disable_except = (
170
+ f'--disable-extensions-except={",".join(extension_paths)}'
171
+ )
172
+ load_extension = f'--load-extension={",".join(extension_paths)}'
173
+ args.append(disable_except)
174
+ args.append(load_extension)
175
+ logger.info(f"Extension args: {disable_except}")
176
+ logger.info(f"Extension args: {load_extension}")
177
+
67
178
  if self.playwright is not None:
68
179
  await self.playwright.stop()
69
180
 
@@ -98,44 +209,64 @@ class Browser:
98
209
  if settings.PROXY_PASSWORD is not None:
99
210
  proxy["password"] = settings.PROXY_PASSWORD
100
211
 
101
- self.playwright = await async_playwright().start()
102
- self.browser = await self.playwright.chromium.launch(
103
- channel=self.channel,
104
- headless=self.headless,
105
- proxy=proxy,
106
- args=[
107
- "--start-fullscreen",
108
- "--disable-popup-blocking",
109
- "--window-size=1920,1080",
110
- f"--remote-debugging-port={self.debug_port}",
111
- "--disable-gpu",
112
- "--disable-extensions",
113
- "--disable-background-networking",
114
- ],
115
- chromium_sandbox=False,
116
- )
212
+ if (
213
+ _global_playwright is None
214
+ or _global_context is None
215
+ or not self.is_dedicated
216
+ ):
217
+ self.playwright = await async_playwright().start()
218
+ self.context = await self.playwright.chromium.launch_persistent_context(
219
+ channel=self.channel,
220
+ user_data_dir=self.user_data_dir,
221
+ headless=self.headless,
222
+ proxy=proxy,
223
+ args=[
224
+ # "--start-fullscreen",
225
+ "--disable-popup-blocking",
226
+ "--window-size=1920,1080",
227
+ f"--remote-debugging-port={self.debug_port}",
228
+ "--disable-gpu",
229
+ "--disable-background-networking",
230
+ ]
231
+ + args,
232
+ chromium_sandbox=False,
233
+ no_viewport=True,
234
+ )
235
+ _global_playwright = self.playwright
236
+ _global_context = self.context
117
237
 
118
- self.context = await self.browser.new_context(
119
- no_viewport=True, ignore_https_errors=True
120
- )
238
+ async def log_request(req: Request):
239
+ await self.log_request(req)
121
240
 
122
- async def log_request(req: Request):
123
- await self.log_request(req)
241
+ async def handle_random_download(download: Download):
242
+ await self.handle_random_download(download)
124
243
 
125
- async def handle_random_download(download: Download):
126
- await self.handle_random_download(download)
244
+ async def handle_random_url_downloads(resp: Response):
245
+ await self.handle_random_url_downloads(resp)
127
246
 
128
- async def handle_random_url_downloads(resp: Response):
129
- await self.handle_random_url_downloads(resp)
247
+ self.context.on("request", log_request)
248
+ self.context.on("response", handle_random_url_downloads)
130
249
 
131
- self.context.on("request", log_request)
132
- self.context.on("response", handle_random_url_downloads)
250
+ self.context.on(
251
+ "page", lambda p: (p.on("download", handle_random_download))
252
+ )
133
253
 
134
- self.context.on(
135
- "page", lambda p: (p.on("download", handle_random_download))
136
- )
254
+ elif self.is_dedicated:
255
+ self.context = _global_context
256
+ self.playwright = _global_playwright
257
+ for i in range(len(self.context.pages) - 1, 0, -1):
258
+ await self.context.pages[i].close()
259
+ else:
260
+ raise ValueError(
261
+ "Browser is not dedicated and global playwright and context are not set"
262
+ )
137
263
 
138
- self.page = await self.context.new_page()
264
+ # self.context = await self.browser.new_context(
265
+ # no_viewport=True, ignore_https_errors=True
266
+ # )
267
+
268
+ # self.page = await self.context.new_page()
269
+ self.page = self.context.pages[0]
139
270
 
140
271
  browser_session = BrowserSession(cdp_url=self.cdp_url, keep_alive=True)
141
272
 
@@ -162,7 +293,7 @@ class Browser:
162
293
  raise e
163
294
 
164
295
  async def stop(self):
165
- logger.debug("Stopping full system")
296
+ logger.debug("Stopping backend agent")
166
297
  if self.backend_agent is not None:
167
298
  logger.debug("Stopping backend agent")
168
299
  self.backend_agent.stop()
@@ -174,21 +305,25 @@ class Browser:
174
305
  logger.debug("Browser session reset")
175
306
  self.backend_agent = None
176
307
 
177
- if self.context is not None:
178
- logger.debug("Stopping context")
179
- await self.context.close()
180
- self.context = None
181
-
182
- if self.browser is not None:
183
- logger.debug("Stopping browser")
184
- await self.browser.close()
185
- self.browser = None
186
-
187
- if self.playwright is not None:
188
- logger.debug("Stopping playwright")
189
- await self.playwright.stop()
190
- self.playwright = None
191
- logger.debug("Full system stopped")
308
+ if not self.is_dedicated:
309
+ logger.debug("Stopping context and playwright and browser as not dedicated")
310
+ if self.context is not None:
311
+ logger.debug("Stopping context")
312
+ await self.context.close()
313
+ self.context = None
314
+
315
+ if self.browser is not None:
316
+ logger.debug("Stopping browser")
317
+ await self.browser.close()
318
+ self.browser = None
319
+
320
+ if self.playwright is not None:
321
+ logger.debug("Stopping playwright")
322
+ await self.playwright.stop()
323
+ self.playwright = None
324
+ shutil.rmtree(self.user_data_dir, ignore_errors=True)
325
+ else:
326
+ logger.debug("browser not stopped as dedicated")
192
327
 
193
328
  async def get_current_page(self) -> Page | None:
194
329
  if self.context is None:
@@ -0,0 +1,98 @@
1
+ import json
2
+ import logging
3
+ from pathlib import Path
4
+
5
+ logging.basicConfig(level=logging.INFO)
6
+ logger = logging.getLogger(__name__)
7
+
8
+
9
+ def _download_extension(url: str, output_path: Path) -> None:
10
+ """Download extension .crx file."""
11
+ import urllib.request
12
+
13
+ try:
14
+ logger.info(f"Downloading from: {url}")
15
+ with urllib.request.urlopen(url) as response:
16
+ content = response.read()
17
+ logger.info(f"Downloaded {len(content)} bytes")
18
+ with open(output_path, "wb") as f:
19
+ f.write(content)
20
+ logger.info(f"Saved to: {output_path}")
21
+ except Exception as e:
22
+ raise Exception(f"Failed to download extension: {e}")
23
+
24
+
25
+ def _extract_extension(crx_path: Path, extract_dir: Path) -> None:
26
+ """Extract .crx file to directory."""
27
+ import os
28
+ import shutil
29
+ import zipfile
30
+
31
+ # Remove existing directory
32
+ if extract_dir.exists():
33
+ shutil.rmtree(extract_dir)
34
+
35
+ extract_dir.mkdir(parents=True, exist_ok=True)
36
+
37
+ try:
38
+ # CRX files are ZIP files with a header, try to extract as ZIP
39
+ with zipfile.ZipFile(crx_path, "r") as zip_ref:
40
+ zip_ref.extractall(extract_dir)
41
+
42
+ # Verify manifest exists
43
+ if not (extract_dir / "manifest.json").exists():
44
+ raise Exception("No manifest.json found in extension")
45
+
46
+ logger.info("✅ Extracted as regular ZIP file")
47
+
48
+ except zipfile.BadZipFile:
49
+ logger.info("📦 Processing CRX header...")
50
+ # CRX files have a header before the ZIP data
51
+ with open(crx_path, "rb") as f:
52
+ # Read CRX header to find ZIP start
53
+ magic = f.read(4)
54
+ if magic != b"Cr24":
55
+ raise Exception(f"Invalid CRX file format. Magic: {magic}")
56
+
57
+ version = int.from_bytes(f.read(4), "little")
58
+ logger.info(f"CRX version: {version}")
59
+
60
+ if version == 2:
61
+ pubkey_len = int.from_bytes(f.read(4), "little")
62
+ sig_len = int.from_bytes(f.read(4), "little")
63
+ f.seek(16 + pubkey_len + sig_len)
64
+ elif version == 3:
65
+ header_len = int.from_bytes(f.read(4), "little")
66
+ f.seek(12 + header_len)
67
+ else:
68
+ raise Exception(f"Unsupported CRX version: {version}")
69
+
70
+ # Extract ZIP data
71
+ zip_data = f.read()
72
+ logger.info(f"ZIP data size: {len(zip_data)} bytes")
73
+
74
+ # Write ZIP data to temp file and extract
75
+ import tempfile
76
+
77
+ with tempfile.NamedTemporaryFile(suffix=".zip", delete=False) as temp_zip:
78
+ temp_zip.write(zip_data)
79
+ temp_zip.flush()
80
+
81
+ with zipfile.ZipFile(temp_zip.name, "r") as zip_ref:
82
+ zip_ref.extractall(extract_dir)
83
+
84
+ os.unlink(temp_zip.name)
85
+
86
+ # Remove 'key' from manifest if present (can cause issues)
87
+ manifest_path = extract_dir / "manifest.json"
88
+ if manifest_path.exists():
89
+ data = json.loads(manifest_path.read_text())
90
+ logger.info(f"Manifest version: {data.get('manifest_version')}")
91
+ logger.info(f"Extension name: {data.get('name')}")
92
+
93
+ if "key" in data:
94
+ logger.info("Removing 'key' field from manifest")
95
+ del data["key"]
96
+ manifest_path.write_text(json.dumps(data, indent=2))
97
+ else:
98
+ raise Exception("manifest.json not found after extraction")
optexity/schema/task.py CHANGED
@@ -72,6 +72,7 @@ class Task(BaseModel):
72
72
  max_retries: int = 1
73
73
  api_key: str
74
74
  callback_url: CallbackUrl | None = None
75
+ is_dedicated: bool = False
75
76
 
76
77
  class Config:
77
78
  json_encoders = {datetime: lambda v: v.isoformat() if v is not None else None}
optexity/utils/utils.py CHANGED
@@ -3,6 +3,7 @@ import logging
3
3
  import os
4
4
  from pathlib import Path
5
5
  from typing import List, Optional
6
+ from urllib.parse import urlparse
6
7
 
7
8
  import aiofiles
8
9
  import pyotp
@@ -74,3 +75,16 @@ async def get_onepassword_value(vault_name: str, item_name: str, field_name: str
74
75
  )
75
76
 
76
77
  return str_value
78
+
79
+
80
+ def clean_url(url: str) -> str:
81
+ if not url.startswith(("http://", "https://")):
82
+ url = "http://" + url # needed for urlparse
83
+
84
+ parsed = urlparse(url)
85
+ domain = parsed.netloc.lower()
86
+
87
+ if domain.startswith("www."):
88
+ domain = domain[4:]
89
+
90
+ return domain
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: optexity
3
- Version: 0.1.5.2
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
@@ -29,9 +29,9 @@ optexity/inference/agents/two_fa_extraction/__init__.py,sha256=47DEQpj8HBSa-_TIm
29
29
  optexity/inference/agents/two_fa_extraction/prompt.py,sha256=eAqz_InZeyTnFqPMeYm-xyF7rtFWMBIF7nQW3QJnW08,1328
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
- optexity/inference/core/logging.py,sha256=Zq2KL6XWlPIgsJjprxv-BLsnP5MAAKS2ciNMNkQrfBU,13870
32
+ optexity/inference/core/logging.py,sha256=0iMJq6f2mC3xphoEbQ_LqwzwkMP96B7NKst-rW4pCyM,14129
33
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
34
+ optexity/inference/core/run_automation.py,sha256=Xns3ic4zByRN9Owgn7cRfh-WnN_kohm2_EZMt9EVHLw,16815
35
35
  optexity/inference/core/run_extraction.py,sha256=JLdMIUM0syc3yffjkrDQ3SM2VID-tZW-Zoi7ZD3uURM,9089
36
36
  optexity/inference/core/run_interaction.py,sha256=R1llSQKAQUmll4n03x34TEBgxn5Q8jC2mborJ1EAszw,9739
37
37
  optexity/inference/core/run_python_script.py,sha256=WjnCmckZz7YmoLTGBLZeFWhhS1s_x0-kgyKYTM17JHI,547
@@ -50,8 +50,9 @@ optexity/inference/core/interaction/handle_upload.py,sha256=Yrt9KZ-rmgJ-DK5IntIX
50
50
  optexity/inference/core/interaction/utils.py,sha256=_TMt0XBIIJi2K7nVQGf4PMZ-c9SCDehcMIEGPQx1GS8,2842
51
51
  optexity/inference/core/two_factor_auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
52
52
  optexity/inference/infra/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
53
- optexity/inference/infra/browser.py,sha256=GpECOdxhpe5AbwU2FnRmR7jevKUiHHEZCq7PgpMI8t0,15955
53
+ optexity/inference/infra/browser.py,sha256=NmmJjmqpQ_AITiNU1lDxAM8AaXE7obguRwPFZsSssBs,22143
54
54
  optexity/inference/infra/browser_extension.py,sha256=Ur5_8DrHcgRkD9uulDsVaaj01z1WcsvqHxHok4p4RoM,589
55
+ optexity/inference/infra/utils.py,sha256=2BLTC93oHHKC_XDp1aas2lEeupjyHkNSMbyuKrLA418,3399
55
56
  optexity/inference/models/__init__.py,sha256=f66ea3I8ibNrkn4Uf_wYIn-2eoP-ReXds03LgaopOfA,667
56
57
  optexity/inference/models/gemini.py,sha256=ToncY6Ft4kOgIm6qREBVsScT8FG-JCrdWsBxYOgU0is,4100
57
58
  optexity/inference/models/human.py,sha256=K2X6Ohg7xeTWDYkJY6lOAwS9T3nX7YST-cvd8nL1Ydw,394
@@ -61,7 +62,7 @@ optexity/schema/automation.py,sha256=NFy6jxjXNkv4LF5sa9TH1-_ti3QARnCnsww1dsONJfg
61
62
  optexity/schema/callback.py,sha256=MlN41A6oKG7QX01_w0tsxyAFKWnoCVsu_mjpWzPMYuE,519
62
63
  optexity/schema/inference.py,sha256=8mP49IRU-cRxbsC4NnoGZhd5isvdocCuMVspPBOQV9o,2864
63
64
  optexity/schema/memory.py,sha256=e3AMDAivCF_KnKbeDugqVLib8UN_6dr3ksUCiWaeIGM,3072
64
- optexity/schema/task.py,sha256=8rfg2ertmlyQMJcj28anvHc7r2loEDkz8P_Z10Z9UXk,6865
65
+ optexity/schema/task.py,sha256=7r7MiWp9_QaS1_vQXsFlZkpMsAjSiDFNsOl4MxUFfdE,6896
65
66
  optexity/schema/token_usage.py,sha256=iwZjUqTrNhrEBMeeZNMWqD4qs2toKDczRVoDTOLLNso,2114
66
67
  optexity/schema/actions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
67
68
  optexity/schema/actions/assertion_action.py,sha256=lcD6h7phacrEoB5j-It9qP9Ym3TKb1bkv1aa8tqwG-Q,1991
@@ -72,10 +73,10 @@ optexity/schema/actions/prompts.py,sha256=GZud5T2kQvQKhAXHmAnalVUP8iMcDz8be3jRp-
72
73
  optexity/schema/actions/two_fa_action.py,sha256=fcnuxlM3B4RguPUDw18RvMUpzLdPBRVGVC3gM_e1wLE,564
73
74
  optexity/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
74
75
  optexity/utils/settings.py,sha256=h6StXzYslRgZf0c8k43-kOxoa77dOgDvSOvfQUi5yI8,1864
75
- optexity/utils/utils.py,sha256=QgVeKK3jAq-TLgP_RYiCXRAOEbuypFox0RxYEjruoTA,2565
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,,
76
+ optexity/utils/utils.py,sha256=4RZs_EFJlAJukYsEb-w5XgrL3VZ3rORTNF5tGSSzqBM,2883
77
+ optexity-0.1.5.3.dist-info/licenses/LICENSE,sha256=WpSBqSAcwd68PmS3zRsfACJOz-u-UfTzftsEnzp4ZCY,1065
78
+ optexity-0.1.5.3.dist-info/METADATA,sha256=1M6zbsuB5kWyXSHw_m0kKxZdKBlP5cfYMwCo4M1laXc,9826
79
+ optexity-0.1.5.3.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
80
+ optexity-0.1.5.3.dist-info/entry_points.txt,sha256=hcn77ooRr6a_N8fo0vij3Fpo6waqc9ijpaScQ7Kj35k,47
81
+ optexity-0.1.5.3.dist-info/top_level.txt,sha256=OZEtBX8IabC8EnBrNW98z7NzdGQsjFhHleSthhjjEMM,9
82
+ optexity-0.1.5.3.dist-info/RECORD,,