sunholo 0.74.8__tar.gz → 0.75.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. {sunholo-0.74.8 → sunholo-0.75.0}/PKG-INFO +2 -2
  2. {sunholo-0.74.8 → sunholo-0.75.0}/setup.py +1 -1
  3. sunholo-0.75.0/sunholo/auth/__init__.py +2 -0
  4. sunholo-0.75.0/sunholo/auth/gcloud.py +14 -0
  5. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/auth/run.py +4 -12
  6. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/tools/web_browser.py +110 -11
  7. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/utils/parsers.py +1 -1
  8. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/vertex/extensions_class.py +19 -0
  9. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo.egg-info/PKG-INFO +2 -2
  10. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo.egg-info/SOURCES.txt +1 -0
  11. sunholo-0.74.8/sunholo/auth/__init__.py +0 -1
  12. {sunholo-0.74.8 → sunholo-0.75.0}/LICENSE.txt +0 -0
  13. {sunholo-0.74.8 → sunholo-0.75.0}/MANIFEST.in +0 -0
  14. {sunholo-0.74.8 → sunholo-0.75.0}/README.md +0 -0
  15. {sunholo-0.74.8 → sunholo-0.75.0}/setup.cfg +0 -0
  16. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/__init__.py +0 -0
  17. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/agents/__init__.py +0 -0
  18. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/agents/chat_history.py +0 -0
  19. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/agents/dispatch_to_qa.py +0 -0
  20. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/agents/fastapi/__init__.py +0 -0
  21. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/agents/fastapi/base.py +0 -0
  22. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/agents/fastapi/qna_routes.py +0 -0
  23. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/agents/flask/__init__.py +0 -0
  24. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/agents/flask/base.py +0 -0
  25. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/agents/flask/qna_routes.py +0 -0
  26. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/agents/flask/vac_routes.py +0 -0
  27. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/agents/langserve.py +0 -0
  28. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/agents/pubsub.py +0 -0
  29. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/agents/route.py +0 -0
  30. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/agents/special_commands.py +0 -0
  31. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/agents/swagger.py +0 -0
  32. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/archive/__init__.py +0 -0
  33. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/archive/archive.py +0 -0
  34. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/bots/__init__.py +0 -0
  35. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/bots/discord.py +0 -0
  36. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/bots/github_webhook.py +0 -0
  37. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/bots/webapp.py +0 -0
  38. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/chunker/__init__.py +0 -0
  39. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/chunker/data_to_embed_pubsub.py +0 -0
  40. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/chunker/doc_handling.py +0 -0
  41. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/chunker/images.py +0 -0
  42. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/chunker/loaders.py +0 -0
  43. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/chunker/message_data.py +0 -0
  44. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/chunker/pdfs.py +0 -0
  45. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/chunker/publish.py +0 -0
  46. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/chunker/splitter.py +0 -0
  47. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/cli/__init__.py +0 -0
  48. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/cli/chat_vac.py +0 -0
  49. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/cli/cli.py +0 -0
  50. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/cli/cli_init.py +0 -0
  51. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/cli/configs.py +0 -0
  52. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/cli/deploy.py +0 -0
  53. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/cli/embedder.py +0 -0
  54. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/cli/merge_texts.py +0 -0
  55. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/cli/run_proxy.py +0 -0
  56. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/cli/sun_rich.py +0 -0
  57. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/cli/swagger.py +0 -0
  58. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/components/__init__.py +0 -0
  59. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/components/llm.py +0 -0
  60. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/components/retriever.py +0 -0
  61. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/components/vectorstore.py +0 -0
  62. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/database/__init__.py +0 -0
  63. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/database/alloydb.py +0 -0
  64. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/database/alloydb_client.py +0 -0
  65. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/database/database.py +0 -0
  66. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/database/lancedb.py +0 -0
  67. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/database/sql/sb/create_function.sql +0 -0
  68. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/database/sql/sb/create_function_time.sql +0 -0
  69. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/database/sql/sb/create_table.sql +0 -0
  70. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/database/sql/sb/delete_source_row.sql +0 -0
  71. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/database/sql/sb/return_sources.sql +0 -0
  72. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/database/sql/sb/setup.sql +0 -0
  73. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/database/static_dbs.py +0 -0
  74. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/database/uuid.py +0 -0
  75. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/discovery_engine/__init__.py +0 -0
  76. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/discovery_engine/chunker_handler.py +0 -0
  77. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/discovery_engine/create_new.py +0 -0
  78. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/discovery_engine/discovery_engine_client.py +0 -0
  79. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/embedder/__init__.py +0 -0
  80. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/embedder/embed_chunk.py +0 -0
  81. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/gcs/__init__.py +0 -0
  82. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/gcs/add_file.py +0 -0
  83. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/gcs/download_url.py +0 -0
  84. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/gcs/metadata.py +0 -0
  85. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/invoke/__init__.py +0 -0
  86. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/invoke/invoke_vac_utils.py +0 -0
  87. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/langfuse/__init__.py +0 -0
  88. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/langfuse/callback.py +0 -0
  89. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/langfuse/prompts.py +0 -0
  90. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/llamaindex/__init__.py +0 -0
  91. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/llamaindex/generate.py +0 -0
  92. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/llamaindex/get_files.py +0 -0
  93. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/llamaindex/import_files.py +0 -0
  94. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/logging.py +0 -0
  95. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/lookup/__init__.py +0 -0
  96. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/lookup/model_lookup.yaml +0 -0
  97. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/patches/__init__.py +0 -0
  98. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/patches/langchain/__init__.py +0 -0
  99. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/patches/langchain/lancedb.py +0 -0
  100. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/patches/langchain/vertexai.py +0 -0
  101. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/pubsub/__init__.py +0 -0
  102. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/pubsub/process_pubsub.py +0 -0
  103. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/pubsub/pubsub_manager.py +0 -0
  104. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/qna/__init__.py +0 -0
  105. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/qna/parsers.py +0 -0
  106. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/qna/retry.py +0 -0
  107. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/streaming/__init__.py +0 -0
  108. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/streaming/content_buffer.py +0 -0
  109. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/streaming/langserve.py +0 -0
  110. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/streaming/stream_lookup.py +0 -0
  111. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/streaming/streaming.py +0 -0
  112. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/summarise/__init__.py +0 -0
  113. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/summarise/summarise.py +0 -0
  114. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/tools/__init__.py +0 -0
  115. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/utils/__init__.py +0 -0
  116. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/utils/api_key.py +0 -0
  117. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/utils/big_context.py +0 -0
  118. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/utils/config.py +0 -0
  119. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/utils/config_class.py +0 -0
  120. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/utils/config_schema.py +0 -0
  121. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/utils/gcp.py +0 -0
  122. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/utils/gcp_project.py +0 -0
  123. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/utils/timedelta.py +0 -0
  124. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/utils/user_ids.py +0 -0
  125. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/utils/version.py +0 -0
  126. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/vertex/__init__.py +0 -0
  127. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/vertex/init.py +0 -0
  128. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/vertex/memory_tools.py +0 -0
  129. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo/vertex/safety.py +0 -0
  130. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo.egg-info/dependency_links.txt +0 -0
  131. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo.egg-info/entry_points.txt +0 -0
  132. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo.egg-info/requires.txt +0 -0
  133. {sunholo-0.74.8 → sunholo-0.75.0}/sunholo.egg-info/top_level.txt +0 -0
  134. {sunholo-0.74.8 → sunholo-0.75.0}/tests/test_chat_history.py +0 -0
  135. {sunholo-0.74.8 → sunholo-0.75.0}/tests/test_chunker.py +0 -0
  136. {sunholo-0.74.8 → sunholo-0.75.0}/tests/test_config.py +0 -0
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sunholo
3
- Version: 0.74.8
3
+ Version: 0.75.0
4
4
  Summary: Large Language Model DevOps - a package to help deploy LLMs to the Cloud.
