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.
- gl_browser_use/__init__.py +60 -0
- gl_browser_use/__init__.pyi +12 -0
- gl_browser_use/client.py +639 -0
- gl_browser_use/client.pyi +57 -0
- gl_browser_use/config.py +169 -0
- gl_browser_use/config.pyi +42 -0
- gl_browser_use/errors.py +119 -0
- gl_browser_use/errors.pyi +40 -0
- gl_browser_use/events.py +147 -0
- gl_browser_use/events.pyi +29 -0
- gl_browser_use/infrastructure/__init__.py +39 -0
- gl_browser_use/infrastructure/__init__.pyi +6 -0
- gl_browser_use/infrastructure/base.py +103 -0
- gl_browser_use/infrastructure/base.pyi +56 -0
- gl_browser_use/infrastructure/steel.py +296 -0
- gl_browser_use/infrastructure/steel.pyi +56 -0
- gl_browser_use/models.py +45 -0
- gl_browser_use/models.pyi +32 -0
- gl_browser_use/parsing.py +264 -0
- gl_browser_use/parsing.pyi +41 -0
- gl_browser_use/parsing_recovery.py +204 -0
- gl_browser_use/parsing_recovery.pyi +35 -0
- gl_browser_use/session_errors.py +72 -0
- gl_browser_use/session_errors.pyi +16 -0
- gl_browser_use/storage/__init__.py +39 -0
- gl_browser_use/storage/__init__.pyi +6 -0
- gl_browser_use/storage/base.py +125 -0
- gl_browser_use/storage/base.pyi +46 -0
- gl_browser_use/storage/minio_compatible.py +84 -0
- gl_browser_use/storage/minio_compatible.pyi +15 -0
- gl_browser_use_binary-0.0.0b5.dist-info/METADATA +236 -0
- gl_browser_use_binary-0.0.0b5.dist-info/RECORD +34 -0
- gl_browser_use_binary-0.0.0b5.dist-info/WHEEL +5 -0
- gl_browser_use_binary-0.0.0b5.dist-info/top_level.txt +1 -0
gl_browser_use/config.py
ADDED
|
@@ -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
|
+
'''
|
gl_browser_use/errors.py
ADDED
|
@@ -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."""
|
gl_browser_use/events.py
ADDED
|
@@ -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
|