fix: remote dialogs and rest

This commit is contained in:
zhom committed 2026-11-07 15:07:23 +04:00
1 parent 901612c3f6
commit 4fd2d3d3be
18 files changed
+2118 -173

No files matched your search

+57
View File
@@ -21,6 +21,8 @@ from urllib.parse import quote, urlencode, urlsplit
from .errors import DonutConnectionError, DonutError, error_for_status
from .models import (
AgentClick,
AgentDialogAnswer,
AgentHold,
AgentTyping,
ApiBulkCreateResponse,
ApiGroupResponse,
@@ -57,6 +59,7 @@ from .models import (
ImportProxiesResponse,
LocatorDescription,
LocatorResolution,
PageDialog,
PerceptionPage,
PickedElement,
ProfileImportBatchResult,
@@ -666,6 +669,32 @@ class DonutClient:
body=_body(locator=dict(locator), button=button, click_count=click_count),
)
def agent_hold(
self,
profile_id: str,
*,
locator: Optional[LocatorDescription] = None,
selector: Optional[str] = None,
duration_ms: Optional[float] = None,
button: Optional[str] = None,
) -> AgentHold:
"""POST /v1/profiles/{id}/agent/hold
Name the element with ``locator`` or ``selector``. ``duration_ms`` is
100 to 30000, default 3000; ``button`` is ``"left"`` (the default),
``"middle"`` or ``"right"``.
"""
return self._request(
"POST",
f"/v1/profiles/{_segment(profile_id)}/agent/hold",
body=_body(
locator=dict(locator) if locator is not None else None,
selector=selector,
duration_ms=duration_ms,
button=button,
),
)
def agent_type(
self,
profile_id: str,
@@ -739,6 +768,34 @@ class DonutClient:
body=_body(timeout_ms=timeout_ms),
)
def agent_dialog(self, profile_id: str) -> PageDialog:
"""GET /v1/profiles/{id}/agent/dialog
Whether a native dialog holds the page, and which. Never refused for
the dialog: while one is open, every other agent call raises
``DIALOG_OPEN``.
"""
return self._request("GET", f"/v1/profiles/{_segment(profile_id)}/agent/dialog")
def agent_handle_dialog(
self,
profile_id: str,
*,
accept: bool,
prompt_text: Optional[str] = None,
) -> AgentDialogAnswer:
"""POST /v1/profiles/{id}/agent/handle-dialog
``accept`` true is OK, false is Cancel. An accepted prompt takes
``prompt_text``, or keeps its default text. Raises ``NO_DIALOG_OPEN``
when nothing is open.
"""
return self._request(
"POST",
f"/v1/profiles/{_segment(profile_id)}/agent/handle-dialog",
body=_body(accept=accept, prompt_text=prompt_text),
)
# ------------------------------------------------------------------
# Remote sessions
# ------------------------------------------------------------------
+3
View File
@@ -69,7 +69,10 @@ OPERATIONS: Dict[Operation, str] = {
("GET", "/v1/profiles/{id}/fingerprint"): "get_profile_fingerprint",
("PUT", "/v1/profiles/{id}"): "update_profile",
("POST", "/v1/profiles/{id}/agent/click"): "agent_click",
("GET", "/v1/profiles/{id}/agent/dialog"): "agent_dialog",
("POST", "/v1/profiles/{id}/agent/extract"): "agent_extract",
("POST", "/v1/profiles/{id}/agent/handle-dialog"): "agent_handle_dialog",
("POST", "/v1/profiles/{id}/agent/hold"): "agent_hold",
("POST", "/v1/profiles/{id}/agent/perceive"): "agent_perceive",
("POST", "/v1/profiles/{id}/agent/pick"): "agent_pick",
("POST", "/v1/profiles/{id}/agent/resolve-locator"): "agent_resolve_locator",
+67 -2
View File
@@ -9,8 +9,8 @@ Two spellings live side by side because the app sends both. Most bodies are
snake_case; the browser-facing agent types (``LocatorDescription``,
``LocatorCandidate``, ``PerceptionPage`` and friends) carry the browser's own
camelCase, because they are handed through from the browser rather than
restated. ``AgentClick`` and ``AgentTyping`` are the exceptions inside the
agent surface: they are snake_case with a single ``match`` key. The types below
restated. ``AgentClick``, ``AgentHold`` and ``AgentTyping`` are the exceptions
inside the agent surface: they are snake_case with a single ``match`` key. The types below
follow the wire rather than tidying it, so a value read from one call can be
passed straight into the next.
"""
@@ -94,7 +94,11 @@ __all__ = [
"Extraction",
"PickedElement",
"AgentClick",
"AgentHold",
"AgentTyping",
"OpenDialog",
"PageDialog",
"AgentDialogAnswer",
]
# The app's own JSON for a proxy's settings. Declared `Object` in the OpenAPI
@@ -931,3 +935,64 @@ class AgentTyping(_AgentTypingRequired, total=False):
"""
corrections: int
class _AgentHoldRequired(TypedDict):
held: bool
engine: str
navigated: bool
button: str
held_ms: int
class AgentHold(_AgentHoldRequired, total=False):
"""What a press and hold did. Note the snake_case body and the ``match`` key.
``match`` is the element a locator settled on, absent when a selector named
it. ``held_ms`` is how long the button was down.
"""
match: LocatorCandidate
class OpenDialog(TypedDict):
"""A native dialog (alert, confirm, prompt, "leave this page?") on a page.
``type`` is ``alert``, ``confirm``, ``prompt`` or ``beforeunload``;
``default_prompt`` is the text a prompt puts in its field; ``url`` is the
page that opened it.
"""
type: str
message: str
default_prompt: str
url: str
class _PageDialogRequired(TypedDict):
open: Optional[bool]
class PageDialog(_PageDialogRequired, total=False):
"""Whether a native dialog holds the page.
``open`` is true while a dialog holds the page, false when none does, and
``None`` when the page answers nothing and no dialog was seen opening on
it; ``page_responding`` is then false. A navigation ends that.
"""
dialog: OpenDialog
page_responding: bool
note: str
class _AgentDialogAnswerRequired(TypedDict):
accepted: bool
dialog: OpenDialog
class AgentDialogAnswer(_AgentDialogAnswerRequired, total=False):
"""What answering a dialog did. ``next`` is a dialog the page opened in
answer, which holds the page now."""
next: OpenDialog
+30
View File
@@ -365,6 +365,36 @@ CASES: List[Case] = [
{},
"/v1/profiles/{id}/agent/pick",
),
(
"agent_hold",
("p1",),
{"selector": "#press", "duration_ms": 4000.0},
"POST",
"/v1/profiles/p1/agent/hold",
{"selector": "#press", "duration_ms": 4000.0},
{},
"/v1/profiles/{id}/agent/hold",
),
(
"agent_dialog",
("p1",),
{},
"GET",
"/v1/profiles/p1/agent/dialog",
None,
{},
"/v1/profiles/{id}/agent/dialog",
),
(
"agent_handle_dialog",
("p1",),
{"accept": True, "prompt_text": "Bob"},
"POST",
"/v1/profiles/p1/agent/handle-dialog",
{"accept": True, "prompt_text": "Bob"},
{},
"/v1/profiles/{id}/agent/handle-dialog",
),
# -- remote sessions ---------------------------------------------------
(
"list_remote_sessions",