SDK: download files (#4196)

This commit is contained in:
Stanislav Novosad
2025-12-04 10:50:29 -07:00
committed by GitHub
parent b30f3b09c8
commit 4665f8907d
16 changed files with 983 additions and 105 deletions

View File

@@ -160,6 +160,24 @@ await skyvern.login({
onepassword_item_id: "1PASSWORD ITEM ID"
});
"""
FILE_DOWNLOAD_CODE_SAMPLE_PYTHON = """from skyvern import Skyvern
skyvern = Skyvern(api_key="YOUR_API_KEY")
await skyvern.file_download(
url="https://example.com/downloads",
navigation_goal="Navigate to the downloads page and click the 'Download PDF' button",
download_suffix="report.pdf"
)
"""
FILE_DOWNLOAD_CODE_SAMPLE_TS = """import { SkyvernClient } from "@skyvern/client";
const skyvern = new SkyvernClient({ apiKey: "YOUR_API_KEY" });
await skyvern.fileDownload({
url: "https://example.com/downloads",
navigation_goal: "Navigate to the downloads page and click the 'Download PDF' button",
download_suffix: "report.pdf"
});
"""
# Workflows
CREATE_WORKFLOW_CODE_SAMPLE_CURL = """curl -X POST https://api.skyvern.com/v1/workflows \

View File

@@ -8,6 +8,8 @@ from skyvern.exceptions import MissingBrowserAddressError
from skyvern.forge import app
from skyvern.forge.sdk.core import skyvern_context
from skyvern.forge.sdk.routes.code_samples import (
FILE_DOWNLOAD_CODE_SAMPLE_PYTHON,
FILE_DOWNLOAD_CODE_SAMPLE_TS,
LOGIN_CODE_SAMPLE_BITWARDEN_PYTHON,
LOGIN_CODE_SAMPLE_BITWARDEN_TS,
LOGIN_CODE_SAMPLE_ONEPASSWORD_PYTHON,
@@ -19,12 +21,13 @@ from skyvern.forge.sdk.routes.routers import base_router
from skyvern.forge.sdk.schemas.organizations import Organization
from skyvern.forge.sdk.services import org_auth_service
from skyvern.forge.sdk.workflow.models.parameter import WorkflowParameterType
from skyvern.forge.sdk.workflow.models.workflow import WorkflowRequestBody
from skyvern.schemas.run_blocks import CredentialType, LoginRequest
from skyvern.forge.sdk.workflow.models.workflow import Workflow, WorkflowRequestBody
from skyvern.schemas.run_blocks import BaseRunBlockRequest, CredentialType, FileDownloadRequest, LoginRequest
from skyvern.schemas.runs import ProxyLocation, RunType, WorkflowRunRequest, WorkflowRunResponse
from skyvern.schemas.workflows import (
AzureVaultCredentialParameterYAML,
BitwardenLoginCredentialParameterYAML,
FileDownloadBlockYAML,
LoginBlockYAML,
OnePasswordCredentialParameterYAML,
WorkflowCreateYAMLRequest,
@@ -43,6 +46,80 @@ If you fail to login to find the login page or can't login after several trials,
If login is completed, you're successful."""
def _validate_url(url: str | None) -> str | None:
if not url:
return None
try:
return prepend_scheme_and_validate_url(url)
except Exception as e:
raise HTTPException(status_code=400, detail=str(e)) from e
async def _run_workflow_and_build_response(
request: Request,
background_tasks: BackgroundTasks,
new_workflow: Workflow,
workflow_id: str,
organization: Organization,
run_block_request: BaseRunBlockRequest,
webhook_url: str | None,
totp_verification_url: str | None,
totp_identifier: str | None,
x_api_key: str | None,
) -> WorkflowRunResponse:
context = skyvern_context.ensure_context()
request_id = context.request_id
legacy_workflow_request = WorkflowRequestBody(
proxy_location=run_block_request.proxy_location,
webhook_callback_url=webhook_url,
totp_identifier=totp_identifier,
totp_verification_url=totp_verification_url,
browser_session_id=run_block_request.browser_session_id,
browser_profile_id=run_block_request.browser_profile_id,
browser_address=run_block_request.browser_address,
max_screenshot_scrolls=run_block_request.max_screenshot_scrolling_times,
extra_http_headers=run_block_request.extra_http_headers,
)
try:
workflow_run = await workflow_service.run_workflow(
workflow_id=workflow_id,
organization=organization,
workflow_request=legacy_workflow_request,
template=False,
version=None,
api_key=x_api_key or None,
request_id=request_id,
request=request,
background_tasks=background_tasks,
)
except MissingBrowserAddressError as e:
raise HTTPException(status_code=400, detail=str(e)) from e
return WorkflowRunResponse(
run_id=workflow_run.workflow_run_id,
run_type=RunType.workflow_run,
status=str(workflow_run.status),
failure_reason=workflow_run.failure_reason,
created_at=workflow_run.created_at,
modified_at=workflow_run.modified_at,
run_request=WorkflowRunRequest(
workflow_id=new_workflow.workflow_id,
title=new_workflow.title,
proxy_location=run_block_request.proxy_location,
webhook_url=webhook_url,
totp_url=totp_verification_url,
totp_identifier=totp_identifier,
browser_session_id=run_block_request.browser_session_id,
browser_profile_id=run_block_request.browser_profile_id,
max_screenshot_scrolls=run_block_request.max_screenshot_scrolling_times,
),
app_url=f"{settings.SKYVERN_APP_URL.rstrip('/')}/runs/{workflow_run.workflow_run_id}",
browser_session_id=run_block_request.browser_session_id,
browser_profile_id=run_block_request.browser_profile_id,
)
@base_router.post(
"/run/tasks/login",
tags=["Agent"],
@@ -72,14 +149,9 @@ async def login(
organization: Organization = Depends(org_auth_service.get_current_org),
x_api_key: Annotated[str | None, Header()] = None,
) -> WorkflowRunResponse:
try:
url = prepend_scheme_and_validate_url(login_request.url) if login_request.url else None
totp_verification_url = (
prepend_scheme_and_validate_url(login_request.totp_url) if login_request.totp_url else None
)
webhook_url = prepend_scheme_and_validate_url(login_request.webhook_url) if login_request.webhook_url else None
except Exception as e:
raise HTTPException(status_code=400, detail=str(e)) from e
url = _validate_url(login_request.url)
totp_verification_url = _validate_url(login_request.totp_url)
webhook_url = _validate_url(login_request.webhook_url)
# 1. create empty workflow with a credential parameter
new_workflow = await app.WORKFLOW_SERVICE.create_empty_workflow(
@@ -196,54 +268,104 @@ async def login(
# 3. create and run workflow with the credential
workflow_id = new_workflow.workflow_permanent_id
context = skyvern_context.ensure_context()
request_id = context.request_id
legacy_workflow_request = WorkflowRequestBody(
proxy_location=login_request.proxy_location,
webhook_callback_url=webhook_url,
totp_identifier=resolved_totp_identifier,
return await _run_workflow_and_build_response(
request=request,
background_tasks=background_tasks,
new_workflow=new_workflow,
workflow_id=workflow_id,
organization=organization,
run_block_request=login_request,
webhook_url=webhook_url,
totp_verification_url=totp_verification_url,
browser_session_id=login_request.browser_session_id,
browser_profile_id=login_request.browser_profile_id,
browser_address=login_request.browser_address,
max_screenshot_scrolls=login_request.max_screenshot_scrolling_times,
extra_http_headers=login_request.extra_http_headers,
totp_identifier=resolved_totp_identifier,
x_api_key=x_api_key,
)
try:
workflow_run = await workflow_service.run_workflow(
workflow_id=workflow_id,
organization=organization,
workflow_request=legacy_workflow_request,
template=False,
version=None,
api_key=x_api_key or None,
request_id=request_id,
request=request,
background_tasks=background_tasks,
)
except MissingBrowserAddressError as e:
raise HTTPException(status_code=400, detail=str(e)) from e
return WorkflowRunResponse(
run_id=workflow_run.workflow_run_id,
run_type=RunType.workflow_run,
status=str(workflow_run.status),
failure_reason=workflow_run.failure_reason,
created_at=workflow_run.created_at,
modified_at=workflow_run.modified_at,
run_request=WorkflowRunRequest(
workflow_id=new_workflow.workflow_id,
title=new_workflow.title,
proxy_location=login_request.proxy_location,
webhook_url=webhook_url,
totp_url=totp_verification_url,
totp_identifier=resolved_totp_identifier,
browser_session_id=login_request.browser_session_id,
browser_profile_id=login_request.browser_profile_id,
max_screenshot_scrolls=login_request.max_screenshot_scrolling_times,
),
app_url=f"{settings.SKYVERN_APP_URL.rstrip('/')}/runs/{workflow_run.workflow_run_id}",
browser_session_id=login_request.browser_session_id,
browser_profile_id=login_request.browser_profile_id,
@base_router.post(
"/run/tasks/file_download",
tags=["Agent"],
response_model=WorkflowRunResponse,
openapi_extra={
"x-fern-sdk-method-name": "file_download",
"x-fern-examples": [
{
"code-samples": [
{"sdk": "python", "code": FILE_DOWNLOAD_CODE_SAMPLE_PYTHON},
{"sdk": "typescript", "code": FILE_DOWNLOAD_CODE_SAMPLE_TS},
]
}
],
},
description="Download a file from a website by navigating and clicking download buttons",
summary="File Download Task",
)
async def file_download(
request: Request,
background_tasks: BackgroundTasks,
file_download_request: FileDownloadRequest,
organization: Organization = Depends(org_auth_service.get_current_org),
x_api_key: Annotated[str | None, Header()] = None,
) -> WorkflowRunResponse:
url = _validate_url(file_download_request.url)
totp_verification_url = _validate_url(file_download_request.totp_url)
webhook_url = _validate_url(file_download_request.webhook_url)
# 1. create empty workflow
new_workflow = await app.WORKFLOW_SERVICE.create_empty_workflow(
organization,
"File Download",
proxy_location=file_download_request.proxy_location,
max_screenshot_scrolling_times=file_download_request.max_screenshot_scrolling_times,
extra_http_headers=file_download_request.extra_http_headers,
status=WorkflowStatus.auto_generated,
)
# 2. add a file download block to the workflow
label = "file_download"
file_download_block_yaml = FileDownloadBlockYAML(
label=label,
title=label,
url=url,
navigation_goal=file_download_request.navigation_goal,
max_steps_per_run=file_download_request.max_steps_per_run or 10,
parameter_keys=file_download_request.parameter_keys or [],
totp_verification_url=totp_verification_url,
totp_identifier=file_download_request.totp_identifier,
download_suffix=file_download_request.download_suffix,
download_timeout=file_download_request.download_timeout,
)
yaml_blocks = [file_download_block_yaml]
workflow_definition_yaml = WorkflowDefinitionYAML(
parameters=[],
blocks=yaml_blocks,
)
workflow_create_request = WorkflowCreateYAMLRequest(
title=new_workflow.title,
description=new_workflow.description,
proxy_location=file_download_request.proxy_location or ProxyLocation.RESIDENTIAL,
workflow_definition=workflow_definition_yaml,
status=new_workflow.status,
max_screenshot_scrolls=file_download_request.max_screenshot_scrolling_times,
)
workflow = await app.WORKFLOW_SERVICE.create_workflow_from_request(
organization=organization,
request=workflow_create_request,
workflow_permanent_id=new_workflow.workflow_permanent_id,
)
LOG.info("Workflow created", workflow_id=workflow.workflow_id)
# 3. create and run workflow
workflow_id = new_workflow.workflow_permanent_id
return await _run_workflow_and_build_response(
request=request,
background_tasks=background_tasks,
new_workflow=new_workflow,
workflow_id=workflow_id,
organization=organization,
run_block_request=file_download_request,
webhook_url=webhook_url,
totp_verification_url=totp_verification_url,
totp_identifier=file_download_request.totp_identifier,
x_api_key=x_api_key,
)