diff --git a/skyvern/forge/sdk/routes/agent_protocol.py b/skyvern/forge/sdk/routes/agent_protocol.py index 2639d362..0342778f 100644 --- a/skyvern/forge/sdk/routes/agent_protocol.py +++ b/skyvern/forge/sdk/routes/agent_protocol.py @@ -29,6 +29,8 @@ from skyvern.forge.sdk.routes.code_samples import ( CREATE_WORKFLOW_CODE_SAMPLE_PYTHON, DELETE_WORKFLOW_CODE_SAMPLE, GET_RUN_CODE_SAMPLE, + GET_WORKFLOWS_CODE_SAMPLE, + RETRY_RUN_WEBHOOK_CODE_SAMPLE, RUN_TASK_CODE_SAMPLE, RUN_WORKFLOW_CODE_SAMPLE, UPDATE_WORKFLOW_CODE_SAMPLE, @@ -666,6 +668,69 @@ async def delete_workflow( await app.WORKFLOW_SERVICE.delete_workflow_by_permanent_id(workflow_id, current_org.organization_id) +@base_router.get( + "/artifacts/{artifact_id}", + tags=["Artifacts"], + response_model=Artifact, + openapi_extra={ + "x-fern-sdk-group-name": "artifacts", + "x-fern-sdk-method-name": "get_artifact", + }, + description="Get an artifact", + summary="Get an artifact", + responses={ + 200: {"description": "Successfully retrieved artifact"}, + 404: {"description": "Artifact not found"}, + }, +) +@base_router.get("/artifacts/{artifact_id}/", response_model=Artifact, include_in_schema=False) +async def get_artifact( + artifact_id: str, + current_org: Organization = Depends(org_auth_service.get_current_org), +) -> Artifact: + analytics.capture("skyvern-oss-artifact-get") + artifact = await app.DATABASE.get_artifact_by_id( + artifact_id=artifact_id, + organization_id=current_org.organization_id, + ) + if not artifact: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"Artifact not found {artifact_id}", + ) + if settings.ENV != "local" or settings.GENERATE_PRESIGNED_URLS: + signed_urls = await app.ARTIFACT_MANAGER.get_share_links([artifact]) + if signed_urls: + artifact.signed_url = signed_urls[0] + else: + LOG.warning( + "Failed to get signed url for artifact", + artifact_id=artifact_id, + ) + return artifact + + +@base_router.post( + "/runs/{run_id}/retry_webhook", + tags=["Agent"], + openapi_extra={ + "x-fern-sdk-group-name": "agent", + "x-fern-sdk-method-name": "retry_run_webhook", + "x-fern-examples": [{"code-samples": [{"sdk": "python", "code": RETRY_RUN_WEBHOOK_CODE_SAMPLE}]}], + }, + description="Retry sending the webhook for a run", + summary="Retry run webhook", +) +@base_router.post("/runs/{run_id}/retry_webhook/", include_in_schema=False) +async def retry_run_webhook( + run_id: str = Path(..., description="The id of the task run or the workflow run.", examples=["tsk_123", "wr_123"]), + current_org: Organization = Depends(org_auth_service.get_current_org), + x_api_key: Annotated[str | None, Header()] = None, +) -> None: + analytics.capture("skyvern-oss-agent-run-retry-webhook") + await run_service.retry_run_webhook(run_id, organization_id=current_org.organization_id, api_key=x_api_key) + + ################# Legacy Endpoints ################# @legacy_base_router.post( "/webhook", @@ -1344,6 +1409,17 @@ async def get_workflow_run( response_model=list[Workflow], include_in_schema=False, ) +@base_router.get( + "/workflows", + response_model=list[Workflow], + tags=["Workflows"], + openapi_extra={ + "x-fern-sdk-group-name": "workflows", + "x-fern-sdk-method-name": "get_workflows", + "x-fern-examples": [{"code-samples": [{"sdk": "python", "code": GET_WORKFLOWS_CODE_SAMPLE}]}], + }, +) +@base_router.get("/workflows/", response_model=list[Workflow], include_in_schema=False) async def get_workflows( page: int = Query(1, ge=1), page_size: int = Query(10, ge=1), diff --git a/skyvern/forge/sdk/routes/code_samples.py b/skyvern/forge/sdk/routes/code_samples.py index e9e3cbe8..6597c445 100644 --- a/skyvern/forge/sdk/routes/code_samples.py +++ b/skyvern/forge/sdk/routes/code_samples.py @@ -1,3 +1,4 @@ +# Agent RUN_TASK_CODE_SAMPLE = """from skyvern import Skyvern skyvern = Skyvern(api_key="YOUR_API_KEY") @@ -19,6 +20,13 @@ CANCEL_RUN_CODE_SAMPLE = """from skyvern import Skyvern skyvern = Skyvern(api_key="YOUR_API_KEY") await skyvern.agent.cancel_run(run_id="tsk_v2_123") """ +RETRY_RUN_WEBHOOK_CODE_SAMPLE = """from skyvern import Skyvern + +skyvern = Skyvern(api_key="YOUR_API_KEY") +await skyvern.agent.retry_run_webhook(run_id="tsk_v2_123") +""" + +# Workflows CREATE_WORKFLOW_CODE_SAMPLE = """curl -X POST https://api.skyvern.com/v1/workflows \ --header 'x-api-key: {{x-api-key}}' \ --header 'Content-Type: text/plain' \ @@ -353,6 +361,14 @@ DELETE_WORKFLOW_CODE_SAMPLE = """from skyvern import Skyvern skyvern = Skyvern(api_key="YOUR_API_KEY") await skyvern.workflows.delete_workflow(workflow_id="wpid_123") """ +GET_WORKFLOWS_CODE_SAMPLE = """from skyvern import Skyvern + +skyvern = Skyvern(api_key="YOUR_API_KEY") +workflows = await skyvern.workflows.get_workflows() +print(workflows) +""" + +# Credentials SEND_TOTP_CODE_CODE_SAMPLE = """from skyvern import Skyvern skyvern = Skyvern(api_key="YOUR_API_KEY") @@ -400,6 +416,9 @@ skyvern = Skyvern(api_key="YOUR_API_KEY") credentials = await skyvern.credentials.get_credentials() print(credentials) """ + +# Browser Sessions + CREATE_BROWSER_SESSION_CODE_SAMPLE = """from skyvern import Skyvern skyvern = Skyvern(api_key="YOUR_API_KEY") diff --git a/skyvern/services/run_service.py b/skyvern/services/run_service.py index 49c2804b..c4e6a5ba 100644 --- a/skyvern/services/run_service.py +++ b/skyvern/services/run_service.py @@ -137,3 +137,40 @@ async def cancel_run(run_id: str, organization_id: str | None = None, api_key: s status_code=status.HTTP_400_BAD_REQUEST, detail=f"Invalid run type to cancel: {run.task_run_type}", ) + + +async def retry_run_webhook(run_id: str, organization_id: str | None = None, api_key: str | None = None) -> None: + """Retry sending the webhook for a run.""" + + run = await app.DATABASE.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 run.task_run_type in [RunType.task_v1, RunType.openai_cua, RunType.anthropic_cua]: + task = await app.DATABASE.get_task(run_id, organization_id=organization_id) + if not task: + raise TaskNotFound(task_id=run_id) + latest_step = await app.DATABASE.get_latest_step(run_id, organization_id=organization_id) + if latest_step: + await app.agent.execute_task_webhook(task=task, last_step=latest_step, api_key=api_key) + elif run.task_run_type == RunType.task_v2: + task_v2 = await app.DATABASE.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.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}", + )