add timezone support (#1389)
This commit is contained in:
@@ -1310,6 +1310,7 @@ class ForgeAgent:
|
||||
if not template:
|
||||
raise UnsupportedTaskType(task_type=task_type)
|
||||
|
||||
context = skyvern_context.ensure_context()
|
||||
return prompt_engine.load_prompt(
|
||||
template=template,
|
||||
navigation_goal=navigation_goal,
|
||||
@@ -1320,7 +1321,7 @@ class ForgeAgent:
|
||||
data_extraction_goal=task.data_extraction_goal,
|
||||
action_history=actions_and_results_str,
|
||||
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,
|
||||
complete_criterion=task.complete_criterion,
|
||||
terminate_criterion=task.terminate_criterion,
|
||||
|
||||
@@ -51,7 +51,7 @@ Select History:
|
||||
{{ select_history }}
|
||||
```
|
||||
{% 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 }}
|
||||
```
|
||||
|
||||
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_history }}
|
||||
{% 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 }}
|
||||
|
||||
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 }}
|
||||
```
|
||||
|
||||
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 }}
|
||||
```
|
||||
|
||||
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 }}
|
||||
```
|
||||
|
||||
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 }}
|
||||
```
|
||||
|
||||
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 dataclasses import dataclass, field
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -10,6 +11,7 @@ class SkyvernContext:
|
||||
workflow_id: str | None = None
|
||||
workflow_run_id: str | None = None
|
||||
max_steps_override: int | None = None
|
||||
tz_info: ZoneInfo | None = None
|
||||
totp_codes: dict[str, str | None] = field(default_factory=dict)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
|
||||
@@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
from datetime import datetime
|
||||
from enum import StrEnum
|
||||
from typing import Any
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
|
||||
@@ -26,6 +27,46 @@ class ProxyLocation(StrEnum):
|
||||
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):
|
||||
title: str | None = Field(
|
||||
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.core.aiohttp_helper import aiohttp_post
|
||||
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.models import Step
|
||||
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)
|
||||
|
||||
skyvern_context = ensure_context()
|
||||
prompt = prompt_engine.load_prompt(
|
||||
"custom-select",
|
||||
field_information=context.field,
|
||||
@@ -1886,7 +1888,7 @@ async def select_from_dropdown(
|
||||
navigation_payload_str=json.dumps(task.navigation_payload),
|
||||
elements=html,
|
||||
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(
|
||||
@@ -2449,6 +2451,8 @@ async def extract_information_for_navigation_goal(
|
||||
element_tree_in_prompt: str = scraped_page.build_element_tree(element_tree_format)
|
||||
|
||||
scraped_page_refreshed = await scraped_page.refresh()
|
||||
|
||||
context = ensure_context()
|
||||
extract_information_prompt = prompt_engine.load_prompt(
|
||||
prompt_template,
|
||||
navigation_goal=task.navigation_goal,
|
||||
@@ -2459,7 +2463,7 @@ async def extract_information_for_navigation_goal(
|
||||
current_url=scraped_page_refreshed.url,
|
||||
extracted_text=scraped_page_refreshed.extracted_text,
|
||||
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(
|
||||
|
||||
@@ -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.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
|
||||
|
||||
LOG = structlog.get_logger()
|
||||
@@ -128,7 +128,7 @@ def initialize_download_dir() -> str:
|
||||
|
||||
class BrowserContextCreator(Protocol):
|
||||
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]]: ...
|
||||
|
||||
|
||||
@@ -162,14 +162,13 @@ class BrowserContextFactory:
|
||||
f.write(preference_file_content)
|
||||
|
||||
@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')}"
|
||||
har_dir = (
|
||||
f"{settings.HAR_PATH}/{datetime.utcnow().strftime('%Y-%m-%d')}/{BrowserContextFactory.get_subdir()}.har"
|
||||
)
|
||||
return {
|
||||
args = {
|
||||
"locale": settings.BROWSER_LOCALE,
|
||||
"timezone_id": settings.BROWSER_TIMEZONE,
|
||||
"color_scheme": "no-preference",
|
||||
"args": [
|
||||
"--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
|
||||
def build_browser_artifacts(
|
||||
video_artifacts: list[VideoArtifact] | None = None,
|
||||
@@ -221,6 +225,12 @@ class BrowserContextFactory:
|
||||
browser_context, browser_artifacts, cleanup_func = await creator(playwright, **kwargs)
|
||||
set_browser_console_log(browser_context=browser_context, browser_artifacts=browser_artifacts)
|
||||
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
|
||||
except Exception as e:
|
||||
if browser_context is not None:
|
||||
@@ -279,7 +289,7 @@ class BrowserArtifacts(BaseModel):
|
||||
|
||||
|
||||
async def _create_headless_chromium(
|
||||
playwright: Playwright, **kwargs: dict
|
||||
playwright: Playwright, proxy_location: ProxyLocation | None = None, **kwargs: dict
|
||||
) -> tuple[BrowserContext, BrowserArtifacts, BrowserCleanupFunc]:
|
||||
user_data_dir = make_temp_directory(prefix="skyvern_browser_")
|
||||
download_dir = initialize_download_dir()
|
||||
@@ -287,7 +297,7 @@ async def _create_headless_chromium(
|
||||
user_data_dir=user_data_dir,
|
||||
download_dir=download_dir,
|
||||
)
|
||||
browser_args = BrowserContextFactory.build_browser_args()
|
||||
browser_args = BrowserContextFactory.build_browser_args(proxy_location=proxy_location)
|
||||
browser_args.update(
|
||||
{
|
||||
"user_data_dir": user_data_dir,
|
||||
@@ -301,7 +311,7 @@ async def _create_headless_chromium(
|
||||
|
||||
|
||||
async def _create_headful_chromium(
|
||||
playwright: Playwright, **kwargs: dict
|
||||
playwright: Playwright, proxy_location: ProxyLocation | None = None, **kwargs: dict
|
||||
) -> tuple[BrowserContext, BrowserArtifacts, BrowserCleanupFunc]:
|
||||
user_data_dir = make_temp_directory(prefix="skyvern_browser_")
|
||||
download_dir = initialize_download_dir()
|
||||
@@ -309,7 +319,7 @@ async def _create_headful_chromium(
|
||||
user_data_dir=user_data_dir,
|
||||
download_dir=download_dir,
|
||||
)
|
||||
browser_args = BrowserContextFactory.build_browser_args()
|
||||
browser_args = BrowserContextFactory.build_browser_args(proxy_location=proxy_location)
|
||||
browser_args.update(
|
||||
{
|
||||
"user_data_dir": user_data_dir,
|
||||
|
||||
Reference in New Issue
Block a user