add timeout to browser session request (#2338)

This commit is contained in:
Shuchang Zheng
2025-05-13 16:06:13 -07:00
committed by GitHub
parent 5c29914a20
commit 082f944123
7 changed files with 47 additions and 64 deletions

View File

@@ -1,6 +1,6 @@
import json import json
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import Any, List, Optional, Sequence from typing import Any, List, Sequence
import structlog import structlog
from sqlalchemy import and_, delete, distinct, func, pool, select, tuple_, update from sqlalchemy import and_, delete, distinct, func, pool, select, tuple_, update
@@ -2656,7 +2656,7 @@ class AgentDB:
for workflow_run_block in workflow_run_blocks for workflow_run_block in workflow_run_blocks
] ]
async def get_active_persistent_browser_sessions(self, organization_id: str) -> List[PersistentBrowserSession]: async def get_active_persistent_browser_sessions(self, organization_id: str) -> list[PersistentBrowserSession]:
"""Get all active persistent browser sessions for an organization.""" """Get all active persistent browser sessions for an organization."""
try: try:
async with self.Session() as session: async with self.Session() as session:
@@ -2676,7 +2676,7 @@ class AgentDB:
async def get_persistent_browser_session_by_id( async def get_persistent_browser_session_by_id(
self, session_id: str, organization_id: str | None = None self, session_id: str, organization_id: str | None = None
) -> Optional[PersistentBrowserSession]: ) -> PersistentBrowserSession | None:
"""Get a specific persistent browser session.""" """Get a specific persistent browser session."""
try: try:
async with self.Session() as session: async with self.Session() as session:
@@ -2703,7 +2703,7 @@ class AgentDB:
async def get_persistent_browser_session( async def get_persistent_browser_session(
self, session_id: str, organization_id: str self, session_id: str, organization_id: str
) -> Optional[PersistentBrowserSessionModel]: ) -> PersistentBrowserSession | None:
"""Get a specific persistent browser session.""" """Get a specific persistent browser session."""
try: try:
async with self.Session() as session: async with self.Session() as session:
@@ -2733,6 +2733,7 @@ class AgentDB:
organization_id: str, organization_id: str,
runnable_type: str | None = None, runnable_type: str | None = None,
runnable_id: str | None = None, runnable_id: str | None = None,
timeout_minutes: int | None = None,
) -> PersistentBrowserSessionModel: ) -> PersistentBrowserSessionModel:
"""Create a new persistent browser session.""" """Create a new persistent browser session."""
try: try:
@@ -2741,6 +2742,7 @@ class AgentDB:
organization_id=organization_id, organization_id=organization_id,
runnable_type=runnable_type, runnable_type=runnable_type,
runnable_id=runnable_id, runnable_id=runnable_id,
timeout_minutes=timeout_minutes,
) )
session.add(browser_session) session.add(browser_session)
await session.commit() await session.commit()

View File

