From 7afb5190a6cd837a6a63ae24ae3f43be508ba1e4 Mon Sep 17 00:00:00 2001 From: Shuchang Zheng Date: Mon, 29 Sep 2025 18:37:42 -0700 Subject: [PATCH] Script gen support - send email with file attachments (#3551) --- .../script_generations/generate_script.py | 17 +++++----- .../core/script_generations/skyvern_page.py | 31 +++++++++++++++++++ .../script_generations/workflow_wrappers.py | 6 +++- skyvern/forge/sdk/workflow/context_manager.py | 4 ++- skyvern/services/script_service.py | 5 +-- 5 files changed, 50 insertions(+), 13 deletions(-) diff --git a/skyvern/core/script_generations/generate_script.py b/skyvern/core/script_generations/generate_script.py index 092d4b3f..c501e24b 100644 --- a/skyvern/core/script_generations/generate_script.py +++ b/skyvern/core/script_generations/generate_script.py @@ -775,15 +775,14 @@ def _build_send_email_statement(block: dict[str, Any]) -> cst.SimpleStatementLin last_line=cst.SimpleWhitespace(INDENT), ), ), - # TODO: support file attachments? - # cst.Arg( - # keyword=cst.Name("file_attachments"), - # value=_value(block.get("file_attachments", [])), - # whitespace_after_arg=cst.ParenthesizedWhitespace( - # indent=True, - # last_line=cst.SimpleWhitespace(INDENT), - # ), - # ), + cst.Arg( + keyword=cst.Name("file_attachments"), + value=_value(block.get("file_attachments", [])), + whitespace_after_arg=cst.ParenthesizedWhitespace( + indent=True, + last_line=cst.SimpleWhitespace(INDENT), + ), + ), cst.Arg( keyword=cst.Name("label"), value=_value(block.get("label", "")), diff --git a/skyvern/core/script_generations/skyvern_page.py b/skyvern/core/script_generations/skyvern_page.py index 7c2f2525..a796698b 100644 --- a/skyvern/core/script_generations/skyvern_page.py +++ b/skyvern/core/script_generations/skyvern_page.py @@ -80,6 +80,35 @@ def _get_context_data(data: str | dict[str, Any] | None = None) -> dict[str, Any return result +def _render_template_with_label(template: str, label: str | None = None) -> str: + template_data = {} + context = skyvern_context.current() + if context and context.workflow_run_id: + workflow_run_context = app.WORKFLOW_CONTEXT_MANAGER.get_workflow_run_context(context.workflow_run_id) + block_reference_data: dict[str, Any] = workflow_run_context.get_block_metadata(label) + template_data = workflow_run_context.values.copy() + if label in template_data: + current_value = template_data[label] + if isinstance(current_value, dict): + block_reference_data.update(current_value) + else: + LOG.warning( + f"Script service: Parameter {label} has a registered reference value, going to overwrite it by block metadata" + ) + + if label: + template_data[label] = block_reference_data + + # inject the forloop metadata as global variables + if "current_index" in block_reference_data: + template_data["current_index"] = block_reference_data["current_index"] + if "current_item" in block_reference_data: + template_data["current_item"] = block_reference_data["current_item"] + if "current_value" in block_reference_data: + template_data["current_value"] = block_reference_data["current_value"] + return render_template(template, data=template_data) + + def render_template(template: str, data: dict[str, Any] | None = None) -> str: """ Refer to Block.format_block_parameter_template_from_workflow_run_context @@ -118,6 +147,7 @@ class SkyvernPage: self.scraped_page = scraped_page self.page = page self._record = recorder or (lambda ac: None) + self.current_label: str | None = None @classmethod async def _get_or_create_browser_state(cls, browser_session_id: str | None = None) -> BrowserState: @@ -681,6 +711,7 @@ class SkyvernPage: tz_info = datetime.now(tz=timezone.utc).tzinfo if context and context.tz_info: tz_info = context.tz_info + prompt = _render_template_with_label(prompt, label=self.current_label) extract_information_prompt = load_prompt_with_elements( element_tree_builder=scraped_page_refreshed, prompt_engine=prompt_engine, diff --git a/skyvern/core/script_generations/workflow_wrappers.py b/skyvern/core/script_generations/workflow_wrappers.py index e7bb0c6e..a42dbc0a 100644 --- a/skyvern/core/script_generations/workflow_wrappers.py +++ b/skyvern/core/script_generations/workflow_wrappers.py @@ -24,7 +24,11 @@ def cached(cache_key: str) -> Callable: async def wrapper(page: SkyvernPage, context: RunContext, *args: Any, **kwargs: Any) -> Any: # Store the function in context.cached_fns - return await func(page, context, *args, **kwargs) + page.current_label = cache_key + try: + return await func(page, context, *args, **kwargs) + finally: + page.current_label = None return wrapper diff --git a/skyvern/forge/sdk/workflow/context_manager.py b/skyvern/forge/sdk/workflow/context_manager.py index c1cd77fc..4c2309cd 100644 --- a/skyvern/forge/sdk/workflow/context_manager.py +++ b/skyvern/forge/sdk/workflow/context_manager.py @@ -166,7 +166,9 @@ class WorkflowRunContext: return self.blocks_metadata[label] = metadata - def get_block_metadata(self, label: str) -> BlockMetadata: + def get_block_metadata(self, label: str | None) -> BlockMetadata: + if label is None: + label = "" return self.blocks_metadata.get(label, BlockMetadata()) def get_original_secret_value_or_none(self, secret_id_or_value: Any) -> Any: diff --git a/skyvern/services/script_service.py b/skyvern/services/script_service.py index 55b12894..1e0ee7af 100644 --- a/skyvern/services/script_service.py +++ b/skyvern/services/script_service.py @@ -1544,7 +1544,7 @@ async def run_script( def _render_template_with_label(template: str, label: str | None = None) -> str: template_data = {} context = skyvern_context.current() - if context and context.workflow_run_id and label: + if context and context.workflow_run_id: workflow_run_context = app.WORKFLOW_CONTEXT_MANAGER.get_workflow_run_context(context.workflow_run_id) block_reference_data: dict[str, Any] = workflow_run_context.get_block_metadata(label) template_data = workflow_run_context.values.copy() @@ -1557,7 +1557,8 @@ def _render_template_with_label(template: str, label: str | None = None) -> str: f"Script service: Parameter {label} has a registered reference value, going to overwrite it by block metadata" ) - template_data[label] = block_reference_data + if label: + template_data[label] = block_reference_data # inject the forloop metadata as global variables if "current_index" in block_reference_data: