eigent/backend/app/utils/cdp_browser_state.py
Tong Chen cf3cb8cbcd
feat: Space layer above Project + RunContext + Single Agent CDP (#1655)
Co-authored-by: Douglas Lai <115660088+Douglasymlai@users.noreply.github.com>
Co-authored-by: Douglas <douglas.ym.lai@gmail.com>
2026-05-29 16:19:48 +08:00

167 lines
5 KiB
Python

# ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
import logging
import time
from typing import Any
from fastapi import Request
from app.component.environment import env
from app.utils.browser_launcher import (
_is_cdp_available,
is_cdp_url_available,
is_local_cdp_host,
normalize_cdp_url,
)
logger = logging.getLogger("cdp_browser_state")
_web_cdp_browser_meta_by_owner: dict[str, dict[str, Any]] = {}
def browser_owner_key(request: Request | None) -> str:
auth = getattr(getattr(request, "state", None), "brain_auth", None)
user_id = getattr(auth, "user_id", None)
if user_id and user_id != "local":
return str(user_id)
# Bridge release fallback: NoneAuth still emits "local", while the
# frontend already sends X-User-ID. Phase B auth will make this token-only.
if request is not None:
header_user_id = request.headers.get("x-user-id")
if header_user_id:
return header_user_id
return "local"
def build_web_cdp_browser(
endpoint: str,
*,
is_external: bool,
name: str | None = None,
added_at: int | None = None,
resource_session_id: str | None = None,
managed_by: str = "local",
) -> dict[str, Any]:
normalized_endpoint, host, port = normalize_cdp_url(endpoint)
default_location = (
str(port) if is_local_cdp_host(host) else f"{host}:{port}"
)
browser_name = name or (
f"External Browser ({default_location})"
if is_external
else f"Managed Browser ({default_location})"
)
browser_id = resource_session_id or (
f"web-cdp-{port}"
if is_local_cdp_host(host)
else f"web-cdp-{host.replace('.', '-')}-{port}"
)
return {
"id": browser_id,
"port": port,
"endpoint": normalized_endpoint,
"host": host,
"isExternal": is_external,
"name": browser_name,
"addedAt": added_at or int(time.time() * 1000),
"resourceSessionId": resource_session_id,
"managedBy": managed_by,
}
def get_connected_cdp_endpoint(owner_key: str) -> str | None:
if owner_key in _web_cdp_browser_meta_by_owner:
return _web_cdp_browser_meta_by_owner[owner_key].get("endpoint")
cdp_url = env("EIGENT_CDP_URL")
if cdp_url:
return cdp_url
return None
def get_connected_cdp_endpoint_for_request(
request: Request | None,
) -> str | None:
return get_connected_cdp_endpoint(browser_owner_key(request))
def get_connected_cdp_meta(owner_key: str) -> dict[str, Any] | None:
return _web_cdp_browser_meta_by_owner.get(owner_key)
def get_connected_cdp_port(owner_key: str) -> int | None:
cdp_url = get_connected_cdp_endpoint(owner_key)
if not cdp_url:
return None
try:
_, _, port = normalize_cdp_url(cdp_url)
return port
except Exception:
logger.warning("Invalid EIGENT_CDP_URL: %s", cdp_url)
return None
def set_connected_cdp_browser(
owner_key: str,
endpoint: str,
*,
is_external: bool,
name: str | None = None,
resource_session_id: str | None = None,
managed_by: str = "local",
) -> dict[str, Any]:
normalized_endpoint, _, _ = normalize_cdp_url(endpoint)
browser = build_web_cdp_browser(
normalized_endpoint,
is_external=is_external,
name=name,
resource_session_id=resource_session_id,
managed_by=managed_by,
)
_web_cdp_browser_meta_by_owner[owner_key] = browser
return browser
def clear_connected_cdp_browser(owner_key: str) -> None:
_web_cdp_browser_meta_by_owner.pop(owner_key, None)
def clear_connected_cdp_browser_for_request(request: Request | None) -> None:
clear_connected_cdp_browser(browser_owner_key(request))
def is_cdp_endpoint_available(endpoint: str) -> bool:
_, host, port = normalize_cdp_url(endpoint)
if is_local_cdp_host(host):
return _is_cdp_available(port)
return is_cdp_url_available(endpoint)
def list_connected_cdp_browsers(owner_key: str) -> list[dict[str, Any]]:
meta = _web_cdp_browser_meta_by_owner.get(owner_key)
endpoint = get_connected_cdp_endpoint(owner_key)
if endpoint is None:
return []
if not is_cdp_endpoint_available(endpoint):
if meta and meta.get("endpoint") == endpoint:
clear_connected_cdp_browser(owner_key)
return []
if meta and meta.get("endpoint") == endpoint:
return [meta]
return [build_web_cdp_browser(endpoint, is_external=True)]