add timezone support (#1389)
This commit is contained in:
@@ -1310,6 +1310,7 @@ class ForgeAgent:
|
|||||||
if not template:
|
if not template:
|
||||||
raise UnsupportedTaskType(task_type=task_type)
|
raise UnsupportedTaskType(task_type=task_type)
|
||||||
|
|
||||||
|
context = skyvern_context.ensure_context()
|
||||||
return prompt_engine.load_prompt(
|
return prompt_engine.load_prompt(
|
||||||
template=template,
|
template=template,
|
||||||
navigation_goal=navigation_goal,
|
navigation_goal=navigation_goal,
|
||||||
@@ -1320,7 +1321,7 @@ class ForgeAgent:
|
|||||||
data_extraction_goal=task.data_extraction_goal,
|
data_extraction_goal=task.data_extraction_goal,
|
||||||
action_history=actions_and_results_str,
|
action_history=actions_and_results_str,
|
||||||
error_code_mapping_str=(json.dumps(task.error_code_mapping) if task.error_code_mapping else None),
|
error_code_mapping_str=(json.dumps(task.error_code_mapping) if task.error_code_mapping else None),
|
||||||
utc_datetime=datetime.utcnow().strftime("%Y-%m-%d %H:%M"),
|
local_datetime=datetime.now(context.tz_info).isoformat(),
|
||||||
verification_code_check=verification_code_check,
|
verification_code_check=verification_code_check,
|
||||||
complete_criterion=task.complete_criterion,
|
complete_criterion=task.complete_criterion,
|
||||||
terminate_criterion=task.terminate_criterion,
|
terminate_criterion=task.terminate_criterion,
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ Select History:
|
|||||||
{{ select_history }}
|
{{ select_history }}
|
||||||
```
|
```
|
||||||
{% endif %}
|
{% endif %}
|
||||||
Current datetime in UTC, YYYY-MM-DD HH:MM format:
|
Current datetime, ISO format:
|
||||||
```
|
```
|
||||||
{{ utc_datetime }}
|
{{ local_datetime }}
|
||||||
```
|
```
|
||||||
@@ -49,7 +49,7 @@ User details:
|
|||||||
{{ navigation_payload_str }}
|
{{ navigation_payload_str }}
|
||||||
```
|
```
|
||||||
|
|
||||||
Current datetime in UTC, YYYY-MM-DD HH:MM format:
|
Current datetime, ISO format:
|
||||||
```
|
```
|
||||||
{{ utc_datetime }}
|
{{ local_datetime }}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ User details:
|
|||||||
Action results from previous steps: (note: even if the action history suggests goal is achieved, check the screenshot and the DOM elements to make sure the goal is achieved)
|
Action results from previous steps: (note: even if the action history suggests goal is achieved, check the screenshot and the DOM elements to make sure the goal is achieved)
|
||||||
{{ action_history }}
|
{{ action_history }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
Current datetime in UTC, YYYY-MM-DD HH:MM format:
|
Current datetime, ISO format:
|
||||||
```
|
```
|
||||||
{{ utc_datetime }}
|
{{ local_datetime }}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ Text extracted from the webpage: {{ extracted_text }}
|
|||||||
|
|
||||||
User Navigation Payload: {{ navigation_payload }}
|
User Navigation Payload: {{ navigation_payload }}
|
||||||
|
|
||||||
Current datetime in UTC, YYYY-MM-DD HH:MM format:
|
Current datetime, ISO format:
|
||||||
```
|
```
|
||||||
{{ utc_datetime }}
|
{{ local_datetime }}
|
||||||
```
|
```
|
||||||
@@ -35,7 +35,7 @@ User details:
|
|||||||
{{ navigation_payload_str }}
|
{{ navigation_payload_str }}
|
||||||
```
|
```
|
||||||
|
|
||||||
Current datetime in UTC, YYYY-MM-DD HH:MM format:
|
Current datetime, ISO format:
|
||||||
```
|
```
|
||||||
{{ utc_datetime }}
|
{{ local_datetime }}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ User details:
|
|||||||
{{ navigation_payload_str }}
|
{{ navigation_payload_str }}
|
||||||
```
|
```
|
||||||
|
|
||||||
Current datetime in UTC, YYYY-MM-DD HH:MM format:
|
Current datetime, ISO format:
|
||||||
```
|
```
|
||||||
{{ utc_datetime }}
|
{{ local_datetime }}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ User details:
|
|||||||
{{ navigation_payload_str }}
|
{{ navigation_payload_str }}
|
||||||
```
|
```
|
||||||
|
|
||||||
Current datetime in UTC, YYYY-MM-DD HH:MM format:
|
Current datetime, ISO format:
|
||||||
```
|
```
|
||||||
{{ utc_datetime }}
|
{{ local_datetime }}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ User details:
|
|||||||
{{ navigation_payload_str }}
|
{{ navigation_payload_str }}
|
||||||
```
|
```
|
||||||
|
|
||||||
Current datetime in UTC, YYYY-MM-DD HH:MM format:
|
Current datetime, ISO format:
|
||||||
```
|
```
|
||||||
{{ utc_datetime }}
|
{{ local_datetime }}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from contextvars import ContextVar
|
from contextvars import ContextVar
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -10,6 +11,7 @@ class SkyvernContext:
|
|||||||
workflow_id: str | None = None
|
workflow_id: str | None = None
|
||||||
workflow_run_id: str | None = None
|
workflow_run_id: str | None = None
|
||||||
max_steps_override: int | None = None
|
max_steps_override: int | None = None
|
||||||
|
tz_info: ZoneInfo | None = None
|
||||||
totp_codes: dict[str, str | None] = field(default_factory=dict)
|
totp_codes: dict[str, str | None] = field(default_factory=dict)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from enum import StrEnum
|
from enum import StrEnum
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
from pydantic import BaseModel, Field, field_validator
|
from pydantic import BaseModel, Field, field_validator
|
||||||
|
|
||||||
@@ -26,6 +27,46 @@ class ProxyLocation(StrEnum):
|
|||||||
NONE = "NONE"
|
NONE = "NONE"
|
||||||
|
|
||||||
|
|
||||||
|
def get_tzinfo_from_proxy(proxy_location: ProxyLocation) -> ZoneInfo | None:
|
||||||
|
if proxy_location == ProxyLocation.NONE:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if proxy_location == ProxyLocation.US_CA:
|
||||||
|
return ZoneInfo("America/Los_Angeles")
|
||||||
|
|
||||||
|
if proxy_location == ProxyLocation.US_NY:
|
||||||
|
return ZoneInfo("America/New_York")
|
||||||
|
|
||||||
|
if proxy_location == ProxyLocation.US_TX:
|
||||||
|
return ZoneInfo("America/Chicago")
|
||||||
|
|
||||||
|
if proxy_location == ProxyLocation.US_FL:
|
||||||
|
return ZoneInfo("America/New_York")
|
||||||
|
|
||||||
|
if proxy_location == ProxyLocation.US_WA:
|
||||||
|
return ZoneInfo("America/New_York")
|
||||||
|
|
||||||
|
if proxy_location == ProxyLocation.RESIDENTIAL:
|
||||||
|
return ZoneInfo("America/New_York")
|
||||||
|
|
||||||
|
if proxy_location == ProxyLocation.RESIDENTIAL_ES:
|
||||||
|
return ZoneInfo("Europe/Madrid")
|
||||||
|
|
||||||
|
if proxy_location == ProxyLocation.RESIDENTIAL_IE:
|
||||||
|
return ZoneInfo("Europe/Dublin")
|
||||||
|
|
||||||
|
if proxy_location == ProxyLocation.RESIDENTIAL_GB:
|
||||||
|
return ZoneInfo("Europe/London")
|
||||||
|
|
||||||
|
if proxy_location == ProxyLocation.RESIDENTIAL_IN:
|
||||||
|
return ZoneInfo("Asia/Kolkata")
|
||||||
|
|
||||||
|
if proxy_location == ProxyLocation.RESIDENTIAL_JP:
|
||||||
|
return ZoneInfo("Asia/Kolkata")
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class TaskBase(BaseModel):
|
class TaskBase(BaseModel):
|
||||||
title: str | None = Field(
|
title: str | None = Field(
|
||||||
default=None,
|
default=None,
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ from skyvern.forge.prompts import prompt_engine
|
|||||||
from skyvern.forge.sdk.api.files import download_file, get_download_dir, list_files_in_directory
|
from skyvern.forge.sdk.api.files import download_file, get_download_dir, list_files_in_directory
|
||||||
from skyvern.forge.sdk.core.aiohttp_helper import aiohttp_post
|
from skyvern.forge.sdk.core.aiohttp_helper import aiohttp_post
|
||||||
from skyvern.forge.sdk.core.security import generate_skyvern_signature
|
from skyvern.forge.sdk.core.security import generate_skyvern_signature
|
||||||
|
from skyvern.forge.sdk.core.skyvern_context import ensure_context
|
||||||
from skyvern.forge.sdk.db.enums import OrganizationAuthTokenType
|
from skyvern.forge.sdk.db.enums import OrganizationAuthTokenType
|
||||||
from skyvern.forge.sdk.models import Step
|
from skyvern.forge.sdk.models import Step
|
||||||
from skyvern.forge.sdk.schemas.tasks import Task
|
from skyvern.forge.sdk.schemas.tasks import Task
|
||||||
@@ -1877,6 +1878,7 @@ async def select_from_dropdown(
|
|||||||
|
|
||||||
html = incremental_scraped.build_html_tree(element_tree=trimmed_element_tree)
|
html = incremental_scraped.build_html_tree(element_tree=trimmed_element_tree)
|
||||||
|
|
||||||
|
skyvern_context = ensure_context()
|
||||||
prompt = prompt_engine.load_prompt(
|
prompt = prompt_engine.load_prompt(
|
||||||
"custom-select",
|
"custom-select",
|
||||||
field_information=context.field,
|
field_information=context.field,
|
||||||
@@ -1886,7 +1888,7 @@ async def select_from_dropdown(
|
|||||||
navigation_payload_str=json.dumps(task.navigation_payload),
|
navigation_payload_str=json.dumps(task.navigation_payload),
|
||||||
elements=html,
|
elements=html,
|
||||||
select_history=json.dumps(build_sequential_select_history(select_history)) if select_history else "",
|
select_history=json.dumps(build_sequential_select_history(select_history)) if select_history else "",
|
||||||
utc_datetime=datetime.utcnow().strftime("%Y-%m-%d %H:%M"),
|
local_datetime=datetime.now(skyvern_context.tz_info).isoformat(),
|
||||||
)
|
)
|
||||||
|
|
||||||
LOG.info(
|
LOG.info(
|
||||||
@@ -2449,6 +2451,8 @@ async def extract_information_for_navigation_goal(
|
|||||||
element_tree_in_prompt: str = scraped_page.build_element_tree(element_tree_format)
|
element_tree_in_prompt: str = scraped_page.build_element_tree(element_tree_format)
|
||||||
|
|
||||||
scraped_page_refreshed = await scraped_page.refresh()
|
scraped_page_refreshed = await scraped_page.refresh()
|
||||||
|
|
||||||
|
context = ensure_context()
|
||||||
extract_information_prompt = prompt_engine.load_prompt(
|
extract_information_prompt = prompt_engine.load_prompt(
|
||||||
prompt_template,
|
prompt_template,
|
||||||
navigation_goal=task.navigation_goal,
|
navigation_goal=task.navigation_goal,
|
||||||
@@ -2459,7 +2463,7 @@ async def extract_information_for_navigation_goal(
|
|||||||
current_url=scraped_page_refreshed.url,
|
current_url=scraped_page_refreshed.url,
|
||||||
extracted_text=scraped_page_refreshed.extracted_text,
|
extracted_text=scraped_page_refreshed.extracted_text,
|
||||||
error_code_mapping_str=(json.dumps(task.error_code_mapping) if task.error_code_mapping else None),
|
error_code_mapping_str=(json.dumps(task.error_code_mapping) if task.error_code_mapping else None),
|
||||||
utc_datetime=datetime.utcnow().strftime("%Y-%m-%d %H:%M"),
|
local_datetime=datetime.now(context.tz_info).isoformat(),
|
||||||
)
|
)
|
||||||
|
|
||||||
json_response = await app.LLM_API_HANDLER(
|
json_response = await app.LLM_API_HANDLER(
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ from skyvern.exceptions import (
|
|||||||
)
|
)
|
||||||
from skyvern.forge.sdk.api.files import get_download_dir, make_temp_directory
|
from skyvern.forge.sdk.api.files import get_download_dir, make_temp_directory
|
||||||
from skyvern.forge.sdk.core.skyvern_context import current, ensure_context
|
from skyvern.forge.sdk.core.skyvern_context import current, ensure_context
|
||||||
from skyvern.forge.sdk.schemas.tasks import ProxyLocation
|
from skyvern.forge.sdk.schemas.tasks import ProxyLocation, get_tzinfo_from_proxy
|
||||||
from skyvern.webeye.utils.page import SkyvernFrame
|
from skyvern.webeye.utils.page import SkyvernFrame
|
||||||
|
|
||||||
LOG = structlog.get_logger()
|
LOG = structlog.get_logger()
|
||||||
@@ -128,7 +128,7 @@ def initialize_download_dir() -> str:
|
|||||||
|
|
||||||
class BrowserContextCreator(Protocol):
|
class BrowserContextCreator(Protocol):
|
||||||
def __call__(
|
def __call__(
|
||||||
self, playwright: Playwright, **kwargs: dict[str, Any]
|
self, playwright: Playwright, proxy_location: ProxyLocation | None = None, **kwargs: dict[str, Any]
|
||||||
) -> Awaitable[tuple[BrowserContext, BrowserArtifacts, BrowserCleanupFunc]]: ...
|
) -> Awaitable[tuple[BrowserContext, BrowserArtifacts, BrowserCleanupFunc]]: ...
|
||||||
|
|
||||||
|
|
||||||
@@ -162,14 +162,13 @@ class BrowserContextFactory:
|
|||||||
f.write(preference_file_content)
|
f.write(preference_file_content)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def build_browser_args() -> dict[str, Any]:
|
def build_browser_args(proxy_location: ProxyLocation | None = None) -> dict[str, Any]:
|
||||||
video_dir = f"{settings.VIDEO_PATH}/{datetime.utcnow().strftime('%Y-%m-%d')}"
|
video_dir = f"{settings.VIDEO_PATH}/{datetime.utcnow().strftime('%Y-%m-%d')}"
|
||||||
har_dir = (
|
har_dir = (
|
||||||
f"{settings.HAR_PATH}/{datetime.utcnow().strftime('%Y-%m-%d')}/{BrowserContextFactory.get_subdir()}.har"
|
f"{settings.HAR_PATH}/{datetime.utcnow().strftime('%Y-%m-%d')}/{BrowserContextFactory.get_subdir()}.har"
|
||||||
)
|
)
|
||||||
return {
|
args = {
|
||||||
"locale": settings.BROWSER_LOCALE,
|
"locale": settings.BROWSER_LOCALE,
|
||||||
"timezone_id": settings.BROWSER_TIMEZONE,
|
|
||||||
"color_scheme": "no-preference",
|
"color_scheme": "no-preference",
|
||||||
"args": [
|
"args": [
|
||||||
"--disable-blink-features=AutomationControlled",
|
"--disable-blink-features=AutomationControlled",
|
||||||
@@ -188,6 +187,11 @@ class BrowserContextFactory:
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if proxy_location:
|
||||||
|
if tz_info := get_tzinfo_from_proxy(proxy_location=proxy_location):
|
||||||
|
args["timezone_id"] = tz_info.key
|
||||||
|
return args
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def build_browser_artifacts(
|
def build_browser_artifacts(
|
||||||
video_artifacts: list[VideoArtifact] | None = None,
|
video_artifacts: list[VideoArtifact] | None = None,
|
||||||
@@ -221,6 +225,12 @@ class BrowserContextFactory:
|
|||||||
browser_context, browser_artifacts, cleanup_func = await creator(playwright, **kwargs)
|
browser_context, browser_artifacts, cleanup_func = await creator(playwright, **kwargs)
|
||||||
set_browser_console_log(browser_context=browser_context, browser_artifacts=browser_artifacts)
|
set_browser_console_log(browser_context=browser_context, browser_artifacts=browser_artifacts)
|
||||||
set_download_file_listener(browser_context=browser_context, **kwargs)
|
set_download_file_listener(browser_context=browser_context, **kwargs)
|
||||||
|
|
||||||
|
proxy_location: ProxyLocation | None = kwargs.get("proxy_location")
|
||||||
|
if proxy_location is not None:
|
||||||
|
context = ensure_context()
|
||||||
|
context.tz_info = get_tzinfo_from_proxy(proxy_location)
|
||||||
|
|
||||||
return browser_context, browser_artifacts, cleanup_func
|
return browser_context, browser_artifacts, cleanup_func
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if browser_context is not None:
|
if browser_context is not None:
|
||||||
@@ -279,7 +289,7 @@ class BrowserArtifacts(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
async def _create_headless_chromium(
|
async def _create_headless_chromium(
|
||||||
playwright: Playwright, **kwargs: dict
|
playwright: Playwright, proxy_location: ProxyLocation | None = None, **kwargs: dict
|
||||||
) -> tuple[BrowserContext, BrowserArtifacts, BrowserCleanupFunc]:
|
) -> tuple[BrowserContext, BrowserArtifacts, BrowserCleanupFunc]:
|
||||||
user_data_dir = make_temp_directory(prefix="skyvern_browser_")
|
user_data_dir = make_temp_directory(prefix="skyvern_browser_")
|
||||||
download_dir = initialize_download_dir()
|
download_dir = initialize_download_dir()
|
||||||
@@ -287,7 +297,7 @@ async def _create_headless_chromium(
|
|||||||
user_data_dir=user_data_dir,
|
user_data_dir=user_data_dir,
|
||||||
download_dir=download_dir,
|
download_dir=download_dir,
|
||||||
)
|
)
|
||||||
browser_args = BrowserContextFactory.build_browser_args()
|
browser_args = BrowserContextFactory.build_browser_args(proxy_location=proxy_location)
|
||||||
browser_args.update(
|
browser_args.update(
|
||||||
{
|
{
|
||||||
"user_data_dir": user_data_dir,
|
"user_data_dir": user_data_dir,
|
||||||
@@ -301,7 +311,7 @@ async def _create_headless_chromium(
|
|||||||
|
|
||||||
|
|
||||||
async def _create_headful_chromium(
|
async def _create_headful_chromium(
|
||||||
playwright: Playwright, **kwargs: dict
|
playwright: Playwright, proxy_location: ProxyLocation | None = None, **kwargs: dict
|
||||||
) -> tuple[BrowserContext, BrowserArtifacts, BrowserCleanupFunc]:
|
) -> tuple[BrowserContext, BrowserArtifacts, BrowserCleanupFunc]:
|
||||||
user_data_dir = make_temp_directory(prefix="skyvern_browser_")
|
user_data_dir = make_temp_directory(prefix="skyvern_browser_")
|
||||||
download_dir = initialize_download_dir()
|
download_dir = initialize_download_dir()
|
||||||
@@ -309,7 +319,7 @@ async def _create_headful_chromium(
|
|||||||
user_data_dir=user_data_dir,
|
user_data_dir=user_data_dir,
|
||||||
download_dir=download_dir,
|
download_dir=download_dir,
|
||||||
)
|
)
|
||||||
browser_args = BrowserContextFactory.build_browser_args()
|
browser_args = BrowserContextFactory.build_browser_args(proxy_location=proxy_location)
|
||||||
browser_args.update(
|
browser_args.update(
|
||||||
{
|
{
|
||||||
"user_data_dir": user_data_dir,
|
"user_data_dir": user_data_dir,
|
||||||
|
|||||||
Reference in New Issue
Block a user