2026-02-07 02:33:13 -08:00
from __future__ import annotations
from typing import Annotated , Any
from pydantic import Field
2026-02-18 11:34:12 -08:00
from skyvern . cli . core . api_key_hash import hash_api_key_for_cache
from skyvern . cli . core . client import get_active_api_key
from skyvern . cli . core . session_manager import is_stateless_http_mode
2026-02-17 11:24:56 -08:00
from skyvern . cli . core . session_ops import do_session_close , do_session_create , do_session_list
2026-02-18 11:34:12 -08:00
from skyvern . schemas . runs import ProxyLocation
2026-02-07 02:33:13 -08:00
from . _common import BrowserContext , ErrorCode , Timer , make_error , make_result
from . _session import (
SessionState ,
get_current_session ,
get_skyvern ,
resolve_browser ,
set_current_session ,
)
2026-02-18 11:34:12 -08:00
def _session_api_key_hash ( ) - > str | None :
api_key = get_active_api_key ( )
if not api_key :
return None
return hash_api_key_for_cache ( api_key )
2026-02-07 02:33:13 -08:00
async def skyvern_session_create (
timeout : Annotated [ int | None , Field ( description = " Session timeout in minutes (5-1440) " ) ] = 60 ,
proxy_location : Annotated [ str | None , Field ( description = " Proxy location: RESIDENTIAL, US, etc. " ) ] = None ,
local : Annotated [ bool , Field ( description = " Launch local browser instead of cloud " ) ] = False ,
headless : Annotated [ bool , Field ( description = " Run local browser in headless mode " ) ] = False ,
) - > dict [ str , Any ] :
2026-02-12 14:49:10 -08:00
""" Create a new browser session to start interacting with websites. Creates a cloud-hosted browser by default with geographic proxy support. This must be called before using any browser tools (navigate, click, extract, etc.).
2026-02-07 02:33:13 -08:00
Use local = true for a local Chromium instance .
The session persists across tool calls until explicitly closed .
"""
with Timer ( ) as timer :
try :
2026-02-18 11:34:12 -08:00
if is_stateless_http_mode ( ) and local :
return make_result (
" skyvern_session_create " ,
ok = False ,
error = make_error (
ErrorCode . INVALID_INPUT ,
" Local browser sessions are not supported in stateless HTTP mode " ,
" Use cloud sessions for remote MCP transport " ,
) ,
)
2026-02-07 02:33:13 -08:00
skyvern = get_skyvern ( )
2026-02-18 11:34:12 -08:00
if is_stateless_http_mode ( ) :
proxy = ProxyLocation ( proxy_location ) if proxy_location else None
session = await skyvern . create_browser_session ( timeout = timeout or 60 , proxy_location = proxy )
timer . mark ( " sdk " )
ctx = BrowserContext ( mode = " cloud_session " , session_id = session . browser_session_id )
return make_result (
" skyvern_session_create " ,
browser_context = ctx ,
data = {
" session_id " : session . browser_session_id ,
" timeout_minutes " : timeout or 60 ,
} ,
timing_ms = timer . timing_ms ,
)
2026-02-17 11:24:56 -08:00
browser , result = await do_session_create (
skyvern ,
timeout = timeout or 60 ,
proxy_location = proxy_location ,
local = local ,
headless = headless ,
)
timer . mark ( " sdk " )
2026-02-07 02:33:13 -08:00
2026-02-17 11:24:56 -08:00
if result . local :
2026-02-07 02:33:13 -08:00
ctx = BrowserContext ( mode = " local " )
2026-02-17 11:24:56 -08:00
else :
ctx = BrowserContext ( mode = " cloud_session " , session_id = result . session_id )
2026-02-18 11:34:12 -08:00
set_current_session ( SessionState ( browser = browser , context = ctx , api_key_hash = _session_api_key_hash ( ) ) )
2026-02-07 02:33:13 -08:00
except ValueError as e :
return make_result (
" skyvern_session_create " ,
ok = False ,
timing_ms = timer . timing_ms ,
error = make_error (
ErrorCode . SDK_ERROR ,
str ( e ) ,
" Cloud sessions require SKYVERN_API_KEY. Check your environment. " ,
) ,
)
except Exception as e :
return make_result (
" skyvern_session_create " ,
ok = False ,
timing_ms = timer . timing_ms ,
error = make_error ( ErrorCode . SDK_ERROR , str ( e ) , " Failed to create browser session " ) ,
)
2026-02-17 11:24:56 -08:00
if result . local :
return make_result (
" skyvern_session_create " ,
browser_context = ctx ,
data = { " local " : True , " headless " : result . headless } ,
timing_ms = timer . timing_ms ,
)
2026-02-07 02:33:13 -08:00
return make_result (
" skyvern_session_create " ,
browser_context = ctx ,
data = {
2026-02-17 11:24:56 -08:00
" session_id " : result . session_id ,
" timeout_minutes " : result . timeout_minutes ,
2026-02-07 02:33:13 -08:00
} ,
timing_ms = timer . timing_ms ,
)
async def skyvern_session_close (
session_id : Annotated [ str | None , Field ( description = " Session ID to close (uses current if not specified) " ) ] = None ,
) - > dict [ str , Any ] :
""" Close a browser session when you ' re done. Frees cloud resources.
Closes the specified session or the current active session .
"""
current = get_current_session ( )
with Timer ( ) as timer :
try :
if session_id :
2026-02-18 10:53:55 -08:00
matching_cloud_session = (
current . context is not None
and current . context . mode == " cloud_session "
and current . context . session_id == session_id
)
2026-02-07 02:33:13 -08:00
skyvern = get_skyvern ( )
2026-02-18 10:53:55 -08:00
result = None
close_error : Exception | None = None
try :
result = await do_session_close ( skyvern , session_id )
except Exception as e :
close_error = e
if matching_cloud_session :
if current . browser is None :
set_current_session ( SessionState ( ) )
raise RuntimeError ( " Expected active browser for matching cloud session " )
try :
await current . browser . close ( )
except Exception as browser_err :
if close_error is not None :
raise browser_err from close_error
raise
finally :
set_current_session ( SessionState ( ) )
elif current . context and current . context . session_id == session_id :
2026-02-07 02:33:13 -08:00
set_current_session ( SessionState ( ) )
2026-02-18 10:53:55 -08:00
if close_error is not None :
raise close_error
if result is None :
raise RuntimeError ( " Expected session close result after successful close operation " )
2026-02-07 02:33:13 -08:00
timer . mark ( " sdk " )
return make_result (
" skyvern_session_close " ,
2026-02-17 11:24:56 -08:00
data = { " session_id " : result . session_id , " closed " : result . closed } ,
2026-02-07 02:33:13 -08:00
timing_ms = timer . timing_ms ,
)
if current . browser is None :
return make_result (
" skyvern_session_close " ,
ok = False ,
error = make_error (
ErrorCode . NO_ACTIVE_BROWSER ,
" No active session to close " ,
" Provide a session_id or create a session first " ,
) ,
)
closed_id = current . context . session_id if current . context else None
await current . browser . close ( )
set_current_session ( SessionState ( ) )
timer . mark ( " sdk " )
except Exception as e :
return make_result (
" skyvern_session_close " ,
ok = False ,
timing_ms = timer . timing_ms ,
error = make_error ( ErrorCode . SDK_ERROR , str ( e ) , " Failed to close session " ) ,
)
return make_result (
" skyvern_session_close " ,
data = { " session_id " : closed_id , " closed " : True } ,
timing_ms = timer . timing_ms ,
)
async def skyvern_session_list ( ) - > dict [ str , Any ] :
""" List all active browser sessions. Use to find available sessions to connect to. """
with Timer ( ) as timer :
try :
skyvern = get_skyvern ( )
2026-02-17 11:24:56 -08:00
sessions = await do_session_list ( skyvern )
2026-02-07 02:33:13 -08:00
timer . mark ( " sdk " )
session_data = [
{
2026-02-17 11:24:56 -08:00
" session_id " : s . session_id ,
2026-02-07 02:33:13 -08:00
" status " : s . status ,
2026-02-17 11:24:56 -08:00
" started_at " : s . started_at ,
2026-02-07 02:33:13 -08:00
" timeout " : s . timeout ,
" runnable_id " : s . runnable_id ,
2026-02-17 11:24:56 -08:00
" available " : s . available ,
2026-02-07 02:33:13 -08:00
}
for s in sessions
]
except ValueError as e :
return make_result (
" skyvern_session_list " ,
ok = False ,
timing_ms = timer . timing_ms ,
error = make_error (
ErrorCode . SDK_ERROR ,
str ( e ) ,
" Listing sessions requires SKYVERN_API_KEY " ,
) ,
)
except Exception as e :
return make_result (
" skyvern_session_list " ,
ok = False ,
timing_ms = timer . timing_ms ,
error = make_error ( ErrorCode . SDK_ERROR , str ( e ) , " Failed to list sessions " ) ,
)
current = get_current_session ( )
current_id = current . context . session_id if current . context else None
return make_result (
" skyvern_session_list " ,
data = {
" sessions " : session_data ,
" count " : len ( session_data ) ,
" current_session_id " : current_id ,
} ,
timing_ms = timer . timing_ms ,
)
async def skyvern_session_get (
session_id : Annotated [ str , " Browser session ID to get details for " ] ,
) - > dict [ str , Any ] :
""" Get details about a specific browser session -- status, timeout, availability. """
with Timer ( ) as timer :
try :
skyvern = get_skyvern ( )
session = await skyvern . get_browser_session ( session_id )
timer . mark ( " sdk " )
except Exception as e :
return make_result (
" skyvern_session_get " ,
ok = False ,
timing_ms = timer . timing_ms ,
error = make_error ( ErrorCode . BROWSER_NOT_FOUND , str ( e ) , " Check the session ID is correct " ) ,
)
current = get_current_session ( )
is_current = current . context and current . context . session_id == session_id
return make_result (
" skyvern_session_get " ,
browser_context = BrowserContext ( mode = " cloud_session " , session_id = session_id ) if is_current else None ,
data = {
" session_id " : session . browser_session_id ,
" status " : session . status ,
" started_at " : session . started_at . isoformat ( ) if session . started_at else None ,
" completed_at " : session . completed_at . isoformat ( ) if session . completed_at else None ,
" timeout " : session . timeout ,
" runnable_id " : session . runnable_id ,
" is_current " : is_current ,
} ,
timing_ms = timer . timing_ms ,
)
async def skyvern_session_connect (
session_id : Annotated [ str | None , Field ( description = " Cloud session ID (pbs_...) " ) ] = None ,
cdp_url : Annotated [ str | None , Field ( description = " CDP WebSocket URL " ) ] = None ,
) - > dict [ str , Any ] :
""" Connect to an existing browser -- a cloud session by ID or any browser via CDP URL.
Use this to resume work in a previously created session or attach to an external browser .
"""
if not session_id and not cdp_url :
return make_result (
" skyvern_session_connect " ,
ok = False ,
error = make_error (
ErrorCode . INVALID_INPUT ,
" Must provide session_id or cdp_url " ,
" Specify which browser to connect to " ,
) ,
)
with Timer ( ) as timer :
try :
browser , ctx = await resolve_browser ( session_id = session_id , cdp_url = cdp_url )
timer . mark ( " sdk " )
except Exception as e :
return make_result (
" skyvern_session_connect " ,
ok = False ,
timing_ms = timer . timing_ms ,
error = make_error ( ErrorCode . BROWSER_NOT_FOUND , str ( e ) , " Check the session ID or CDP URL is valid " ) ,
)
return make_result (
" skyvern_session_connect " ,
browser_context = ctx ,
data = { " connected " : True } ,
timing_ms = timer . timing_ms ,
)