@@ -3,9 +3,10 @@ from fastapi.responses import ORJSONResponse
from skyvern import analytics from skyvern import analytics
from skyvern.forge import app from skyvern.forge import app
from skyvern.forge.sdk.routes.routers import base_router, legacy_base_router from skyvern.forge.sdk.routes.routers import base_router
from skyvern.forge.sdk.schemas.organizations import Organization from skyvern.forge.sdk.schemas.organizations import Organization
from skyvern.forge.sdk.services import org_auth_service from skyvern.forge.sdk.services import org_auth_service
from skyvern.schemas.browser_sessions import CreateBrowserSessionRequest
from skyvern.webeye.schemas import BrowserSessionResponse from skyvern.webeye.schemas import BrowserSessionResponse
@@ -25,20 +26,6 @@ from skyvern.webeye.schemas import BrowserSessionResponse
401: {"description": "Unauthorized - Invalid or missing authentication"}, 401: {"description": "Unauthorized - Invalid or missing authentication"},
}, },
) )
@legacy_base_router.get(
"/browser_sessions/{browser_session_id}",
response_model=BrowserSessionResponse,
tags=["session"],
openapi_extra={
"x-fern-sdk-group-name": "session",
"x-fern-sdk-method-name": "get_browser_session",
},
)
@legacy_base_router.get(
"/browser_sessions/{browser_session_id}/",
response_model=BrowserSessionResponse,
include_in_schema=False,
)
async def get_browser_session( async def get_browser_session(
browser_session_id: str, browser_session_id: str,
current_org: Organization = Depends(org_auth_service.get_current_org), current_org: Organization = Depends(org_auth_service.get_current_org),
@@ -68,20 +55,6 @@ async def get_browser_session(
401: {"description": "Unauthorized - Invalid or missing authentication"}, 401: {"description": "Unauthorized - Invalid or missing authentication"},
}, },
) )
@legacy_base_router.get(
"/browser_sessions",
response_model=list[BrowserSessionResponse],
tags=["session"],
openapi_extra={
"x-fern-sdk-group-name": "browser_sessions",
"x-fern-sdk-method-name": "get_browser_sessions",
},
)
@legacy_base_router.get(
"/browser_sessions/",
response_model=list[BrowserSessionResponse],
include_in_schema=False,
)
async def get_browser_sessions( async def get_browser_sessions(
current_org: Organization = Depends(org_auth_service.get_current_org), current_org: Organization = Depends(org_auth_service.get_current_org),
) -> list[BrowserSessionResponse]: ) -> list[BrowserSessionResponse]:
@@ -106,24 +79,14 @@ async def get_browser_sessions(
401: {"description": "Unauthorized - Invalid or missing authentication"}, 401: {"description": "Unauthorized - Invalid or missing authentication"},
}, },
) )
@legacy_base_router.post(
"/browser_sessions",
response_model=BrowserSessionResponse,
tags=["Browser Sessions"],
openapi_extra={
"x-fern-sdk-group-name": "session",
"x-fern-sdk-method-name": "create_browser_session",
},
)
@legacy_base_router.post(
"/browser_sessions/",
response_model=BrowserSessionResponse,
include_in_schema=False,
)
async def create_browser_session( async def create_browser_session(
browser_session_request: CreateBrowserSessionRequest,
current_org: Organization = Depends(org_auth_service.get_current_org), current_org: Organization = Depends(org_auth_service.get_current_org),
) -> BrowserSessionResponse: ) -> BrowserSessionResponse:
browser_session = await app.PERSISTENT_SESSIONS_MANAGER.create_session(current_org.organization_id) browser_session = await app.PERSISTENT_SESSIONS_MANAGER.create_session(
organization_id=current_org.organization_id,
timeout_minutes=browser_session_request.timeout,
)
return BrowserSessionResponse.from_browser_session(browser_session) return BrowserSessionResponse.from_browser_session(browser_session)
@@ -141,18 +104,6 @@ async def create_browser_session(
401: {"description": "Unauthorized - Invalid or missing authentication"}, 401: {"description": "Unauthorized - Invalid or missing authentication"},
}, },
) )
@legacy_base_router.post(
"/browser_sessions/{browser_session_id}/close",
tags=["Browser Sessions"],
openapi_extra={
"x-fern-sdk-group-name": "browser_session",
"x-fern-sdk-method-name": "close_browser_session",
},
)
@legacy_base_router.post(
"/browser_sessions/{browser_session_id}/close/",
include_in_schema=False,
)
async def close_browser_session( async def close_browser_session(
browser_session_id: str, browser_session_id: str,
current_org: Organization = Depends(org_auth_service.get_current_org), current_org: Organization = Depends(org_auth_service.get_current_org),

View File

@@ -12,6 +12,9 @@ class PersistentBrowserSession(BaseModel):
runnable_id: str | None = None runnable_id: str | None = None
browser_address: str | None = None browser_address: str | None = None
status: str | None = None status: str | None = None
timeout_minutes: int | None = None
started_at: datetime | None = None
completed_at: datetime | None = None
created_at: datetime created_at: datetime
modified_at: datetime modified_at: datetime
deleted_at: datetime | None = None deleted_at: datetime | None = None

View File

@@ -0,0 +1,14 @@
from pydantic import BaseModel, Field
MIN_TIMEOUT = 5
MAX_TIMEOUT = 10080
DEFAULT_TIMEOUT = 60
class CreateBrowserSessionRequest(BaseModel):
timeout: int = Field(
default=DEFAULT_TIMEOUT,
description=f"Timeout in minutes for the session. Timeout is applied after the session is started. Must be between {MIN_TIMEOUT} and {MAX_TIMEOUT}. Defaults to {DEFAULT_TIMEOUT}.",
ge=MIN_TIMEOUT,
le=MAX_TIMEOUT,
)

View File

@@ -1,7 +1,7 @@
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
from typing import Dict, List, Optional, Tuple from typing import Dict, Optional, Tuple
import structlog import structlog
from playwright._impl._errors import TargetClosedError from playwright._impl._errors import TargetClosedError
@@ -31,7 +31,7 @@ class PersistentSessionsManager:
cls.instance.database = database cls.instance.database = database
return cls.instance return cls.instance
async def get_active_sessions(self, organization_id: str) -> List[PersistentBrowserSession]: async def get_active_sessions(self, organization_id: str) -> list[PersistentBrowserSession]:
"""Get all active sessions for an organization.""" """Get all active sessions for an organization."""
return await self.database.get_active_persistent_browser_sessions(organization_id) return await self.database.get_active_persistent_browser_sessions(organization_id)
@@ -40,7 +40,7 @@ class PersistentSessionsManager:
browser_session = self._browser_sessions.get(session_id) browser_session = self._browser_sessions.get(session_id)
return browser_session.browser_state if browser_session else None return browser_session.browser_state if browser_session else None
async def get_session(self, session_id: str, organization_id: str) -> Optional[PersistentBrowserSession]: async def get_session(self, session_id: str, organization_id: str) -> PersistentBrowserSession | None:
"""Get a specific browser session by session ID.""" """Get a specific browser session by session ID."""
return await self.database.get_persistent_browser_session(session_id, organization_id) return await self.database.get_persistent_browser_session(session_id, organization_id)
@@ -49,6 +49,7 @@ class PersistentSessionsManager:
organization_id: str, organization_id: str,
runnable_id: str | None = None, runnable_id: str | None = None,
runnable_type: str | None = None, runnable_type: str | None = None,
timeout_minutes: int | None = None,
) -> PersistentBrowserSession: ) -> PersistentBrowserSession:
"""Create a new browser session for an organization and return its ID with the browser state.""" """Create a new browser session for an organization and return its ID with the browser state."""
@@ -61,6 +62,7 @@ class PersistentSessionsManager:
organization_id=organization_id, organization_id=organization_id,
runnable_type=runnable_type, runnable_type=runnable_type,
runnable_id=runnable_id, runnable_id=runnable_id,
timeout_minutes=timeout_minutes,
) )
return browser_session_db return browser_session_db

