diff --git a/skyvern/forge/agent.py b/skyvern/forge/agent.py index 978ea7282..1a2c0b23d 100644 --- a/skyvern/forge/agent.py +++ b/skyvern/forge/agent.py @@ -3770,6 +3770,10 @@ class ForgeAgent: task_id=task.task_id, ) return + + # Strip whitespace from the webhook URL to handle user input with leading/trailing spaces + task.webhook_callback_url = task.webhook_callback_url.strip() + last_step = await app.DATABASE.tasks.get_latest_step(task.task_id, organization_id=task.organization_id) task_response = await self.build_task_response(task=task, last_step=last_step) diff --git a/skyvern/forge/sdk/routes/agent_protocol.py b/skyvern/forge/sdk/routes/agent_protocol.py index 4bca19bff..f8005bdd1 100644 --- a/skyvern/forge/sdk/routes/agent_protocol.py +++ b/skyvern/forge/sdk/routes/agent_protocol.py @@ -122,7 +122,7 @@ from skyvern.schemas.runs import ( WorkflowRunRequest, WorkflowRunResponse, ) -from skyvern.schemas.webhooks import RetryRunWebhookRequest +from skyvern.schemas.webhooks import RetryRunWebhookRequest, RunWebhookReplayResponse from skyvern.schemas.workflows import ( BlockType, WorkflowCreateYAMLRequest, @@ -1591,6 +1591,7 @@ async def get_run_artifacts( }, description="Retry sending the webhook for a run", summary="Retry run webhook", + response_model=RunWebhookReplayResponse, ) @base_router.post("/runs/{run_id}/retry_webhook/", include_in_schema=False) async def retry_run_webhook( @@ -1598,9 +1599,9 @@ async def retry_run_webhook( request: RetryRunWebhookRequest | None = None, current_org: Organization = Depends(org_auth_service.get_current_org), x_api_key: Annotated[str | None, Header()] = None, -) -> None: +) -> RunWebhookReplayResponse: analytics.capture("skyvern-oss-agent-run-retry-webhook") - await run_service.retry_run_webhook( + return await run_service.retry_run_webhook( run_id, organization_id=current_org.organization_id, api_key=x_api_key, diff --git a/skyvern/forge/sdk/workflow/service.py b/skyvern/forge/sdk/workflow/service.py index 50b6587a8..7d05809ba 100644 --- a/skyvern/forge/sdk/workflow/service.py +++ b/skyvern/forge/sdk/workflow/service.py @@ -4677,6 +4677,9 @@ class WorkflowService: ) return + # Strip whitespace from the webhook URL to handle user input with leading/trailing spaces + workflow_run.webhook_callback_url = workflow_run.webhook_callback_url.strip() + signing_api_key = api_key if not signing_api_key: org_api_key = await app.DATABASE.organizations.get_valid_org_auth_token( diff --git a/skyvern/services/run_service.py b/skyvern/services/run_service.py index fdd0a29d8..0dde3e122 100644 --- a/skyvern/services/run_service.py +++ b/skyvern/services/run_service.py @@ -6,6 +6,7 @@ from skyvern.forge import app from skyvern.forge.sdk.schemas.tasks import TaskStatus from skyvern.forge.sdk.workflow.models.workflow import WorkflowRunStatus from skyvern.schemas.runs import RunEngine, RunResponse, RunType, TaskRunRequest, TaskRunResponse +from skyvern.schemas.webhooks import RunWebhookReplayResponse from skyvern.services import task_v1_service, task_v2_service, webhook_service, workflow_service @@ -153,49 +154,13 @@ async def retry_run_webhook( organization_id: str | None = None, api_key: str | None = None, webhook_url: str | None = None, -) -> None: - """Retry sending the webhook for a run.""" - - run = await app.DATABASE.tasks.get_run(run_id, organization_id=organization_id) - if not run: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail=f"Run not found {run_id}", - ) - - if webhook_url: - if not organization_id: - raise OrganizationNotFound(organization_id="") - await webhook_service.replay_run_webhook( - organization_id=organization_id, - run_id=run_id, - target_url=webhook_url, - api_key=api_key, - ) - return - - if run.task_run_type in [RunType.task_v1, RunType.openai_cua, RunType.anthropic_cua, RunType.ui_tars]: - task = await app.DATABASE.tasks.get_task(run_id, organization_id=organization_id) - if not task: - raise TaskNotFound(task_id=run_id) - latest_step = await app.DATABASE.tasks.get_latest_step(run_id, organization_id=organization_id) - if latest_step: - await app.agent.execute_task_webhook(task=task, api_key=api_key) - elif run.task_run_type == RunType.task_v2: - task_v2 = await app.DATABASE.observer.get_task_v2(run_id, organization_id=organization_id) - if not task_v2: - raise TaskNotFound(task_id=run_id) - await task_v2_service.send_task_v2_webhook(task_v2) - elif run.task_run_type == RunType.workflow_run: - workflow_run = await app.DATABASE.workflow_runs.get_workflow_run( - workflow_run_id=run_id, - organization_id=organization_id, - ) - if not workflow_run: - raise WorkflowRunNotFound(workflow_run_id=run_id) - await app.WORKFLOW_SERVICE.execute_workflow_webhook(workflow_run, api_key=api_key) - else: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail=f"Invalid run type to retry webhook: {run.task_run_type}", - ) +) -> RunWebhookReplayResponse: + """Retry sending the webhook for a run, optionally to a custom URL.""" + if not organization_id: + raise OrganizationNotFound(organization_id="") + return await webhook_service.replay_run_webhook( + organization_id=organization_id, + run_id=run_id, + target_url=webhook_url, + api_key=api_key, + ) diff --git a/skyvern/services/task_v2_service.py b/skyvern/services/task_v2_service.py index 537df89b6..e845c4bc9 100644 --- a/skyvern/services/task_v2_service.py +++ b/skyvern/services/task_v2_service.py @@ -2052,6 +2052,8 @@ async def build_task_v2_run_response(task_v2: TaskV2) -> TaskRunResponse: async def send_task_v2_webhook(task_v2: TaskV2) -> None: if not task_v2.webhook_callback_url: return + # Strip whitespace from the webhook URL to handle user input with leading/trailing spaces + task_v2.webhook_callback_url = task_v2.webhook_callback_url.strip() organization_id = task_v2.organization_id if not organization_id: return diff --git a/skyvern/services/webhook_service.py b/skyvern/services/webhook_service.py index 847abd49a..14cc16ae7 100644 --- a/skyvern/services/webhook_service.py +++ b/skyvern/services/webhook_service.py @@ -516,6 +516,7 @@ def _as_run_type_str(run_type: RunType | str | None) -> str: def _validate_target_url(url: str) -> str: + url = url.strip() try: validated_url = validate_url(url) if not validated_url: