Backend: unified /runs URL (#3898)

This commit is contained in:
Jonathan Dobson
2025-11-04 18:30:17 -05:00
committed by GitHub
parent 324c3f921d
commit 8288c973bd
10 changed files with 113 additions and 22 deletions

View File

@@ -1494,6 +1494,34 @@ class AgentDB:
LOG.error("SQLAlchemyError", exc_info=True)
raise
async def get_workflow_for_workflow_run(
self,
workflow_run_id: str,
organization_id: str | None = None,
exclude_deleted: bool = True,
) -> Workflow | None:
try:
get_workflow_query = select(WorkflowModel)
if exclude_deleted:
get_workflow_query = get_workflow_query.filter(WorkflowModel.deleted_at.is_(None))
if organization_id:
get_workflow_query = get_workflow_query.filter_by(organization_id=organization_id)
get_workflow_query = get_workflow_query.join(
WorkflowRunModel,
WorkflowRunModel.workflow_id == WorkflowModel.workflow_id,
)
get_workflow_query = get_workflow_query.filter(WorkflowRunModel.workflow_run_id == workflow_run_id)
async with self.Session() as session:
if workflow := (await session.scalars(get_workflow_query)).first():
return convert_to_workflow(workflow, self.debug_enabled)
return None
except SQLAlchemyError:
LOG.error("SQLAlchemyError", exc_info=True)
raise
async def get_workflow_versions_by_permanent_id(
self,
workflow_permanent_id: str,

View File

@@ -81,6 +81,7 @@ from skyvern.forge.sdk.workflow.models.workflow import (
WorkflowRun,
WorkflowRunResponseBase,
WorkflowRunStatus,
WorkflowRunWithWorkflowResponse,
)
from skyvern.schemas.artifacts import EntityType, entity_type_to_param
from skyvern.schemas.runs import (
@@ -203,7 +204,7 @@ async def run_task(
failure_reason=task_v1_response.failure_reason,
created_at=task_v1_response.created_at,
modified_at=task_v1_response.modified_at,
app_url=f"{settings.SKYVERN_APP_URL.rstrip('/')}/tasks/{task_v1_response.task_id}",
app_url=f"{settings.SKYVERN_APP_URL.rstrip('/')}/runs/{task_v1_response.task_id}",
run_request=TaskRunRequest(
engine=run_request.engine,
prompt=task_v1_response.navigation_goal,
@@ -266,7 +267,7 @@ async def run_task(
failure_reason=None,
created_at=task_v2.created_at,
modified_at=task_v2.modified_at,
app_url=f"{settings.SKYVERN_APP_URL.rstrip('/')}/workflows/{task_v2.workflow_permanent_id}/{task_v2.workflow_run_id}",
app_url=f"{settings.SKYVERN_APP_URL.rstrip('/')}/runs/{task_v2.workflow_run_id}",
run_request=TaskRunRequest(
engine=RunEngine.skyvern_v2,
prompt=task_v2.prompt,
@@ -367,7 +368,7 @@ async def run_workflow(
run_request=workflow_run_request,
downloaded_files=None,
recording_url=None,
app_url=f"{settings.SKYVERN_APP_URL.rstrip('/')}/workflows/{workflow_run.workflow_permanent_id}/{workflow_run.workflow_run_id}",
app_url=f"{settings.SKYVERN_APP_URL.rstrip('/')}/runs/{workflow_run.workflow_run_id}",
run_with=workflow_run.run_with,
ai_fallback=workflow_run.ai_fallback,
)
@@ -1118,7 +1119,7 @@ async def run_block(
run_request=block_run_request,
downloaded_files=None,
recording_url=None,
app_url=f"{settings.SKYVERN_APP_URL.rstrip('/')}/workflows/{workflow_run.workflow_permanent_id}/{workflow_run.workflow_run_id}",
app_url=f"{settings.SKYVERN_APP_URL.rstrip('/')}/runs/{workflow_run.workflow_run_id}",
)
@@ -1808,6 +1809,42 @@ async def get_workflow_run_with_workflow_id(
return return_dict
@base_router.get(
"/workflows/runs/{workflow_run_id}",
include_in_schema=False,
)
@base_router.get(
"/workflows/runs/{workflow_run_id}/",
include_in_schema=False,
)
async def get_workflow_and_run_from_workflow_run_id(
workflow_run_id: str,
current_org: Organization = Depends(org_auth_service.get_current_org),
) -> WorkflowRunWithWorkflowResponse:
workflow = await app.WORKFLOW_SERVICE.get_workflow_by_workflow_run_id(
workflow_run_id=workflow_run_id,
organization_id=current_org.organization_id,
)
if not workflow:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Workflow run not found {workflow_run_id}",
)
workflow_run_status_api_response = await get_workflow_run_with_workflow_id(
workflow_id=workflow.workflow_permanent_id,
workflow_run_id=workflow_run_id,
current_org=current_org,
)
workflow_run_status_api_response["workflow"] = workflow
response = WorkflowRunWithWorkflowResponse.model_validate(workflow_run_status_api_response)
return response
@legacy_base_router.get(
"/workflows/{workflow_id}/runs/{workflow_run_id}/timeline",
tags=["agent"],

View File

@@ -238,6 +238,6 @@ async def login(
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}",
app_url=f"{settings.SKYVERN_APP_URL.rstrip('/')}/runs/{workflow_run.workflow_run_id}",
browser_session_id=login_request.browser_session_id,
)

View File

@@ -3187,8 +3187,7 @@ class HumanInteractionBlock(BaseTaskBlock):
organization_id=organization_id,
)
workflow_permanent_id = workflow_run.workflow_permanent_id
app_url = f"{settings.SKYVERN_APP_URL}/workflows/{workflow_permanent_id}/{workflow_run_id}/overview"
app_url = f"{settings.SKYVERN_APP_URL}/runs/{workflow_run_id}/overview"
body = f"{self.body}\n\nKindly visit {app_url}\n\n{self.instructions}\n\n"
subject = f"{self.subject} - Workflow Run ID: {workflow_run_id}"

View File

@@ -201,3 +201,7 @@ class WorkflowRunResponseBase(BaseModel):
browser_address: str | None = None
script_run: ScriptRunResponse | None = None
errors: list[dict[str, Any]] | None = None
class WorkflowRunWithWorkflowResponse(WorkflowRunResponseBase):
workflow: Workflow

View File

@@ -26,6 +26,7 @@ from skyvern.exceptions import (
ScriptTerminationException,
SkyvernException,
WorkflowNotFound,
WorkflowNotFoundForWorkflowRun,
WorkflowRunNotFound,
)
from skyvern.forge import app
@@ -1123,6 +1124,23 @@ class WorkflowService:
)
return workflows
async def get_workflow_by_workflow_run_id(
self,
workflow_run_id: str,
organization_id: str | None = None,
exclude_deleted: bool = True,
) -> Workflow:
workflow = await app.DATABASE.get_workflow_for_workflow_run(
workflow_run_id,
organization_id=organization_id,
exclude_deleted=exclude_deleted,
)
if not workflow:
raise WorkflowNotFoundForWorkflowRun(workflow_run_id=workflow_run_id)
return workflow
async def get_block_outputs_for_debug_session(
self,
workflow_permanent_id: str,
@@ -2049,10 +2067,8 @@ class WorkflowService:
return
# build new schema for backward compatible webhook payload
app_url = (
f"{settings.SKYVERN_APP_URL.rstrip('/')}/workflows/"
f"{workflow_run.workflow_permanent_id}/{workflow_run.workflow_run_id}"
)
app_url = f"{settings.SKYVERN_APP_URL.rstrip('/')}/runs/{workflow_run.workflow_run_id}"
workflow_run_response = WorkflowRunResponse(
run_id=workflow_run.workflow_run_id,
run_type=RunType.workflow_run,