API to run login task (#2920)
This commit is contained in:
162
skyvern/forge/sdk/routes/run_blocks.py
Normal file
162
skyvern/forge/sdk/routes/run_blocks.py
Normal file
@@ -0,0 +1,162 @@
|
||||
from typing import Annotated
|
||||
|
||||
import structlog
|
||||
from fastapi import BackgroundTasks, Depends, Header, HTTPException, Request
|
||||
|
||||
from skyvern.config import settings
|
||||
from skyvern.exceptions import MissingBrowserAddressError
|
||||
from skyvern.forge import app
|
||||
from skyvern.forge.sdk.core import skyvern_context
|
||||
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.workflow import WorkflowRequestBody
|
||||
from skyvern.forge.sdk.workflow.models.yaml import (
|
||||
CredentialParameterYAML,
|
||||
LoginBlockYAML,
|
||||
WorkflowCreateYAMLRequest,
|
||||
WorkflowDefinitionYAML,
|
||||
)
|
||||
from skyvern.schemas.run_blocks import LoginRequest
|
||||
from skyvern.schemas.runs import ProxyLocation, RunType, WorkflowRunRequest, WorkflowRunResponse
|
||||
from skyvern.services import workflow_service
|
||||
|
||||
LOG = structlog.get_logger()
|
||||
DEFAULT_LOGIN_PROMPT = """If you're not on the login page, navigate to login page and login using the credentials given.
|
||||
First, take actions on promotional popups or cookie prompts that could prevent taking other action on the web page.
|
||||
If a 2-factor step appears, enter the authentication code.
|
||||
If you fail to login to find the login page or can't login after several trials, terminate.
|
||||
If login is completed, you're successful."""
|
||||
|
||||
|
||||
@base_router.post(
|
||||
"/run/tasks/login",
|
||||
response_model=WorkflowRunResponse,
|
||||
)
|
||||
async def login(
|
||||
request: Request,
|
||||
background_tasks: BackgroundTasks,
|
||||
login_request: LoginRequest,
|
||||
organization: Organization = Depends(org_auth_service.get_current_org),
|
||||
x_api_key: Annotated[str | None, Header()] = None,
|
||||
) -> WorkflowRunResponse:
|
||||
# 0. validate credential
|
||||
credential = await app.DATABASE.get_credential(login_request.credential_id, organization.organization_id)
|
||||
if not credential:
|
||||
raise HTTPException(status_code=404, detail=f"Credential {login_request.credential_id} not found")
|
||||
|
||||
# 1. create empty workflow with a credential parameter
|
||||
new_workflow = await app.WORKFLOW_SERVICE.create_empty_workflow(
|
||||
organization,
|
||||
"Login",
|
||||
proxy_location=login_request.proxy_location,
|
||||
max_screenshot_scrolling_times=login_request.max_screenshot_scrolling_times,
|
||||
extra_http_headers=login_request.extra_http_headers,
|
||||
)
|
||||
workflow_run = await app.WORKFLOW_SERVICE.setup_workflow_run(
|
||||
request_id=None,
|
||||
workflow_request=WorkflowRequestBody(
|
||||
max_screenshot_scrolls=login_request.max_screenshot_scrolling_times,
|
||||
browser_session_id=login_request.browser_session_id,
|
||||
extra_http_headers=login_request.extra_http_headers,
|
||||
),
|
||||
workflow_permanent_id=new_workflow.workflow_permanent_id,
|
||||
organization=organization,
|
||||
version=None,
|
||||
max_steps_override=10,
|
||||
parent_workflow_run_id=None,
|
||||
)
|
||||
# 2. add a login block to the workflow
|
||||
label = "login"
|
||||
login_block_yaml = LoginBlockYAML(
|
||||
label=label,
|
||||
title=label,
|
||||
url=login_request.url,
|
||||
navigation_goal=login_request.prompt or DEFAULT_LOGIN_PROMPT,
|
||||
max_steps_per_run=10,
|
||||
parameter_keys=[login_request.credential_id],
|
||||
totp_verification_url=login_request.totp_url,
|
||||
totp_identifier=login_request.totp_identifier,
|
||||
)
|
||||
yaml_parameters = [
|
||||
CredentialParameterYAML(
|
||||
name="credential_id",
|
||||
type="workflow",
|
||||
description="The ID of the credential to use for login",
|
||||
)
|
||||
]
|
||||
yaml_blocks = [login_block_yaml]
|
||||
workflow_definition_yaml = WorkflowDefinitionYAML(
|
||||
parameters=yaml_parameters,
|
||||
blocks=yaml_blocks,
|
||||
)
|
||||
workflow_create_request = WorkflowCreateYAMLRequest(
|
||||
title=new_workflow.title,
|
||||
description=new_workflow.description,
|
||||
proxy_location=login_request.proxy_location or ProxyLocation.RESIDENTIAL,
|
||||
workflow_definition=workflow_definition_yaml,
|
||||
status=new_workflow.status,
|
||||
max_screenshot_scrolls=login_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 with the credential_id
|
||||
workflow_id = new_workflow.workflow_id
|
||||
context = skyvern_context.ensure_context()
|
||||
request_id = context.request_id
|
||||
legacy_workflow_request = WorkflowRequestBody(
|
||||
data={
|
||||
"credential_id": login_request.credential_id,
|
||||
},
|
||||
proxy_location=login_request.proxy_location,
|
||||
webhook_callback_url=login_request.webhook_url,
|
||||
totp_identifier=login_request.totp_identifier,
|
||||
totp_verification_url=login_request.totp_url,
|
||||
browser_session_id=login_request.browser_session_id,
|
||||
max_screenshot_scrolls=login_request.max_screenshot_scrolling_times,
|
||||
extra_http_headers=login_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,
|
||||
parameters={
|
||||
"credential_id": login_request.credential_id,
|
||||
},
|
||||
title=new_workflow.title,
|
||||
proxy_location=login_request.proxy_location,
|
||||
webhook_url=login_request.webhook_url,
|
||||
totp_url=login_request.totp_url,
|
||||
totp_identifier=login_request.totp_identifier,
|
||||
browser_session_id=login_request.browser_session_id,
|
||||
max_screenshot_scrolls=login_request.max_screenshot_scrolling_times,
|
||||
),
|
||||
app_url=f"{settings.SKYVERN_APP_URL.rstrip('/')}/workflows/{workflow_run.workflow_permanent_id}/{workflow_run.workflow_run_id}",
|
||||
browser_session_id=login_request.browser_session_id,
|
||||
)
|
||||
16
skyvern/schemas/run_blocks.py
Normal file
16
skyvern/schemas/run_blocks.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
from skyvern.schemas.runs import ProxyLocation
|
||||
|
||||
|
||||
class LoginRequest(BaseModel):
|
||||
credential_id: str
|
||||
url: str | None = None
|
||||
prompt: str | None = None
|
||||
webhook_url: str | None = None
|
||||
proxy_location: ProxyLocation | None = None
|
||||
totp_identifier: str | None = None
|
||||
totp_url: str | None = None
|
||||
browser_session_id: str | None = None
|
||||
extra_http_headers: dict[str, str] | None = None
|
||||
max_screenshot_scrolling_times: int | None = None
|
||||
Reference in New Issue
Block a user