View File

@@ -16,7 +16,15 @@ class BrowserSessionResponse(BaseModel):
None, description="Type of runnable associated with this session (workflow, task etc)" None, description="Type of runnable associated with this session (workflow, task etc)"
) )
runnable_id: str | None = Field(None, description="ID of the associated runnable") runnable_id: str | None = Field(None, description="ID of the associated runnable")
created_at: datetime = Field(description="Timestamp when the session was created") timeout: int | None = Field(
None,
description="Timeout in minutes for the session. Timeout is applied after the session is started.",
)
started_at: datetime | None = Field(None, description="Timestamp when the session was started")
completed_at: datetime | None = Field(None, description="Timestamp when the session was completed")
created_at: datetime = Field(
description="Timestamp when the session was created (the timestamp for the initial request)"
)
modified_at: datetime = Field(description="Timestamp when the session was last modified") modified_at: datetime = Field(description="Timestamp when the session was last modified")
deleted_at: datetime | None = Field(None, description="Timestamp when the session was deleted, if applicable") deleted_at: datetime | None = Field(None, description="Timestamp when the session was deleted, if applicable")
@@ -36,6 +44,9 @@ class BrowserSessionResponse(BaseModel):
organization_id=browser_session.organization_id, organization_id=browser_session.organization_id,
runnable_type=browser_session.runnable_type, runnable_type=browser_session.runnable_type,
runnable_id=browser_session.runnable_id, runnable_id=browser_session.runnable_id,
timeout=browser_session.timeout_minutes,
started_at=browser_session.started_at,
completed_at=browser_session.completed_at,
created_at=browser_session.created_at, created_at=browser_session.created_at,
modified_at=browser_session.modified_at, modified_at=browser_session.modified_at,
deleted_at=browser_session.deleted_at, deleted_at=browser_session.deleted_at,