gl-browser-use-binary 0.0.0b5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,169 @@
1
+ """Configuration model for :class:`BrowserUseClient`.
2
+
3
+ Authors:
4
+ Reinhart Linanda (reinhart.linanda@gdplabs.id)
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import os
10
+ from typing import Any, Literal
11
+ from urllib.parse import urlparse
12
+
13
+ from openai.types.shared_params.chat_model import ChatModel
14
+ from openai.types.shared_params.reasoning_effort import ReasoningEffort
15
+ from pydantic import BaseModel, ConfigDict, Field, field_validator
16
+
17
+ from gl_browser_use.errors import BrowserUseConfigurationError
18
+
19
+ VisionDetailLevel = Literal["auto", "low", "high"]
20
+
21
+ _SECRET_MARKER: dict[str, Any] = {"secret": True}
22
+
23
+
24
+ class BrowserUseClientConfig(BaseModel):
25
+ """Typed runtime configuration for :class:`BrowserUseClient`."""
26
+
27
+ model_config = ConfigDict(extra="forbid", str_strip_whitespace=True, validate_assignment=True)
28
+
29
+ llm_openai_api_key: str | None = Field(
30
+ default_factory=lambda: os.getenv("OPENAI_API_KEY"),
31
+ description="Primary LLM API key for browser-control reasoning.",
32
+ json_schema_extra=_SECRET_MARKER,
33
+ )
34
+ llm_openai_model: ChatModel | str = Field(
35
+ default="o3",
36
+ description="Primary model for browser-control reasoning.",
37
+ )
38
+ llm_openai_temperature: float | None = Field(
39
+ default=None,
40
+ description="Primary model temperature. Use None to defer to provider defaults.",
41
+ )
42
+ llm_openai_reasoning_effort: ReasoningEffort = Field(
43
+ default="low",
44
+ description="Primary reasoning intensity.",
45
+ )
46
+ llm_openai_base_url: str | None = Field(
47
+ default=None,
48
+ description="Override base URL for the primary LLM API (http/https only).",
49
+ )
50
+
51
+ page_extraction_llm_openai_api_key: str | None = Field(
52
+ default_factory=lambda: os.getenv("OPENAI_API_KEY"),
53
+ description="LLM API key for page extraction.",
54
+ json_schema_extra=_SECRET_MARKER,
55
+ )
56
+ page_extraction_llm_openai_model: ChatModel | str = Field(
57
+ default="gpt-5-mini",
58
+ description="Model for page extraction.",
59
+ )
60
+ page_extraction_llm_openai_temperature: float | None = Field(
61
+ default=None,
62
+ description="Extraction model temperature. Use None to defer to provider defaults.",
63
+ )
64
+ page_extraction_llm_openai_reasoning_effort: ReasoningEffort = Field(
65
+ default="minimal",
66
+ description="Extraction reasoning intensity.",
67
+ )
68
+ page_extraction_llm_openai_base_url: str | None = Field(
69
+ default=None,
70
+ description="Override base URL for the extraction LLM API (http/https only).",
71
+ )
72
+
73
+ extend_system_message: str | None = Field(
74
+ default=None,
75
+ description="Optional system prompt extension.",
76
+ )
77
+ vision_detail_level: VisionDetailLevel = Field(
78
+ default="auto",
79
+ description="Vision detail level for multimodal steps.",
80
+ )
81
+ llm_timeout_in_s: int | None = Field(
82
+ default=None,
83
+ gt=0,
84
+ description="Timeout for LLM calls, in seconds.",
85
+ )
86
+ step_timeout_in_s: int = Field(
87
+ default=180,
88
+ gt=0,
89
+ description="Timeout per browser step, in seconds.",
90
+ )
91
+
92
+ enable_cloud_sync: bool = Field(
93
+ default=False,
94
+ description="Whether browser-use cloud sync is enabled.",
95
+ )
96
+ logging_level: Literal["debug", "info", "warning", "error", "result"] = Field(
97
+ default="info",
98
+ description="Verbosity for browser-use internals.",
99
+ )
100
+ max_session_retries: int = Field(
101
+ default=2,
102
+ ge=0,
103
+ description="How many session recovery attempts to make after recoverable failures.",
104
+ )
105
+ session_retry_delay_in_s: float = Field(
106
+ default=3.0,
107
+ ge=0.0,
108
+ description="Delay between recoverable session retries.",
109
+ )
110
+
111
+ @field_validator("llm_openai_base_url", "page_extraction_llm_openai_base_url")
112
+ @classmethod
113
+ def _validate_supported_url(cls, value: str | None) -> str | None:
114
+ if value is None:
115
+ return value
116
+ if "://" not in value:
117
+ raise ValueError("URL must include an explicit http:// or https:// scheme")
118
+ parsed = urlparse(value)
119
+ if parsed.scheme not in {"http", "https"}:
120
+ raise ValueError("URL scheme must be one of http, https")
121
+ if not parsed.netloc:
122
+ raise ValueError("URL must include a host")
123
+ return value
124
+
125
+ def validate(self, *, strict: bool = False) -> list[str]:
126
+ """Validate required runtime values.
127
+
128
+ Returns a list of field-level validation issues. If ``strict`` is
129
+ ``True``, raise :class:`BrowserUseConfigurationError` on any issues.
130
+ """
131
+ issues: list[str] = []
132
+ if not self.llm_openai_api_key:
133
+ issues.append("llm_openai_api_key is required to run the browser agent")
134
+ if not self.page_extraction_llm_openai_api_key:
135
+ issues.append("page_extraction_llm_openai_api_key is required for page extraction")
136
+
137
+ if self.llm_timeout_in_s is not None and self.llm_timeout_in_s <= 0:
138
+ issues.append("llm_timeout_in_s must be > 0 when set")
139
+ if self.step_timeout_in_s <= 0:
140
+ issues.append("step_timeout_in_s must be > 0")
141
+
142
+ if self.max_session_retries < 0:
143
+ issues.append("max_session_retries must be >= 0")
144
+ if self.session_retry_delay_in_s < 0:
145
+ issues.append("session_retry_delay_in_s must be >= 0")
146
+
147
+ if strict and issues:
148
+ raise BrowserUseConfigurationError(
149
+ "Invalid BrowserUseClientConfig",
150
+ details={"errors": issues, "config": self.redacted_dict()},
151
+ )
152
+
153
+ return issues
154
+
155
+ def redacted_dict(self, *, include_secrets: bool = False) -> dict[str, Any]:
156
+ """Return a config dictionary safe to log.
157
+
158
+ Fields marked with ``json_schema_extra={"secret": True}`` are replaced
159
+ with ``"***"`` unless ``include_secrets`` is ``True``.
160
+ """
161
+ data = self.model_dump()
162
+ if include_secrets:
163
+ return data
164
+ for name, field_info in type(self).model_fields.items():
165
+ extra = field_info.json_schema_extra
166
+ extra_dict = extra if isinstance(extra, dict) else {}
167
+ if extra_dict.get("secret") and data.get(name):
168
+ data[name] = "***"
169
+ return data
@@ -0,0 +1,42 @@
1
+ from _typeshed import Incomplete
2
+ from gl_browser_use.errors import BrowserUseConfigurationError as BrowserUseConfigurationError
3
+ from openai.types.shared_params.chat_model import ChatModel
4
+ from openai.types.shared_params.reasoning_effort import ReasoningEffort
5
+ from pydantic import BaseModel
6
+ from typing import Any, Literal
7
+
8
+ VisionDetailLevel: Incomplete
9
+
10
+ class BrowserUseClientConfig(BaseModel):
11
+ """Typed runtime configuration for :class:`BrowserUseClient`."""
12
+ model_config: Incomplete
13
+ llm_openai_api_key: str | None
14
+ llm_openai_model: ChatModel | str
15
+ llm_openai_temperature: float | None
16
+ llm_openai_reasoning_effort: ReasoningEffort
17
+ llm_openai_base_url: str | None
18
+ page_extraction_llm_openai_api_key: str | None
19
+ page_extraction_llm_openai_model: ChatModel | str
20
+ page_extraction_llm_openai_temperature: float | None
21
+ page_extraction_llm_openai_reasoning_effort: ReasoningEffort
22
+ page_extraction_llm_openai_base_url: str | None
23
+ extend_system_message: str | None
24
+ vision_detail_level: VisionDetailLevel
25
+ llm_timeout_in_s: int | None
26
+ step_timeout_in_s: int
27
+ enable_cloud_sync: bool
28
+ logging_level: Literal['debug', 'info', 'warning', 'error', 'result']
29
+ max_session_retries: int
30
+ session_retry_delay_in_s: float
31
+ def validate(self, *, strict: bool = False) -> list[str]:
32
+ """Validate required runtime values.
33
+
34
+ Returns a list of field-level validation issues. If ``strict`` is
35
+ ``True``, raise :class:`BrowserUseConfigurationError` on any issues.
36
+ """
37
+ def redacted_dict(self, *, include_secrets: bool = False) -> dict[str, Any]:
38
+ '''Return a config dictionary safe to log.
39
+
40
+ Fields marked with ``json_schema_extra={"secret": True}`` are replaced
41
+ with ``"***"`` unless ``include_secrets`` is ``True``.
42
+ '''
@@ -0,0 +1,119 @@
1
+ """Errors for GL Browser Use SDK.
2
+
3
+ Authors:
4
+ Reinhart Linanda (reinhart.linanda@gdplabs.id)
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Any, Final
10
+
11
+ PACKAGE_NAME: Final[str] = "gl-browser-use"
12
+
13
+ STEEL_EXTRA: Final[str] = "steel"
14
+ MINIO_EXTRA: Final[str] = "minio"
15
+
16
+
17
+ def install_hint(extra: str) -> str:
18
+ """Return a ``pip install`` hint for the given optional extra."""
19
+ return f"pip install {PACKAGE_NAME}[{extra}]"
20
+
21
+
22
+ def _merge_details(base: dict[str, Any] | None, **extras: Any) -> dict[str, Any]:
23
+ """Merge ``base`` with ``extras`` into a new details dict, dropping ``None`` values."""
24
+ merged: dict[str, Any] = {k: v for k, v in (base or {}).items() if v is not None}
25
+ merged.update({k: v for k, v in extras.items() if v is not None})
26
+ return merged
27
+
28
+
29
+ class BrowserUseSDKError(RuntimeError):
30
+ """Base exception for all SDK errors."""
31
+
32
+ def __init__(
33
+ self,
34
+ message: str,
35
+ *,
36
+ details: dict[str, Any] | None = None,
37
+ origin: BaseException | None = None,
38
+ ) -> None:
39
+ """Initialize the error."""
40
+ super().__init__(message)
41
+ self.message = message
42
+ self.details = details or {}
43
+ self.origin = origin
44
+
45
+ def __str__(self) -> str:
46
+ """Return the error message."""
47
+ if self.origin is not None:
48
+ return f"{self.message} (caused by {self.origin!s})"
49
+ return self.message
50
+
51
+
52
+ class BrowserUseDependencyError(BrowserUseSDKError):
53
+ """Raised when required optional dependencies are unavailable."""
54
+
55
+ def __init__(
56
+ self,
57
+ message: str,
58
+ *,
59
+ missing_dependency: str | None = None,
60
+ package_hint: str | None = None,
61
+ details: dict[str, Any] | None = None,
62
+ origin: BaseException | None = None,
63
+ ) -> None:
64
+ """Initialize the error."""
65
+ super().__init__(
66
+ message,
67
+ details=_merge_details(details, missing_dependency=missing_dependency, package_hint=package_hint),
68
+ origin=origin,
69
+ )
70
+ self.missing_dependency = missing_dependency
71
+ self.package_hint = package_hint
72
+
73
+
74
+ class BrowserUseMissingDependencyError(BrowserUseDependencyError):
75
+ """Raised when an optional extra dependency is not installed."""
76
+
77
+
78
+ class BrowserUseConfigurationError(BrowserUseSDKError):
79
+ """Raised when required configuration values are missing or invalid."""
80
+
81
+
82
+ class BrowserUseExecutionError(BrowserUseSDKError):
83
+ """Raised when execution of a run fails."""
84
+
85
+
86
+ class BrowserUseRetryExhaustedError(BrowserUseExecutionError):
87
+ """Raised when session retries are exhausted after recoverable errors."""
88
+
89
+ def __init__(
90
+ self,
91
+ message: str,
92
+ *,
93
+ attempt: int,
94
+ max_attempts: int,
95
+ details: dict[str, Any] | None = None,
96
+ origin: BaseException | None = None,
97
+ ) -> None:
98
+ """Initialize the error."""
99
+ super().__init__(
100
+ message,
101
+ details=_merge_details(details, attempt=attempt, max_attempts=max_attempts),
102
+ origin=origin,
103
+ )
104
+ self.attempt = attempt
105
+ self.max_attempts = max_attempts
106
+
107
+
108
+ __all__ = [
109
+ "MINIO_EXTRA",
110
+ "PACKAGE_NAME",
111
+ "STEEL_EXTRA",
112
+ "BrowserUseConfigurationError",
113
+ "BrowserUseDependencyError",
114
+ "BrowserUseExecutionError",
115
+ "BrowserUseMissingDependencyError",
116
+ "BrowserUseRetryExhaustedError",
117
+ "BrowserUseSDKError",
118
+ "install_hint",
119
+ ]
@@ -0,0 +1,40 @@
1
+ from _typeshed import Incomplete
2
+ from typing import Any, Final
3
+
4
+ __all__ = ['MINIO_EXTRA', 'PACKAGE_NAME', 'STEEL_EXTRA', 'BrowserUseConfigurationError', 'BrowserUseDependencyError', 'BrowserUseExecutionError', 'BrowserUseMissingDependencyError', 'BrowserUseRetryExhaustedError', 'BrowserUseSDKError', 'install_hint']
5
+
6
+ PACKAGE_NAME: Final[str]
7
+ STEEL_EXTRA: Final[str]
8
+ MINIO_EXTRA: Final[str]
9
+
10
+ def install_hint(extra: str) -> str:
11
+ """Return a ``pip install`` hint for the given optional extra."""
12
+
13
+ class BrowserUseSDKError(RuntimeError):
14
+ """Base exception for all SDK errors."""
15
+ message: Incomplete
16
+ details: Incomplete
17
+ origin: Incomplete
18
+ def __init__(self, message: str, *, details: dict[str, Any] | None = None, origin: BaseException | None = None) -> None:
19
+ """Initialize the error."""
20
+
21
+ class BrowserUseDependencyError(BrowserUseSDKError):
22
+ """Raised when required optional dependencies are unavailable."""
23
+ missing_dependency: Incomplete
24
+ package_hint: Incomplete
25
+ def __init__(self, message: str, *, missing_dependency: str | None = None, package_hint: str | None = None, details: dict[str, Any] | None = None, origin: BaseException | None = None) -> None:
26
+ """Initialize the error."""
27
+
28
+ class BrowserUseMissingDependencyError(BrowserUseDependencyError):
29
+ """Raised when an optional extra dependency is not installed."""
30
+ class BrowserUseConfigurationError(BrowserUseSDKError):
31
+ """Raised when required configuration values are missing or invalid."""
32
+ class BrowserUseExecutionError(BrowserUseSDKError):
33
+ """Raised when execution of a run fails."""
34
+
35
+ class BrowserUseRetryExhaustedError(BrowserUseExecutionError):
36
+ """Raised when session retries are exhausted after recoverable errors."""
37
+ attempt: Incomplete
38
+ max_attempts: Incomplete
39
+ def __init__(self, message: str, *, attempt: int, max_attempts: int, details: dict[str, Any] | None = None, origin: BaseException | None = None) -> None:
40
+ """Initialize the error."""
@@ -0,0 +1,147 @@
1
+ """Event helpers for GL Browser Use SDK.
2
+
3
+ Authors:
4
+ Reinhart Linanda (reinhart.linanda@gdplabs.id)
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ from textwrap import shorten
11
+ from typing import Any
12
+
13
+ from gl_browser_use.models import BrowserUseStreamEvent
14
+ from gl_browser_use.parsing import read_tool_call
15
+
16
+ EVENT_ID = "default_thinking_id"
17
+ TASK_COMPLETED_MESSAGE = "Task completed"
18
+ PROCESSING_MESSAGE = "Working on your request..."
19
+ STREAMING_MESSAGE = "Receive streaming URL"
20
+ RECORDING_MESSAGE = "Receive recording URL"
21
+
22
+
23
+ def build_activity_event(url: str, content: str) -> BrowserUseStreamEvent:
24
+ """Return an activity event carrying ``url`` in the standard iframe payload."""
25
+ return BrowserUseStreamEvent(
26
+ event_type="status_update",
27
+ content=content,
28
+ thinking_and_activity_info={
29
+ "data_type": "activity",
30
+ "id": EVENT_ID,
31
+ "data_value": json.dumps({"type": "iframe", "message": url}),
32
+ },
33
+ is_final=False,
34
+ tool_info=None,
35
+ metadata={"kind": "agent_thinking_step", "status": "running"},
36
+ )
37
+
38
+
39
+ def build_thinking_event(marker: str, *, content: str, final: bool = False) -> BrowserUseStreamEvent:
40
+ """Return a thinking-marker event for ``thinking_start``/``thinking_end``/``thinking``."""
41
+ data_type = marker if marker in {"thinking_start", "thinking_end"} else "thinking"
42
+ payload: dict[str, Any] = {"data_type": data_type, "data_value": "", "id": EVENT_ID}
43
+ if data_type == "thinking":
44
+ payload["data_value"] = content
45
+ return BrowserUseStreamEvent(
46
+ event_type="status_update",
47
+ content=content,
48
+ thinking_and_activity_info=payload,
49
+ is_final=final,
50
+ tool_info={"tool_calls": []},
51
+ metadata={"kind": "agent_thinking_step", "status": "running"},
52
+ )
53
+
54
+
55
+ def build_step_event(tool_calls: list[Any], content: str, *, is_final: bool) -> BrowserUseStreamEvent:
56
+ """Return a step event summarizing ``tool_calls`` with a fallback thinking narration."""
57
+ serialized: list[dict[str, Any]] = []
58
+ for call in tool_calls:
59
+ name, args, output = read_tool_call(call)
60
+ serialized.append({"name": name or "tool", "args": args, "output": output})
61
+
62
+ thinking = build_fallback_thinking_message(content, serialized)
63
+ return BrowserUseStreamEvent(
64
+ event_type="status_update",
65
+ content=content if not is_final else TASK_COMPLETED_MESSAGE,
66
+ thinking_and_activity_info={"data_type": "thinking", "data_value": thinking, "id": EVENT_ID},
67
+ is_final=is_final,
68
+ tool_info={"tool_calls": serialized, "status": "finished" if is_final else "running"},
69
+ metadata={"kind": "agent_thinking_step", "status": "finished" if is_final else "running"},
70
+ )
71
+
72
+
73
+ def build_error_event(error_message: str, *, recording_url: str = "") -> BrowserUseStreamEvent:
74
+ """Return a terminal tool-result error event with optional ``recording_url``.
75
+
76
+ ``error_message`` is expected to be already summarized by the caller via
77
+ :func:`gl_browser_use.parsing.summarize_terminal_error`.
78
+ """
79
+ metadata: dict[str, Any] = {"kind": "agent_thinking_step", "status": "error"}
80
+ if recording_url:
81
+ metadata["recording_url"] = recording_url
82
+ return BrowserUseStreamEvent(
83
+ event_type="tool_result",
84
+ content=f"Error: {error_message}",
85
+ thinking_and_activity_info={
86
+ "data_type": "error",
87
+ "data_value": error_message,
88
+ "id": EVENT_ID,
89
+ },
90
+ is_final=True,
91
+ tool_info={"tool_calls": []},
92
+ metadata=metadata,
93
+ )
94
+
95
+
96
+ def build_retry_event(attempt: int, max_attempts: int, reason: str) -> BrowserUseStreamEvent:
97
+ """Return a retry status event carrying attempt counters and the recoverable reason."""
98
+ content = f"Retrying after session disconnect ({attempt}/{max_attempts}): {reason}"
99
+ return BrowserUseStreamEvent(
100
+ event_type="status_update",
101
+ content=content,
102
+ thinking_and_activity_info={"data_type": "thinking", "data_value": content, "id": EVENT_ID},
103
+ is_final=False,
104
+ tool_info={"tool_calls": []},
105
+ metadata={"kind": "agent_thinking_step", "status": "running", "attempt": attempt, "max_attempts": max_attempts},
106
+ )
107
+
108
+
109
+ def build_fallback_thinking_message(content: str, tool_calls: list[Any]) -> str:
110
+ """Build a fallback thinking narration that lists recent tool-call outputs."""
111
+ if not tool_calls:
112
+ return "**" + content + "**\n\n- Awaiting tool output"
113
+
114
+ lines = []
115
+ for call in tool_calls:
116
+ name, _, output = read_tool_call(call)
117
+ snippet = shorten(" ".join(output.split()), width=240, placeholder="…") if output.strip() else "running"
118
+ lines.append(f"- {name or 'tool'}: {snippet}")
119
+ return "**" + content + "**\n\n" + "\n".join(lines)
120
+
121
+
122
+ def build_step_content(tool_calls: list[Any], is_done: bool) -> str:
123
+ """Return a user-visible content string for a step event based on ``tool_calls``."""
124
+ if is_done:
125
+ return TASK_COMPLETED_MESSAGE
126
+ if not tool_calls:
127
+ return PROCESSING_MESSAGE
128
+ names = [name for name, _, _ in (read_tool_call(call) for call in tool_calls) if name]
129
+ if names:
130
+ return f"Completed {', '.join(names)}"
131
+ return PROCESSING_MESSAGE
132
+
133
+
134
+ __all__ = [
135
+ "EVENT_ID",
136
+ "PROCESSING_MESSAGE",
137
+ "RECORDING_MESSAGE",
138
+ "STREAMING_MESSAGE",
139
+ "TASK_COMPLETED_MESSAGE",
140
+ "build_activity_event",
141
+ "build_error_event",
142
+ "build_fallback_thinking_message",
143
+ "build_retry_event",
144
+ "build_step_content",
145
+ "build_step_event",
146
+ "build_thinking_event",
147
+ ]
@@ -0,0 +1,29 @@
1
+ from gl_browser_use.models import BrowserUseStreamEvent
2
+ from typing import Any
3
+
4
+ __all__ = ['EVENT_ID', 'PROCESSING_MESSAGE', 'RECORDING_MESSAGE', 'STREAMING_MESSAGE', 'TASK_COMPLETED_MESSAGE', 'build_activity_event', 'build_error_event', 'build_fallback_thinking_message', 'build_retry_event', 'build_step_content', 'build_step_event', 'build_thinking_event']
5
+
6
+ EVENT_ID: str
7
+ TASK_COMPLETED_MESSAGE: str
8
+ PROCESSING_MESSAGE: str
9
+ STREAMING_MESSAGE: str
10
+ RECORDING_MESSAGE: str
11
+
12
+ def build_activity_event(url: str, content: str) -> BrowserUseStreamEvent:
13
+ """Return an activity event carrying ``url`` in the standard iframe payload."""
14
+ def build_thinking_event(marker: str, *, content: str, final: bool = False) -> BrowserUseStreamEvent:
15
+ """Return a thinking-marker event for ``thinking_start``/``thinking_end``/``thinking``."""
16
+ def build_step_event(tool_calls: list[Any], content: str, *, is_final: bool) -> BrowserUseStreamEvent:
17
+ """Return a step event summarizing ``tool_calls`` with a fallback thinking narration."""
18
+ def build_error_event(error_message: str, *, recording_url: str = '') -> BrowserUseStreamEvent:
19
+ """Return a terminal tool-result error event with optional ``recording_url``.
20
+
21
+ ``error_message`` is expected to be already summarized by the caller via
22
+ :func:`gl_browser_use.parsing.summarize_terminal_error`.
23
+ """
24
+ def build_retry_event(attempt: int, max_attempts: int, reason: str) -> BrowserUseStreamEvent:
25
+ """Return a retry status event carrying attempt counters and the recoverable reason."""
26
+ def build_fallback_thinking_message(content: str, tool_calls: list[Any]) -> str:
27
+ """Build a fallback thinking narration that lists recent tool-call outputs."""
28
+ def build_step_content(tool_calls: list[Any], is_done: bool) -> str:
29
+ """Return a user-visible content string for a step event based on ``tool_calls``."""
@@ -0,0 +1,39 @@
1
+ """Browser infrastructure abstractions and implementations.
2
+
3
+ Authors:
4
+ Reinhart Linanda (reinhart.linanda@gdplabs.id)
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from gl_browser_use.errors import STEEL_EXTRA, BrowserUseMissingDependencyError, install_hint
10
+ from gl_browser_use.infrastructure.base import BrowserInfrastructure, BrowserInfrastructureContext
11
+
12
+ __all__ = [
13
+ "BrowserInfrastructure",
14
+ "BrowserInfrastructureContext",
15
+ "SteelBrowserInfrastructure",
16
+ ]
17
+
18
+
19
+ def __getattr__(name: str):
20
+ """Lazy-load infrastructure providers behind optional dependencies."""
21
+ if name == "SteelBrowserInfrastructure":
22
+ try:
23
+ from gl_browser_use.infrastructure.steel import SteelBrowserInfrastructure
24
+ except ImportError as exc:
25
+ hint = install_hint(STEEL_EXTRA)
26
+ raise BrowserUseMissingDependencyError(
27
+ f"The Steel infrastructure provider requires optional dependencies that are not installed. "
28
+ f"Install them via `{hint}`.",
29
+ missing_dependency="steel-sdk",
30
+ package_hint=hint,
31
+ origin=exc,
32
+ ) from exc
33
+ return SteelBrowserInfrastructure
34
+
35
+ raise AttributeError(f"module 'gl_browser_use.infrastructure' has no attribute {name!r}")
36
+
37
+
38
+ def __dir__() -> list[str]:
39
+ return sorted(__all__)
@@ -0,0 +1,6 @@
1
+ from gl_browser_use.infrastructure.base import BrowserInfrastructure as BrowserInfrastructure, BrowserInfrastructureContext as BrowserInfrastructureContext
2
+
3
+ __all__ = ['BrowserInfrastructure', 'BrowserInfrastructureContext', 'SteelBrowserInfrastructure']
4
+
5
+ # Names in __all__ with no definition:
6
+ # SteelBrowserInfrastructure