Skip to content

Commit 9ebc4b3

Browse files
authored
Merge pull request #196 from SentienceAPI/scaffold_captcha
Pagecontrol hook for evaluateJs
2 parents fd303c1 + 1a95ab5 commit 9ebc4b3

File tree

3 files changed

+80
-2
lines changed

3 files changed

+80
-2
lines changed

sentience/agent_runtime.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,13 @@
7070
from dataclasses import dataclass
7171
from typing import TYPE_CHECKING, Any
7272

73-
from .captcha import CaptchaContext, CaptchaHandlingError, CaptchaOptions, CaptchaResolution
73+
from .captcha import (
74+
CaptchaContext,
75+
CaptchaHandlingError,
76+
CaptchaOptions,
77+
CaptchaResolution,
78+
PageControlHook,
79+
)
7480
from .failure_artifacts import FailureArtifactBuffer, FailureArtifactsOptions
7581
from .models import (
7682
EvaluateJsRequest,
@@ -479,8 +485,18 @@ def _build_captcha_context(self, snapshot: Snapshot, source: str) -> CaptchaCont
479485
url=snapshot.url,
480486
source=source, # type: ignore[arg-type]
481487
captcha=captcha,
488+
page_control=self._create_captcha_page_control(),
482489
)
483490

491+
def _create_captcha_page_control(self) -> PageControlHook:
492+
async def _eval(code: str) -> Any:
493+
result = await self.evaluate_js(EvaluateJsRequest(code=code))
494+
if not result.ok:
495+
raise RuntimeError(result.error or "evaluate_js failed")
496+
return result.value
497+
498+
return PageControlHook(evaluate_js=_eval)
499+
484500
def _emit_captcha_event(self, reason_code: str, details: dict[str, Any] | None = None) -> None:
485501
payload = {
486502
"kind": "captcha",

sentience/captcha.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from collections.abc import Awaitable, Callable
44
from dataclasses import dataclass
5-
from typing import Literal, Optional
5+
from typing import Any, Literal, Optional
66

77
from .models import CaptchaDiagnostics
88

@@ -11,6 +11,11 @@
1111
CaptchaSource = Literal["extension", "gateway", "runtime"]
1212

1313

14+
@dataclass
15+
class PageControlHook:
16+
evaluate_js: Callable[[str], Awaitable[Any]]
17+
18+
1419
@dataclass
1520
class CaptchaContext:
1621
run_id: str
@@ -23,6 +28,7 @@ class CaptchaContext:
2328
snapshot_path: str | None = None
2429
live_session_url: str | None = None
2530
meta: dict[str, str] | None = None
31+
page_control: PageControlHook | None = None
2632

2733

2834
@dataclass
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
from __future__ import annotations
2+
3+
import pytest
4+
5+
from sentience.agent_runtime import AgentRuntime
6+
from sentience.captcha import PageControlHook
7+
from sentience.models import CaptchaDiagnostics, CaptchaEvidence, Snapshot, SnapshotDiagnostics
8+
9+
10+
class EvalBackend:
11+
async def eval(self, code: str):
12+
_ = code
13+
return "ok"
14+
15+
16+
class MockTracer:
17+
def __init__(self) -> None:
18+
self.events: list[dict] = []
19+
self.run_id = "test-run"
20+
21+
def emit(self, event_type: str, data: dict, step_id: str | None = None) -> None:
22+
self.events.append({"type": event_type, "data": data, "step_id": step_id})
23+
24+
25+
def make_captcha_snapshot() -> Snapshot:
26+
evidence = CaptchaEvidence(
27+
iframe_src_hits=["https://www.google.com/recaptcha/api2/anchor"],
28+
text_hits=["captcha"],
29+
selector_hits=[],
30+
url_hits=[],
31+
)
32+
captcha = CaptchaDiagnostics(
33+
detected=True,
34+
provider_hint="recaptcha",
35+
confidence=0.9,
36+
evidence=evidence,
37+
)
38+
diagnostics = SnapshotDiagnostics(captcha=captcha)
39+
return Snapshot(
40+
status="success",
41+
url="https://example.com",
42+
elements=[],
43+
diagnostics=diagnostics,
44+
)
45+
46+
47+
@pytest.mark.asyncio
48+
async def test_captcha_context_page_control_evaluate_js() -> None:
49+
runtime = AgentRuntime(backend=EvalBackend(), tracer=MockTracer())
50+
runtime.begin_step("captcha_test")
51+
52+
ctx = runtime._build_captcha_context(make_captcha_snapshot(), source="gateway")
53+
assert isinstance(ctx.page_control, PageControlHook)
54+
55+
result = await ctx.page_control.evaluate_js("1+1")
56+
assert result == "ok"

0 commit comments

Comments
 (0)