5
5
  Home-page: https://github.com/sunholo-data/sunholo-py
6
- Download-URL: https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.74.8.tar.gz
6
+ Download-URL: https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.75.0.tar.gz
7
7
  Author: Holosun ApS
8
8
  Author-email: multivac@sunholo.com
9
9
  License: Apache License, Version 2.0
@@ -1,7 +1,7 @@
1
1
  from setuptools import setup, find_packages
2
2
 
3
3
  # Define your base version
4
- version = '0.74.8'
4
+ version = '0.75.0'
5
5
 
6
6
  setup(
7
7
  name='sunholo',
@@ -0,0 +1,2 @@
1
+ from .run import get_header
2
+ from .gcloud import get_local_gcloud_token
@@ -0,0 +1,14 @@
1
+ import subprocess
2
+
3
+ def get_local_gcloud_token():
4
+ # Use gcloud credentials locally
5
+
6
+ return (
7
+ subprocess.run(
8
+ ["gcloud", "auth", "print-identity-token"],
9
+ stdout=subprocess.PIPE,
10
+ check=True,
11
+ )
12
+ .stdout.strip()
13
+ .decode()
14
+ )
@@ -7,6 +7,7 @@ from ..utils.gcp import is_running_on_cloudrun
7
7
  from ..utils.api_key import has_multivac_api_key, get_multivac_api_key
8
8
  from ..logging import log
9
9
  from ..agents.route import route_vac
10
+ from .gcloud import get_local_gcloud_token
10
11
 
11
12
  def get_run_url(vector_name=None):
12
13
 
@@ -33,20 +34,11 @@ def get_id_token(url: str) -> str:
33
34
  import google.oauth2.id_token # type: ignore
34
35
  auth_req = google.auth.transport.requests.Request()
35
36
  log.info(f'Got id_token for {url}')
37
+
36
38
  return google.oauth2.id_token.fetch_id_token(auth_req, url)
37
- else:
38
- # Use gcloud credentials locally
39
- import subprocess
40
39
 
41
- return (
42
- subprocess.run(
43
- ["gcloud", "auth", "print-identity-token"],
44
- stdout=subprocess.PIPE,
45
- check=True,
46
- )
47
- .stdout.strip()
48
- .decode()
49
- )
40
+ return get_local_gcloud_token()
41
+
50
42
 
51
43
  def get_header(vector_name) -> Optional[dict]:
52
44
  if has_multivac_api_key():
@@ -213,6 +213,35 @@ class BrowseWebWithImagePromptsBot:
213
213
  except Exception as err:
214
214
  log.warning(f"navigate failed with {str(err)}")
215
215
  self.action_log.append(f"Tried to navigate to {url} but got an error")
216
+
217
+ def get_locator_via_roles_and_placeholder(self, selector: str):
218
+ interactive_roles = ["button", "link", "menuitem", "menuitemcheckbox", "menuitemradio", "tab", "option"]
219
+
220
+ for role in interactive_roles:
221
+ log.info(f'Trying role {role} for selector {selector}')
222
+ elements = self.page.get_by_role(role).get_by_placeholder(selector).locator("visible=true").all()
223
+ if elements:
224
+ log.info(f"Got {len(elements)} elements for selector {selector} with role {role}")
225
+ for element in elements:
226
+ try:
227
+ log.info(f"Trying {selector} with element.hover locator: {element}")
228
+ try:
229
+ element.hover(timeout=10000, trial=True)
230
+ self.action_log.append(f"Successfully found element via selector: {selector}")
231
+
232
+ return element
233
+
234
+ except Exception as err:
235
+ log.warning(f"Could not hover over element: {element} {str(err)} - trying next element")
236
+ except Exception as e:
237
+ log.error(f"Failed to get locator for selector '{selector}' with role {role}: {str(e)}")
238
+
239
+ time.sleep(0.5) # Wait for a bit before retrying
240
+
241
+ log.info(f"No elements for '{selector}' within role '{role}'")
242
+
243
+ self.action_log.append(f"FAILED: Using page.get_by_role('role').get_by_placeholder('{selector}').locator('visible=true') could not find any valid element. Try something else.")
244
+ return None
216
245
 
217
246
  def get_locator_via_roles_and_text(self, selector: str):
218
247
  interactive_roles = ["button", "link", "menuitem", "menuitemcheckbox", "menuitemradio", "tab", "option"]
@@ -240,7 +269,7 @@ class BrowseWebWithImagePromptsBot:
240
269
 
241
270
  log.info(f"No elements for '{selector}' within role '{role}'")
242
271
 
243
- self.action_log.append(f"FAILED: Using page.get_by_role('role').locator('text={selector}').locator('visible=true') could not find any valid element. Try something else.")
272
+ self.action_log.append(f"FAILED: Using page.get_by_role('role').get_by_text('{selector}').locator('visible=true') could not find any valid element. Try something else.")
244
273
  return None
245
274
 
246
275
  def get_locator(self, selector, by_text=True):
@@ -256,10 +285,10 @@ class BrowseWebWithImagePromptsBot:
256
285
 
257
286
  return None
258
287
 
259
- def click(self, selector, by_text=True):
288
+ def click(self, selector):
260
289
  (x,y)=(0,0)
261
290
 
262
- element = self.get_locator(selector, by_text=by_text)
291
+ element = self.get_locator_via_roles_and_text(selector)
263
292
  if element is None:
264
293
  self.action_log.append(f"Tried to click on text {selector} but it was not a valid location to click")
265
294
  return (x,y)
@@ -303,9 +332,9 @@ class BrowseWebWithImagePromptsBot:
303
332
  log.warning(f"Scrolled failed with {str(err)}")
304
333
  self.action_log.append(f"Tried to scroll {direction} by {amount} pixels but got an error")
305
334
 
306
- def type_text(self, selector, text, by_text=True):
335
+ def type_text(self, selector, text):
307
336
  (x,y)=(0,0)
308
- element = self.get_locator(selector, by_text=by_text)
337
+ element = self.get_locator_via_roles_and_placeholder(selector)
309
338
  if element is None:
310
339
  self.action_log.append(f"Tried to type {text} via website text: {selector} but it was not a valid element to add text")
311
340
  return (x,y)
@@ -332,6 +361,68 @@ class BrowseWebWithImagePromptsBot:
332
361
 
333
362
  return (x, y)
334
363
 
364
+ def execute_custom_command(self, command):
365
+ """
366
+ Executes a custom command on the page object.
367
+
368
+ Args:
369
+ command (str): The command string to be executed.
370
+ """
371
+ try:
372
+ element_part = command.get('get_locator')
373
+ operation = command.get('operation')
374
+
375
+ if not element_part or not operation:
376
+ raise ValueError("Both 'element_part' and 'operation' must be provided in the command")
377
+
378
+ # Dynamically get the method and its parameters
379
+ method_name, params = self.parse_element_part(element_part)
380
+ method = getattr(self.page, method_name)
381
+ element = method(*params)
382
+
383
+ if not element:
384
+ raise ValueError(f"Element not found for selector: {element_part}")
385
+
386
+ # Execute the operation
387
+ exec(f"element.{operation}")
388
+
389
+ # Mark the action on the screenshot
390
+ bounding_box = element.bounding_box()
391
+ if bounding_box:
392
+ x = bounding_box['x'] + bounding_box['width'] / 2
393
+ y = bounding_box['y'] + bounding_box['height'] / 2
394
+ mark_action = {'type': operation, 'position': (x, y)}
395
+ self.take_screenshot(mark_action=mark_action)
396
+ else:
397
+ self.take_screenshot()
398
+
399
+ log.info(f"Executed custom command on element: {element_part} with operation: {operation}")
400
+ self.action_log.append(f"Executed custom command on element: {element_part} with operation: {operation}")
401
+
402
+ except Exception as e:
403
+ log.error(f"Failed to execute custom command: {command}. Error: {str(e)}")
404
+ self.action_log.append(f"Failed to execute custom command: {command}. Error: {str(e)}")
405
+
406
+ def parse_element_part(self, element_part):
407
+ """
408
+ Parses the element_part string to extract the method name and its parameters.
409
+
410
+ Args:
411
+ element_part (str): The element part string (e.g., "get_by_role('button')")
412
+
413
+ Returns:
414
+ tuple: A tuple containing the method name and a list of parameters.
415
+ """
416
+ try:
417
+ # Extract the method name and parameters
418
+ method_name = element_part.split('(')[0]
419
+ params_str = element_part.split('(')[1].rstrip(')')
420
+ params = eval(f'[{params_str}]') # Safely evaluate parameters
421
+
422
+ return method_name, params
423
+ except Exception as e:
424
+ raise ValueError(f"Failed to parse element part: {element_part}. Error: {str(e)}")
425
+
335
426
  def take_screenshot(self, full_page=False, mark_action=None):
336
427
 
337
428
  from PIL import Image
@@ -344,6 +435,10 @@ class BrowseWebWithImagePromptsBot:
344
435
  url_path = "index.html"
345
436
  else:
346
437
  url_path = url_path.replace("/","_")
438
+
439
+ if get_clean_website_name(url_path) != self.website_name:
440
+ url_path = f"{get_clean_website_name(url_path)}_{url_path}"
441
+
347
442
  screenshot_path = os.path.join(self.screenshot_dir, f"{timestamp}_{url_path}.png")
348
443
  screenshot_bytes = self.page.screenshot(full_page=full_page, scale='css')
349
444
 
@@ -358,7 +453,7 @@ class BrowseWebWithImagePromptsBot:
358
453
  #self.action_log.append(f"Screenshot {self.page.url} taken and saved to {screenshot_path}")
359
454
  self.session_screenshots.append(screenshot_path)
360
455
 
361
- return screenshot_path
456
+ return screenshot_bytes
362
457
 
363
458
  def mark_screenshot(self, screenshot_bytes, mark_action):
364
459
  """
@@ -433,9 +528,9 @@ class BrowseWebWithImagePromptsBot:
433
528
 
434
529
  return output
435
530
 
436
- def send_screenshot_to_llm(self, screenshot_path, last_message):
437
- with open(screenshot_path, "rb") as image_file:
438
- encoded_image = base64.b64encode(image_file.read()).decode('utf-8')
531
+ def send_screenshot_to_llm(self, screenshot_bytes, last_message):
532
+
533
+ encoded_image = base64.b64encode(screenshot_bytes).decode('utf-8')
439
534
 
440
535
  prompt_vars = self.create_prompt_vars(last_message)
441
536
  response = self.send_prompt_to_llm(prompt_vars, encoded_image) # Sending prompt and image separately
@@ -490,14 +585,18 @@ This method should be implemented by subclasses: `def send_prompt_to_llm(self, p
490
585
  x,y = self.type_text(instruction['selector'], instruction['text'])
491
586
  if (x,y) != (0,0):
492
587
  mark_action = {'type':'type', 'position': (x,y)}
588
+ elif action == 'execute':
589
+ x,y,mark = self.execute_custom_command(instruction['command'])
590
+ if mark:
591
+ mark_action = {'type': mark, 'position': (x,y)}
493
592
  self.steps += 1
494
593
  if self.steps >= self.max_steps:
495
594
  log.warning(f"Reached the maximum number of steps: {self.max_steps}")
496
595
  return
497
596
  time.sleep(2)
498
- screenshot_path = self.take_screenshot(mark_action=mark_action)
597
+ screenshot_bytes = self.take_screenshot(mark_action=mark_action)
499
598
  next_browser_instructions = self.send_screenshot_to_llm(
500
- screenshot_path,
599
+ screenshot_bytes,
501
600
  last_message=last_message)
502
601
 
503
602
  return next_browser_instructions
@@ -186,6 +186,6 @@ def escape_braces(text):
186
186
  text = re.sub(r'(?<!})}(?!})', '}}', text) # Replace '}' with '}}' if not already double braced
187
187
  return text
188
188
 
189
- def get_clean_website_name(url):
189
+ def get_clean_website_name(url: str):
190
190
  parsed_url = urllib.parse.urlparse(url)
191
191
  return parsed_url.netloc
@@ -236,6 +236,7 @@ class VertexAIExtensions:
236
236
  if extension_name is None:
237
237
  raise ValueError("Must specify extension_id or init one with class")
238
238
  else:
239
+ extension_id = str(extension_id)
239
240
  if not extension_id.startswith("projects/"):
240
241
  project_id = get_gcp_project()
241
242
  extension_name = f"projects/{project_id}/locations/{self.location}/extensions/{extension_id}"
@@ -244,11 +245,29 @@ class VertexAIExtensions:
244
245
 
245
246
  extension = extensions.Extension(extension_name)
246
247
 
248
+ log.info(f"Executing extension {extension_name=} with {operation_id=} and {operation_params=}")
249
+
250
+ # local testing auth
251
+ from ..utils.gcp import is_running_on_cloudrun
252
+ auth_config=None # on cloud run it sorts itself out via default creds(?)
253
+
254
+ if not is_running_on_cloudrun():
255
+ from ..auth import get_local_gcloud_token
256
+ log.warning("Using local authentication via gcloud")
257
+ auth_config = {
258
+ "authType": "OAUTH",
259
+ "oauth_config": {"access_token": f"'{get_local_gcloud_token()}'"}
260
+ }
261
+ log.info(auth_config)
262
+
247
263
  response = extension.execute(
248
264
  operation_id=operation_id,
249
265
  operation_params=operation_params,
266
+ runtime_auth_config=auth_config
250
267
  )
251
268
 
269
+ log.info(f"Extension {extension_name=} {response=}")
270
+
252
271
  return response
253
272
 
254
273
  def execute_code_extension(self,
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sunholo
3
- Version: 0.74.8
3
+ Version: 0.75.0
4
4
  Summary: Large Language Model DevOps - a package to help deploy LLMs to the Cloud.
5
5
  Home-page: https://github.com/sunholo-data/sunholo-py
6
- Download-URL: https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.74.8.tar.gz
6
+ Download-URL: https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.75.0.tar.gz
7
7
  Author: Holosun ApS
8
8
  Author-email: multivac@sunholo.com
9
9
  License: Apache License, Version 2.0
@@ -29,6 +29,7 @@ sunholo/agents/flask/vac_routes.py
29
29
  sunholo/archive/__init__.py
30
30
  sunholo/archive/archive.py
31
31
  sunholo/auth/__init__.py
32
+ sunholo/auth/gcloud.py
32
33
  sunholo/auth/run.py
33
34
  sunholo/bots/__init__.py
34
35
  sunholo/bots/discord.py
@@ -1 +0,0 @@
1
- from .run import get_header
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes