anchorbrowser 0.1.0a1__py3-none-any.whl → 0.1.0a2__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.
anchorbrowser/_client.py CHANGED
@@ -21,7 +21,7 @@ from ._types import (
21
21
  )
22
22
  from ._utils import is_given, get_async_library
23
23
  from ._version import __version__
24
- from .resources import tools, profiles, extensions
24
+ from .resources import agent, tools, browser, profiles, extensions
25
25
  from ._streaming import Stream as Stream, AsyncStream as AsyncStream
26
26
  from ._exceptions import APIStatusError, AnchorbrowserError
27
27
  from ._base_client import (
@@ -48,6 +48,8 @@ class Anchorbrowser(SyncAPIClient):
48
48
  sessions: sessions.SessionsResource
49
49
  tools: tools.ToolsResource
50
50
  extensions: extensions.ExtensionsResource
51
+ browser: browser.BrowserResource
52
+ agent: agent.AgentResource
51
53
  with_raw_response: AnchorbrowserWithRawResponse
52
54
  with_streaming_response: AnchorbrowserWithStreamedResponse
53
55
 
@@ -109,6 +111,8 @@ class Anchorbrowser(SyncAPIClient):
109
111
  self.sessions = sessions.SessionsResource(self)
110
112
  self.tools = tools.ToolsResource(self)
111
113
  self.extensions = extensions.ExtensionsResource(self)
114
+ self.browser = browser.BrowserResource(self)
115
+ self.agent = agent.AgentResource(self)
112
116
  self.with_raw_response = AnchorbrowserWithRawResponse(self)
113
117
  self.with_streaming_response = AnchorbrowserWithStreamedResponse(self)
114
118
 
@@ -222,6 +226,8 @@ class AsyncAnchorbrowser(AsyncAPIClient):
222
226
  sessions: sessions.AsyncSessionsResource
223
227
  tools: tools.AsyncToolsResource
224
228
  extensions: extensions.AsyncExtensionsResource
229
+ browser: browser.AsyncBrowserResource
230
+ agent: agent.AsyncAgentResource
225
231
  with_raw_response: AsyncAnchorbrowserWithRawResponse
226
232
  with_streaming_response: AsyncAnchorbrowserWithStreamedResponse
227
233
 
@@ -283,6 +289,8 @@ class AsyncAnchorbrowser(AsyncAPIClient):
283
289
  self.sessions = sessions.AsyncSessionsResource(self)
284
290
  self.tools = tools.AsyncToolsResource(self)
285
291
  self.extensions = extensions.AsyncExtensionsResource(self)
292
+ self.browser = browser.AsyncBrowserResource(self)
293
+ self.agent = agent.AsyncAgentResource(self)
286
294
  self.with_raw_response = AsyncAnchorbrowserWithRawResponse(self)
287
295
  self.with_streaming_response = AsyncAnchorbrowserWithStreamedResponse(self)
288
296
 
@@ -397,7 +405,8 @@ class AnchorbrowserWithRawResponse:
397
405
  self.sessions = sessions.SessionsResourceWithRawResponse(client.sessions)
398
406
  self.tools = tools.ToolsResourceWithRawResponse(client.tools)
399
407
  self.extensions = extensions.ExtensionsResourceWithRawResponse(client.extensions)
400
-
408
+ self.browser = browser.BrowserResourceWithRawResponse(client.browser)
409
+ self.agent = agent.AgentResourceWithRawResponse(client.agent)
401
410
 
402
411
  class AsyncAnchorbrowserWithRawResponse:
403
412
  def __init__(self, client: AsyncAnchorbrowser) -> None:
@@ -405,7 +414,8 @@ class AsyncAnchorbrowserWithRawResponse:
405
414
  self.sessions = sessions.AsyncSessionsResourceWithRawResponse(client.sessions)
406
415
  self.tools = tools.AsyncToolsResourceWithRawResponse(client.tools)
407
416
  self.extensions = extensions.AsyncExtensionsResourceWithRawResponse(client.extensions)
408
-
417
+ self.browser = browser.AsyncBrowserResourceWithRawResponse(client.browser)
418
+ self.agent = agent.AsyncAgentResourceWithRawResponse(client.agent)
409
419
 
410
420
  class AnchorbrowserWithStreamedResponse:
411
421
  def __init__(self, client: Anchorbrowser) -> None:
@@ -413,7 +423,8 @@ class AnchorbrowserWithStreamedResponse:
413
423
  self.sessions = sessions.SessionsResourceWithStreamingResponse(client.sessions)
414
424
  self.tools = tools.ToolsResourceWithStreamingResponse(client.tools)
415
425
  self.extensions = extensions.ExtensionsResourceWithStreamingResponse(client.extensions)
416
-
426
+ self.browser = browser.BrowserResourceWithStreamingResponse(client.browser)
427
+ self.agent = agent.AgentResourceWithStreamingResponse(client.agent)
417
428
 
418
429
  class AsyncAnchorbrowserWithStreamedResponse:
419
430
  def __init__(self, client: AsyncAnchorbrowser) -> None:
@@ -421,7 +432,8 @@ class AsyncAnchorbrowserWithStreamedResponse:
421
432
  self.sessions = sessions.AsyncSessionsResourceWithStreamingResponse(client.sessions)
422
433
  self.tools = tools.AsyncToolsResourceWithStreamingResponse(client.tools)
423
434
  self.extensions = extensions.AsyncExtensionsResourceWithStreamingResponse(client.extensions)
424
-
435
+ self.browser = browser.AsyncBrowserResourceWithStreamingResponse(client.browser)
436
+ self.agent = agent.AsyncAgentResourceWithStreamingResponse(client.agent)
425
437
 
426
438
  Client = Anchorbrowser
427
439
 
anchorbrowser/_models.py CHANGED
@@ -208,14 +208,18 @@ class BaseModel(pydantic.BaseModel):
208
208
  else:
209
209
  fields_values[name] = field_get_default(field)
210
210
 
211
+ extra_field_type = _get_extra_fields_type(__cls)
212
+
211
213
  _extra = {}
212
214
  for key, value in values.items():
213
215
  if key not in model_fields:
216
+ parsed = construct_type(value=value, type_=extra_field_type) if extra_field_type is not None else value
217
+
214
218
  if PYDANTIC_V2:
215
- _extra[key] = value
219
+ _extra[key] = parsed
216
220
  else:
217
221
  _fields_set.add(key)
218
- fields_values[key] = value
222
+ fields_values[key] = parsed
219
223
 
220
224
  object.__setattr__(m, "__dict__", fields_values)
221
225
 
@@ -370,6 +374,23 @@ def _construct_field(value: object, field: FieldInfo, key: str) -> object:
370
374
  return construct_type(value=value, type_=type_, metadata=getattr(field, "metadata", None))
371
375
 
372
376
 
377
+ def _get_extra_fields_type(cls: type[pydantic.BaseModel]) -> type | None:
378
+ if not PYDANTIC_V2:
379
+ # TODO
380
+ return None
381
+
382
+ schema = cls.__pydantic_core_schema__
383
+ if schema["type"] == "model":
384
+ fields = schema["schema"]
385
+ if fields["type"] == "model-fields":
386
+ extras = fields.get("extras_schema")
387
+ if extras and "cls" in extras:
388
+ # mypy can't narrow the type
389
+ return extras["cls"] # type: ignore[no-any-return]
390
+
391
+ return None
392
+
393
+
373
394
  def is_basemodel(type_: type) -> bool:
374
395
  """Returns whether or not the given type is either a `BaseModel` or a union of `BaseModel`"""
375
396
  if is_union(type_):
anchorbrowser/_version.py CHANGED
@@ -1,4 +1,4 @@
1
1
  # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2
2
 
3
3
  __title__ = "anchorbrowser"
4
- __version__ = "0.1.0-alpha.1" # x-release-please-version
4
+ __version__ = "0.1.0-alpha.2" # x-release-please-version
@@ -0,0 +1,69 @@
1
+ import json
2
+ import asyncio
3
+ import threading
4
+ from typing import Any, Dict, Callable, Optional, TypedDict
5
+ from asyncio import Future
6
+
7
+ from ..lib.browser import BrowserSetup, get_agent_ws_url
8
+
9
+
10
+ class AgentTaskParams(TypedDict, total=False):
11
+ url: Optional[str]
12
+ output_schema: Optional[Dict[str, Any]]
13
+ on_agent_step: Optional[Callable[[str], None]]
14
+
15
+
16
+ def create_task_payload(prompt: str, output_schema: Optional[Dict[str, Any]] = None) -> str:
17
+ if not prompt or prompt.strip() == "":
18
+ raise ValueError("Prompt cannot be empty")
19
+
20
+ return json.dumps(
21
+ {
22
+ "prompt": prompt,
23
+ "output_schema": output_schema,
24
+ }
25
+ )
26
+
27
+
28
+ def on_agent_step_sync(on_agent_step: Callable[[str], None], browser_setup: BrowserSetup) -> Future[None]:
29
+ import websockets
30
+
31
+ async def websocket_listener() -> None:
32
+ ws_url = get_agent_ws_url(browser_setup.base_url, browser_setup.session_id)
33
+ try:
34
+ async with websockets.connect(ws_url) as ws:
35
+ async for ws_msg in ws:
36
+ on_agent_step(str(ws_msg))
37
+ except Exception as e:
38
+ future.set_exception(e)
39
+
40
+ def run_in_thread() -> None:
41
+ loop = asyncio.new_event_loop()
42
+ asyncio.set_event_loop(loop)
43
+ try:
44
+ loop.run_until_complete(websocket_listener())
45
+ finally:
46
+ loop.close()
47
+
48
+ # Create a future to track the task
49
+ future = Future[None]()
50
+ try:
51
+ thread = threading.Thread(target=lambda: future.set_result(run_in_thread()))
52
+ thread.daemon = True
53
+ thread.start()
54
+ except Exception:
55
+ pass
56
+
57
+ return future
58
+
59
+
60
+ def on_agent_step_async(on_agent_step: Callable[[str], None], browser_setup: BrowserSetup) -> None:
61
+ import websockets
62
+
63
+ async def websocket_listener() -> None:
64
+ ws_url = get_agent_ws_url(browser_setup.base_url, browser_setup.session_id)
65
+ async with websockets.connect(ws_url) as ws:
66
+ async for ws_msg in ws:
67
+ on_agent_step(str(ws_msg))
68
+
69
+ asyncio.create_task(websocket_listener())
@@ -0,0 +1,186 @@
1
+ from typing import TYPE_CHECKING, Any, Dict, Callable, Optional, TypedDict
2
+ from contextlib import contextmanager, asynccontextmanager, _GeneratorContextManager, _AsyncGeneratorContextManager
3
+
4
+ from pydantic import BaseModel
5
+ from playwright.sync_api import Page, Worker, Browser, BrowserContext
6
+ from playwright.async_api import (
7
+ Page as AsyncPage,
8
+ Worker as AsyncWorker,
9
+ Browser as AsyncBrowser,
10
+ BrowserContext as AsyncBrowserContext,
11
+ )
12
+
13
+ if TYPE_CHECKING:
14
+ from collections import Generator, AsyncGenerator
15
+
16
+
17
+ @contextmanager
18
+ def get_playwright_chromium_from_cdp_url(
19
+ api_base_url: str, session_id: str, api_key: str
20
+ ) -> "Generator[Browser, Any, None]":
21
+ from playwright.sync_api import sync_playwright
22
+
23
+ browser = None
24
+ playwright = sync_playwright().start()
25
+ try:
26
+ browser = playwright.chromium.connect_over_cdp(get_cdp_url(api_base_url, session_id, api_key))
27
+ yield browser
28
+ finally:
29
+ if browser:
30
+ browser.close()
31
+ playwright.stop()
32
+
33
+
34
+ @asynccontextmanager
35
+ async def get_async_playwright_chromium_from_cdp_url(
36
+ api_base_url: str, session_id: str, api_key: str
37
+ ) -> "AsyncGenerator[AsyncBrowser, None]":
38
+ from playwright.async_api import async_playwright
39
+
40
+ browser = None
41
+ playwright = await async_playwright().start()
42
+ try:
43
+ browser = await playwright.chromium.connect_over_cdp(get_cdp_url(api_base_url, session_id, api_key))
44
+ yield browser
45
+ finally:
46
+ if browser:
47
+ await browser.close()
48
+ await playwright.stop()
49
+
50
+
51
+ def get_cdp_url(api_base_url: str, session_id: str, api_key: str) -> str:
52
+ return f"{api_base_url.replace('https://', 'wss://').replace('api.', 'connect.')}?apiKey={api_key}&sessionId={session_id}"
53
+
54
+
55
+ def get_agent_ws_url(api_base_url: str, session_id: str) -> str:
56
+ return f"{api_base_url.replace('https://', 'wss://').replace('api.', 'connect.')}/ws?sessionId={session_id}"
57
+
58
+
59
+ def get_ai_service_worker(browser_context: "BrowserContext") -> Optional["Worker"]:
60
+ return next(
61
+ (
62
+ sw
63
+ for sw in browser_context.service_workers
64
+ if "chrome-extension://bppehibnhionalpjigdjdilknbljaeai/background.js" in sw.url
65
+ ),
66
+ None,
67
+ )
68
+
69
+
70
+ async def get_ai_service_worker_async(browser_context: "AsyncBrowserContext") -> Optional["AsyncWorker"]:
71
+ return next(
72
+ (
73
+ sw
74
+ for sw in browser_context.service_workers
75
+ if "chrome-extension://bppehibnhionalpjigdjdilknbljaeai/background.js" in sw.url
76
+ ),
77
+ None,
78
+ )
79
+
80
+
81
+ class BrowserSetup(BaseModel):
82
+ session_id: str
83
+ base_url: str
84
+ api_key: str
85
+ _browser: Optional[Browser] = None
86
+ _async_browser: Optional[AsyncBrowser] = None
87
+ _context_manager: Optional[_GeneratorContextManager[Browser]] = None
88
+ _async_context_manager: Optional[_AsyncGeneratorContextManager[AsyncBrowser]] = None
89
+
90
+ async def __aenter__(self) -> "BrowserSetup":
91
+ self._async_context_manager = get_async_playwright_chromium_from_cdp_url(
92
+ self.base_url,
93
+ self.session_id,
94
+ self.api_key,
95
+ )
96
+ self._async_browser = await self._async_context_manager.__aenter__()
97
+ return self
98
+
99
+ def __enter__(self) -> "BrowserSetup":
100
+ self._context_manager = get_playwright_chromium_from_cdp_url(
101
+ self.base_url,
102
+ self.session_id,
103
+ self.api_key,
104
+ )
105
+ self._browser = self._context_manager.__enter__()
106
+ return self
107
+
108
+ def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> Optional[bool]:
109
+ if self._context_manager:
110
+ return self._context_manager.__exit__(exc_type, exc_val, exc_tb)
111
+ return None
112
+
113
+ async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> Optional[bool]:
114
+ if self._async_context_manager:
115
+ return await self._async_context_manager.__aexit__(exc_type, exc_val, exc_tb)
116
+ return None
117
+
118
+ @property
119
+ def browser_generator(self) -> _GeneratorContextManager[Browser]:
120
+ return get_playwright_chromium_from_cdp_url(
121
+ self.base_url,
122
+ self.session_id,
123
+ self.api_key,
124
+ )
125
+
126
+ @property
127
+ def async_browser_generator(self) -> _AsyncGeneratorContextManager[AsyncBrowser]:
128
+ return get_async_playwright_chromium_from_cdp_url(
129
+ self.base_url,
130
+ self.session_id,
131
+ self.api_key,
132
+ )
133
+
134
+ @property
135
+ def browser(self) -> Browser:
136
+ if self._browser is None:
137
+ raise RuntimeError("BrowserSetup must be used as a context manager")
138
+ return self._browser
139
+
140
+ @property
141
+ async def async_browser(self) -> AsyncBrowser:
142
+ if self._async_browser is None:
143
+ raise RuntimeError("BrowserSetup must be used as a context manager")
144
+ return self._async_browser
145
+
146
+ @property
147
+ def context(self) -> BrowserContext:
148
+ return self.browser.contexts[0]
149
+
150
+ @property
151
+ async def async_context(self) -> AsyncBrowserContext:
152
+ return (await self.async_browser).contexts[0]
153
+
154
+ @property
155
+ def page(self) -> Page:
156
+ return self.context.pages[0]
157
+
158
+ @property
159
+ async def async_page(self) -> AsyncPage:
160
+ return (await self.async_context).pages[0]
161
+
162
+ @property
163
+ def ai(self) -> Worker:
164
+ ai_service_worker = get_ai_service_worker(self.context)
165
+ if not ai_service_worker:
166
+ raise ValueError("AI service worker not found")
167
+ return ai_service_worker
168
+
169
+ @property
170
+ async def async_ai(self) -> AsyncWorker:
171
+ ai_service_worker = await get_ai_service_worker_async(await self.async_context)
172
+ if not ai_service_worker:
173
+ raise ValueError("AI service worker not found")
174
+ return ai_service_worker
175
+
176
+
177
+ class AgentTaskParams(TypedDict, total=False):
178
+ url: Optional[str]
179
+ output_schema: Optional[Dict[str, Any]]
180
+ on_agent_step: Optional[Callable[[str], None]]
181
+
182
+
183
+ class BrowserTaskResponse(TypedDict):
184
+ session_id: str
185
+ task_result_task: Any
186
+ playwright_browser: Any
@@ -0,0 +1,305 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Optional
4
+
5
+ from .._compat import cached_property
6
+ from .._resource import SyncAPIResource, AsyncAPIResource
7
+ from .._response import (
8
+ to_raw_response_wrapper,
9
+ to_streamed_response_wrapper,
10
+ async_to_raw_response_wrapper,
11
+ async_to_streamed_response_wrapper,
12
+ )
13
+ from ..lib.agent import on_agent_step_sync, create_task_payload, on_agent_step_async
14
+ from ..lib.browser import (
15
+ BrowserSetup,
16
+ AgentTaskParams,
17
+ BrowserTaskResponse,
18
+ )
19
+ from ..types.session_create_params import Session
20
+
21
+ __all__ = ["AgentResource", "AsyncAgentResource"]
22
+
23
+
24
+ class AgentResource(SyncAPIResource):
25
+ @cached_property
26
+ def with_raw_response(self) -> AgentResourceWithRawResponse:
27
+ """
28
+ This property can be used as a prefix for any HTTP method call to return
29
+ the raw response object instead of the parsed content.
30
+
31
+ For more information, see https://www.github.com/anchorbrowser/AnchorBrowser-SDK-Python#accessing-raw-response-data-eg-headers
32
+ """
33
+ return AgentResourceWithRawResponse(self)
34
+
35
+ @cached_property
36
+ def with_streaming_response(self) -> AgentResourceWithStreamingResponse:
37
+ """
38
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
39
+
40
+ For more information, see https://www.github.com/anchorbrowser/AnchorBrowser-SDK-Python#with_streaming_response
41
+ """
42
+ return AgentResourceWithStreamingResponse(self)
43
+
44
+ def task(
45
+ self,
46
+ prompt: str,
47
+ *,
48
+ session_options: Optional[Session] = None,
49
+ task_options: Optional[AgentTaskParams] = None,
50
+ ) -> str:
51
+ """Execute an AI agent task within a browser session.
52
+
53
+ Creates a new browser session and executes the given prompt as an AI agent task.
54
+ The agent can optionally navigate to a specific URL and use a structured output schema.
55
+
56
+ Args:
57
+ prompt (str): The task prompt/instruction for the AI agent to execute.
58
+ session_options (Optional[Session], optional): Configuration options for the
59
+ browser session. Defaults to None, which creates a session with default settings.
60
+ task_options (Optional[AgentTaskParams], optional): Additional task configuration
61
+
62
+ Returns:
63
+ str: The result of the AI agent task execution.
64
+ """
65
+ session = self._client.sessions.create(session=session_options or {})
66
+ if not session.data or not session.data.id:
67
+ raise ValueError("Failed to create session: No session ID returned")
68
+
69
+ with BrowserSetup(
70
+ session_id=session.data.id,
71
+ base_url=str(self._client.base_url),
72
+ api_key=self._client.api_key,
73
+ ) as browser_setup:
74
+ output_schema = None
75
+ if task_options:
76
+ output_schema = task_options.get("output_schema")
77
+ url = task_options.get("url")
78
+ if url:
79
+ browser_setup.page.goto(url)
80
+ on_agent_step = task_options.get("on_agent_step")
81
+ if on_agent_step:
82
+ on_agent_step_sync(on_agent_step, browser_setup)
83
+ task_payload = create_task_payload(prompt, output_schema)
84
+ task_result = str(browser_setup.ai.evaluate(task_payload))
85
+ return task_result
86
+
87
+ def browser_task(
88
+ self,
89
+ prompt: str,
90
+ *,
91
+ session_options: Optional[Session] = None,
92
+ task_options: Optional[AgentTaskParams] = None,
93
+ ) -> BrowserTaskResponse:
94
+ """Execute an AI agent task and return a browser task response with session control.
95
+
96
+ Creates a new browser session, executes the given prompt as an AI agent task \n
97
+ returns a object that includes the session ID, task result, and browser instance for continued interaction by the caller. \n
98
+ This method differs from `task()` by returning control of the browser session to the caller rather than automatically closing it after task completion. \n
99
+ Args:
100
+ prompt (str): The task prompt/instruction for the AI agent to execute. \n
101
+ session_options (Optional[Session], optional): Configuration options for the browser session. Defaults to None, which creates a session with default settings. \n
102
+ task_options (Optional[AgentTaskParams], optional): Additional task configuration including: \n
103
+ - output_schema: Schema for structured output formatting
104
+ - url: URL to navigate to before executing the task
105
+ - on_agent_step: Callback function for agent step events
106
+ Defaults to None.
107
+
108
+ Returns:
109
+ Response object containing:
110
+ - session_id: The ID of the created browser session
111
+ - task_result_task: The result of the AI agent task execution
112
+ - playwright_browser: Browser instance for continued interaction
113
+ """
114
+ session = self._client.sessions.create(session=session_options or {})
115
+ if not session.data or not session.data.id:
116
+ raise ValueError("Failed to create session: No session ID returned")
117
+
118
+ with BrowserSetup(
119
+ session_id=session.data.id,
120
+ base_url=str(self._client.base_url),
121
+ api_key=self._client.api_key,
122
+ ) as browser_setup:
123
+ output_schema = None
124
+ if task_options:
125
+ output_schema = task_options.get("output_schema")
126
+ url = task_options.get("url")
127
+ if url:
128
+ browser_setup.page.goto(url)
129
+ on_agent_step = task_options.get("on_agent_step")
130
+ if on_agent_step:
131
+ on_agent_step_sync(on_agent_step, browser_setup)
132
+ task_payload = create_task_payload(prompt, output_schema)
133
+ task_result = str(browser_setup.ai.evaluate(task_payload))
134
+ return BrowserTaskResponse(
135
+ session_id=session.data.id,
136
+ task_result_task=task_result,
137
+ playwright_browser=browser_setup.browser_generator,
138
+ )
139
+
140
+
141
+ class AsyncAgentResource(AsyncAPIResource):
142
+ @cached_property
143
+ def with_raw_response(self) -> AsyncAgentResourceWithRawResponse:
144
+ """
145
+ This property can be used as a prefix for any HTTP method call to return
146
+ the raw response object instead of the parsed content.
147
+
148
+ For more information, see https://www.github.com/anchorbrowser/AnchorBrowser-SDK-Python#accessing-raw-response-data-eg-headers
149
+ """
150
+ return AsyncAgentResourceWithRawResponse(self)
151
+
152
+ @cached_property
153
+ def with_streaming_response(self) -> AsyncAgentResourceWithStreamingResponse:
154
+ """
155
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
156
+
157
+ For more information, see https://www.github.com/anchorbrowser/AnchorBrowser-SDK-Python#with_streaming_response
158
+ """
159
+ return AsyncAgentResourceWithStreamingResponse(self)
160
+
161
+ async def task(
162
+ self,
163
+ prompt: str,
164
+ *,
165
+ session_options: Optional[Session] = None,
166
+ task_options: Optional[AgentTaskParams] = None,
167
+ ) -> str:
168
+ """Execute an AI agent task within a browser session.
169
+
170
+ Creates a new browser session and executes the given prompt as an AI agent task.
171
+ The agent can optionally navigate to a specific URL and use a structured output schema.
172
+
173
+ Args:
174
+ prompt (str): The task prompt/instruction for the AI agent to execute.
175
+ session_options (Optional[Session], optional): Configuration options for the
176
+ browser session. Defaults to None, which creates a session with default settings.
177
+ task_options (Optional[AgentTaskParams], optional): Additional task configuration
178
+
179
+ Returns:
180
+ str: The result of the AI agent task execution.
181
+ """
182
+ session = await self._client.sessions.create(session=session_options or {})
183
+ if not session.data or not session.data.id:
184
+ raise ValueError("Failed to create session: No session ID returned")
185
+
186
+ browser_setup = BrowserSetup(
187
+ session_id=session.data.id,
188
+ base_url=str(self._client.base_url),
189
+ api_key=self._client.api_key,
190
+ )
191
+
192
+ async with browser_setup:
193
+ output_schema = None
194
+ if task_options:
195
+ output_schema = task_options.get("output_schema")
196
+ url = task_options.get("url")
197
+ if url:
198
+ await (await browser_setup.async_page).goto(url)
199
+ on_agent_step = task_options.get("on_agent_step")
200
+ if on_agent_step:
201
+ on_agent_step_async(on_agent_step, browser_setup)
202
+ task_payload = create_task_payload(prompt, output_schema)
203
+ task_result = await (await browser_setup.async_ai).evaluate(task_payload)
204
+ return str(task_result)
205
+
206
+ async def browser_task(
207
+ self,
208
+ prompt: str,
209
+ *,
210
+ session_options: Optional[Session] = None,
211
+ task_options: Optional[AgentTaskParams] = None,
212
+ ) -> BrowserTaskResponse:
213
+ """Execute an AI agent task and return a browser task response with session control.
214
+
215
+ Creates a new browser session, executes the given prompt as an AI agent task \n
216
+ returns a object that includes the session ID, task result, and browser instance for continued interaction by the caller. \n
217
+ This method differs from `task()` by returning control of the browser session to the caller rather than automatically closing it after task completion. \n
218
+ Args:
219
+ prompt (str): The task prompt/instruction for the AI agent to execute. \n
220
+ session_options (Optional[Session], optional): Configuration options for the browser session. Defaults to None, which creates a session with default settings. \n
221
+ task_options (Optional[AgentTaskParams], optional): Additional task configuration including: \n
222
+ - output_schema: Schema for structured output formatting
223
+ - url: URL to navigate to before executing the task
224
+ - on_agent_step: Callback function for agent step events
225
+ Defaults to None.
226
+
227
+ Returns:
228
+ Response object containing:
229
+ - session_id: The ID of the created browser session
230
+ - task_result_task: The result of the AI agent task execution
231
+ - playwright_browser: Browser instance for continued interaction
232
+ """
233
+ session = await self._client.sessions.create(session=session_options or {})
234
+ if not session.data or not session.data.id:
235
+ raise ValueError("Failed to create session: No session ID returned")
236
+
237
+ async with BrowserSetup(
238
+ session_id=session.data.id,
239
+ base_url=str(self._client.base_url),
240
+ api_key=self._client.api_key,
241
+ ) as browser_setup:
242
+ output_schema = None
243
+ if task_options:
244
+ output_schema = task_options.get("output_schema")
245
+ url = task_options.get("url")
246
+ if url:
247
+ await (await browser_setup.async_page).goto(url)
248
+ on_agent_step = task_options.get("on_agent_step")
249
+ if on_agent_step:
250
+ on_agent_step_async(on_agent_step, browser_setup)
251
+ task_payload = create_task_payload(prompt, output_schema)
252
+ task_result = await (await browser_setup.async_ai).evaluate(task_payload)
253
+ return BrowserTaskResponse(
254
+ session_id=session.data.id,
255
+ task_result_task=task_result,
256
+ playwright_browser=browser_setup.async_browser_generator,
257
+ )
258
+
259
+
260
+ class AgentResourceWithRawResponse:
261
+ def __init__(self, agent: AgentResource) -> None:
262
+ self._agent = agent
263
+
264
+ self.task = to_raw_response_wrapper(
265
+ agent.task,
266
+ )
267
+ self.browser_task = to_raw_response_wrapper(
268
+ agent.browser_task,
269
+ )
270
+
271
+
272
+ class AsyncAgentResourceWithRawResponse:
273
+ def __init__(self, agent: AsyncAgentResource) -> None:
274
+ self._agent = agent
275
+
276
+ self.task = async_to_raw_response_wrapper(
277
+ agent.task,
278
+ )
279
+ self.browser_task = async_to_raw_response_wrapper(
280
+ agent.browser_task,
281
+ )
282
+
283
+
284
+ class AgentResourceWithStreamingResponse:
285
+ def __init__(self, agent: AgentResource) -> None:
286
+ self._agent = agent
287
+
288
+ self.task = to_streamed_response_wrapper(
289
+ agent.task,
290
+ )
291
+ self.browser_task = to_streamed_response_wrapper(
292
+ agent.browser_task,
293
+ )
294
+
295
+
296
+ class AsyncAgentResourceWithStreamingResponse:
297
+ def __init__(self, agent: AsyncAgentResource) -> None:
298
+ self._agent = agent
299
+
300
+ self.task = async_to_streamed_response_wrapper(
301
+ agent.task,
302
+ )
303
+ self.browser_task = async_to_streamed_response_wrapper(
304
+ agent.browser_task,
305
+ )
@@ -0,0 +1,152 @@
1
+ from __future__ import annotations
2
+
3
+ from contextlib import _GeneratorContextManager, _AsyncGeneratorContextManager
4
+
5
+ from playwright.sync_api import Browser
6
+ from playwright.async_api import Browser as AsyncBrowser
7
+
8
+ from .._compat import cached_property
9
+ from .._resource import SyncAPIResource, AsyncAPIResource
10
+ from .._response import (
11
+ to_raw_response_wrapper,
12
+ to_streamed_response_wrapper,
13
+ async_to_raw_response_wrapper,
14
+ async_to_streamed_response_wrapper,
15
+ )
16
+ from ..lib.browser import (
17
+ get_playwright_chromium_from_cdp_url,
18
+ get_async_playwright_chromium_from_cdp_url,
19
+ )
20
+
21
+ __all__ = ["BrowserResource", "AsyncBrowserResource"]
22
+
23
+
24
+ class BrowserResource(SyncAPIResource):
25
+ @cached_property
26
+ def with_raw_response(self) -> BrowserResourceWithRawResponse:
27
+ """
28
+ This property can be used as a prefix for any HTTP method call to return
29
+ the raw response object instead of the parsed content.
30
+
31
+ For more information, see https://www.github.com/anchorbrowser/AnchorBrowser-SDK-Python#accessing-raw-response-data-eg-headers
32
+ """
33
+ return BrowserResourceWithRawResponse(self)
34
+
35
+ @cached_property
36
+ def with_streaming_response(self) -> BrowserResourceWithStreamingResponse:
37
+ """
38
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
39
+
40
+ For more information, see https://www.github.com/anchorbrowser/AnchorBrowser-SDK-Python#with_streaming_response
41
+ """
42
+ return BrowserResourceWithStreamingResponse(self)
43
+
44
+ def connect(self, session_id: str) -> _GeneratorContextManager[Browser]:
45
+ """Connect to a browser session.
46
+
47
+ Args:
48
+ session_id (str): The ID of the session to connect to.
49
+
50
+ Returns:
51
+ BrowserContext: a context manager that can be used to interact with the browser(playwright)
52
+ """
53
+ return get_playwright_chromium_from_cdp_url(str(self._client.base_url), session_id, self._client.api_key)
54
+
55
+ def create(self) -> _GeneratorContextManager[Browser]:
56
+ session = self._client.sessions.create()
57
+ if not session.data or not session.data.id:
58
+ raise ValueError("Failed to create session")
59
+ return get_playwright_chromium_from_cdp_url(str(self._client.base_url), session.data.id, self._client.api_key)
60
+
61
+
62
+ class AsyncBrowserResource(AsyncAPIResource):
63
+ @cached_property
64
+ def with_raw_response(self) -> AsyncBrowserResourceWithRawResponse:
65
+ """
66
+ This property can be used as a prefix for any HTTP method call to return
67
+ the raw response object instead of the parsed content.
68
+
69
+ For more information, see https://www.github.com/anchorbrowser/AnchorBrowser-SDK-Python#accessing-raw-response-data-eg-headers
70
+ """
71
+ return AsyncBrowserResourceWithRawResponse(self)
72
+
73
+ @cached_property
74
+ def with_streaming_response(self) -> AsyncBrowserResourceWithStreamingResponse:
75
+ """
76
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
77
+
78
+ For more information, see https://www.github.com/anchorbrowser/AnchorBrowser-SDK-Python#with_streaming_response
79
+ """
80
+ return AsyncBrowserResourceWithStreamingResponse(self)
81
+
82
+ async def connect(self, session_id: str) -> _AsyncGeneratorContextManager[AsyncBrowser]:
83
+ """Connect to a browser session.
84
+
85
+ Args:
86
+ session_id (str): The ID of the session to connect to.
87
+
88
+ Returns:
89
+ BrowserContext: a context manager that can be used to interact with the browser(playwright)
90
+ """
91
+ return get_async_playwright_chromium_from_cdp_url(str(self._client.base_url), session_id, self._client.api_key)
92
+
93
+ async def create(self) -> _AsyncGeneratorContextManager[AsyncBrowser]:
94
+ """Create a new browser session.
95
+
96
+ Returns:
97
+ BrowserContext: a context manager that can be used to interact with the browser(playwright)
98
+ """
99
+ session = await self._client.sessions.create()
100
+ if not session.data or not session.data.id:
101
+ raise ValueError("Failed to create session")
102
+ return get_async_playwright_chromium_from_cdp_url(
103
+ str(self._client.base_url), session.data.id, self._client.api_key
104
+ )
105
+
106
+
107
+ class BrowserResourceWithRawResponse:
108
+ def __init__(self, browser: BrowserResource) -> None:
109
+ self._browser = browser
110
+
111
+ self.connect = to_raw_response_wrapper(
112
+ browser.connect,
113
+ )
114
+ self.create = to_raw_response_wrapper(
115
+ browser.create,
116
+ )
117
+
118
+
119
+ class AsyncBrowserResourceWithRawResponse:
120
+ def __init__(self, browser: AsyncBrowserResource) -> None:
121
+ self._browser = browser
122
+
123
+ self.connect = async_to_raw_response_wrapper(
124
+ browser.connect,
125
+ )
126
+ self.create = async_to_raw_response_wrapper(
127
+ browser.create,
128
+ )
129
+
130
+
131
+ class BrowserResourceWithStreamingResponse:
132
+ def __init__(self, browser: BrowserResource) -> None:
133
+ self._browser = browser
134
+
135
+ self.connect = to_streamed_response_wrapper(
136
+ browser.connect,
137
+ )
138
+ self.create = to_streamed_response_wrapper(
139
+ browser.create,
140
+ )
141
+
142
+
143
+ class AsyncBrowserResourceWithStreamingResponse:
144
+ def __init__(self, browser: AsyncBrowserResource) -> None:
145
+ self._browser = browser
146
+
147
+ self.connect = async_to_streamed_response_wrapper(
148
+ browser.connect,
149
+ )
150
+ self.create = async_to_streamed_response_wrapper(
151
+ browser.create,
152
+ )
@@ -1,6 +1,8 @@
1
1
  # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2
2
 
3
- from typing import TYPE_CHECKING, List, Optional
3
+ from typing import TYPE_CHECKING, Dict, List, Optional
4
+
5
+ from pydantic import Field as FieldInfo
4
6
 
5
7
  from .._models import BaseModel
6
8
 
@@ -18,6 +20,7 @@ class ExtensionManifest(BaseModel):
18
20
 
19
21
  version: Optional[str] = None
20
22
 
23
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
21
24
  if TYPE_CHECKING:
22
25
  # Stub to indicate that arbitrary properties are accepted.
23
26
  # To access properties that are not valid identifiers you can use `getattr`, e.g.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: anchorbrowser
3
- Version: 0.1.0a1
3
+ Version: 0.1.0a2
4
4
  Summary: The official Python library for the anchorbrowser API
5
5
  Project-URL: Homepage, https://github.com/anchorbrowser/AnchorBrowser-SDK-Python
6
6
  Project-URL: Repository, https://github.com/anchorbrowser/AnchorBrowser-SDK-Python
@@ -26,8 +26,10 @@ Requires-Dist: anyio<5,>=3.5.0
26
26
  Requires-Dist: distro<2,>=1.7.0
27
27
  Requires-Dist: httpx<1,>=0.23.0
28
28
  Requires-Dist: pydantic<3,>=1.9.0
29
+ Requires-Dist: pytest-playwright
29
30
  Requires-Dist: sniffio
30
31
  Requires-Dist: typing-extensions<5,>=4.10
32
+ Requires-Dist: websockets
31
33
  Provides-Extra: aiohttp
32
34
  Requires-Dist: aiohttp; extra == 'aiohttp'
33
35
  Requires-Dist: httpx-aiohttp>=0.1.8; extra == 'aiohttp'
@@ -1,17 +1,17 @@
1
1
  anchorbrowser/__init__.py,sha256=Y9SS6YKjFHc3NR74t9tzppSPjZ4U-zyYI8uPM5r17QU,2682
2
2
  anchorbrowser/_base_client.py,sha256=viDcyxFrFwusLXdK0lE6g9hRoB8fBvZcUOQpfSurARs,66929
3
- anchorbrowser/_client.py,sha256=S3WVCPjrYKZO7b73kGqNpzKZeDEIfngyVNSf8wMt5ZI,16915
3
+ anchorbrowser/_client.py,sha256=xzMIA4NH4atxKHtepaW607dpH1yftDQLSr42CValZ2M,17919
4
4
  anchorbrowser/_compat.py,sha256=VWemUKbj6DDkQ-O4baSpHVLJafotzeXmCQGJugfVTIw,6580
5
5
  anchorbrowser/_constants.py,sha256=S14PFzyN9-I31wiV7SmIlL5Ga0MLHxdvegInGdXH7tM,462
6
6
  anchorbrowser/_exceptions.py,sha256=Qz7WOsYUFZ3bEoN28V-C9Wk-EvYerqP83-fMUINlZKQ,3234
7
7
  anchorbrowser/_files.py,sha256=YiRJFxnIpA4R0pnDmFtr_di9nD5txfnUOipc63hvBDs,3634
8
- anchorbrowser/_models.py,sha256=aRCuUozbf1yNMK9XbMmxcgxDJYogijGTtQEr3g_wbyA,29316
8
+ anchorbrowser/_models.py,sha256=KvjsMfb88XZlFUKVoOxr8OyDj47MhoH2OKqWNEbBhk4,30010
9
9
  anchorbrowser/_qs.py,sha256=AOkSz4rHtK4YI3ZU_kzea-zpwBUgEY8WniGmTPyEimc,4846
10
10
  anchorbrowser/_resource.py,sha256=7lE1EgpVj5kwckk-27umtigTOf9nKTyRl97cgNwRbRQ,1142
11
11
  anchorbrowser/_response.py,sha256=xsiyWOC8LWW-NvbFtZ-MJD4f7eI9RnivKwtKImZ-8o4,28860
12
12
  anchorbrowser/_streaming.py,sha256=9uTovnqQkz3LRbIWe9fSWwzB_aI1cX14LvEuMhkEcDI,10128
13
13
  anchorbrowser/_types.py,sha256=-8YuIWJ9gV1xWRzK7aR74ryHGMjFtNl5NGZpbuYtPIU,6204
14
- anchorbrowser/_version.py,sha256=H7EnW4PUznLvRCsBHOvDJ9nrBion45JYEaLEeyBiJfw,173
14
+ anchorbrowser/_version.py,sha256=bq-FIYtBF2Y62oY8DbKdaBTdzcUeJm989xXQKycdEcY,173
15
15
  anchorbrowser/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
16
  anchorbrowser/_utils/__init__.py,sha256=PNZ_QJuzZEgyYXqkO1HVhGkj5IU9bglVUcw7H-Knjzw,2062
17
17
  anchorbrowser/_utils/_logs.py,sha256=VECnOdIr_cYELMwV_E72FPDY8f1qoYxASex-FciwPxc,795
@@ -24,7 +24,11 @@ anchorbrowser/_utils/_transform.py,sha256=n7kskEWz6o__aoNvhFoGVyDoalNe6mJwp-g7BW
24
24
  anchorbrowser/_utils/_typing.py,sha256=D0DbbNu8GnYQTSICnTSHDGsYXj8TcAKyhejb0XcnjtY,4602
25
25
  anchorbrowser/_utils/_utils.py,sha256=ts4CiiuNpFiGB6YMdkQRh2SZvYvsl7mAF-JWHCcLDf4,12312
26
26
  anchorbrowser/lib/.keep,sha256=wuNrz-5SXo3jJaJOJgz4vFHM41YH_g20F5cRQo0vLes,224
27
+ anchorbrowser/lib/agent.py,sha256=M9Yg00Ogntfy0DZ9UvxLQp27prAYjo-0SrVLaovLM8g,2078
28
+ anchorbrowser/lib/browser.py,sha256=aLsNgZYY-54YMfLW2jN4QjsUzOTv4PtSUMWDlhLUSZA,6069
27
29
  anchorbrowser/resources/__init__.py,sha256=fvjfy3JG7uR4Bz-wYI6euyWxCNVFI08v7m9ARGzTaTs,1993
30
+ anchorbrowser/resources/agent.py,sha256=2xAcNfeu9otN6qk0RY4pr6fjNloqKriEQEJW50nJAC4,13052
31
+ anchorbrowser/resources/browser.py,sha256=BB5hq_ayIDL_ziYHq13oj8US3XkHzkoXiGLBm7h9dH0,5548
28
32
  anchorbrowser/resources/extensions.py,sha256=Ri4BuoHYV7_iHdCypGPVQ5AgX4FPkzgHFH_TIMWnsBE,15943
29
33
  anchorbrowser/resources/profiles.py,sha256=WxuQnGxx8OAmEfl44aJfV-Bnx_XRBnNvs-Ozcfcnj9U,21347
30
34
  anchorbrowser/resources/tools.py,sha256=U6kxpmQkHQC3i4-my54R27NA-CBnMEYsZFrudEnxawM,20946
@@ -40,7 +44,7 @@ anchorbrowser/resources/sessions/recordings/recordings.py,sha256=6OXB_m09wPYZGsb
40
44
  anchorbrowser/types/__init__.py,sha256=O5J8M3dkfHUoSYFI6zjxoF3Phmajl_M9vROulwJ4xpI,2520
41
45
  anchorbrowser/types/extension_delete_response.py,sha256=3ixtvxjz_Ay-Kp8lamJzbIrqKpvfqWui5l13Sr035To,327
42
46
  anchorbrowser/types/extension_list_response.py,sha256=-OysuceCBHQLLT8B-zvwtHQDXqx__Zjkkb-gNT58N3Y,876
43
- anchorbrowser/types/extension_manifest.py,sha256=rAudf0olz_iq2Jxb6ejZU4FXd9p-NDFNs3pNDFZ8_8A,704
47
+ anchorbrowser/types/extension_manifest.py,sha256=rW6mjLODcQ9XHXWh6XBU1Sr_WZD0w8eod4YLH5dXjwc,872
44
48
  anchorbrowser/types/extension_retrieve_response.py,sha256=wBpk7gNgAbVf1etsCSrC4JOrKD6c_9HTv2S2FjNiAiM,807
45
49
  anchorbrowser/types/extension_upload_params.py,sha256=bBd8BMRVHu6P18jJovVMLPnIzGXGlV8Geb8TdiuGPbQ,470
46
50
  anchorbrowser/types/extension_upload_response.py,sha256=ee8KYWIb-AjURMzrtAMNUTjV3Izhj6AB0LeAvR0npvk,868
@@ -90,7 +94,7 @@ anchorbrowser/types/sessions/recording_resume_response.py,sha256=JXGXVLDwcbodSOC
90
94
  anchorbrowser/types/sessions/recordings/__init__.py,sha256=OKfJYcKb4NObdiRObqJV_dOyDQ8feXekDUge2o_4pXQ,122
91
95
  anchorbrowser/types/shared/__init__.py,sha256=FQKjY3VDxI8T0feNRazdY5TOqb2KDeEwZaoJjsxuEl4,152
92
96
  anchorbrowser/types/shared/success_response.py,sha256=l9OWrucXoSjBdsx5QKdvBPFtxv8d0YdpYY6iL5cWWuc,314
93
- anchorbrowser-0.1.0a1.dist-info/METADATA,sha256=ufUmrcbnkXX99NqYJKaleOTkeEqu9JCw7JUcbO5BeDc,15083
94
- anchorbrowser-0.1.0a1.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
95
- anchorbrowser-0.1.0a1.dist-info/licenses/LICENSE,sha256=QYTH6OztHxnELDn890vME8F7-euzmsHhWI4XOSYxwOg,11343
96
- anchorbrowser-0.1.0a1.dist-info/RECORD,,
97
+ anchorbrowser-0.1.0a2.dist-info/METADATA,sha256=sJXgogjt-WLPFUGEKcJBDhu5-Srk8iHvd8nLXbyUA3s,15142
98
+ anchorbrowser-0.1.0a2.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
99
+ anchorbrowser-0.1.0a2.dist-info/licenses/LICENSE,sha256=QYTH6OztHxnELDn890vME8F7-euzmsHhWI4XOSYxwOg,11343
100
+ anchorbrowser-0.1.0a2.dist-info/RECORD,,