defmodule AgentSea.Surf do @moduledoc """ Browser automation / computer use via a Node sidecar (`AgentSea.Surf.Sidecar`), plus an adapter that exposes browsing as agent tools. ## Example {:ok, surf} = AgentSea.Surf.start_link() # spawns the priv Playwright server {:ok, _} = AgentSea.Surf.navigate(surf, "https://example.com") {:ok, text} = AgentSea.Surf.text(surf) # or expose it to an agent: tools = AgentSea.Surf.tool_specs(surf) """ alias AgentSea.Surf.Sidecar @doc """ Start a sidecar. Defaults to running the bundled Playwright server with `node` (requires `playwright` installed on the Node side); pass `:command` to run a different script or executable. """ def start_link(opts \\ []) do command = Keyword.get(opts, :command, ["node", default_server()]) Sidecar.start_link(Keyword.put(opts, :command, command)) end @doc "Path to the bundled Playwright-backed Node server." def default_server, do: Application.app_dir(:agentsea_surf, "priv/surf-server.js") # --- Browser commands --- def navigate(server, url), do: Sidecar.call(server, "navigate", %{"url" => url}) def text(server), do: Sidecar.call(server, "text", %{}) def screenshot(server), do: Sidecar.call(server, "screenshot", %{}) def click(server, selector), do: Sidecar.call(server, "click", %{"selector" => selector}) def eval(server, script), do: Sidecar.call(server, "eval", %{"script" => script}) # --- Agent tool adapter --- @doc "Build `AgentSea.Tool.Spec` browser tools bound to a running sidecar." @spec tool_specs(GenServer.server()) :: [AgentSea.Tool.Spec.t()] def tool_specs(server) do [ %AgentSea.Tool.Spec{ name: "browse", description: "Open a URL in a browser and return the page's visible text.", schema: [url: [type: :string, required: true]], run: fn args, _ctx -> with {:ok, _} <- navigate(server, arg(args, :url)) do text(server) end end }, %AgentSea.Tool.Spec{ name: "click", description: "Click an element by CSS selector on the current page.", schema: [selector: [type: :string, required: true]], run: fn args, _ctx -> click(server, arg(args, :selector)) end } ] end defp arg(args, key), do: Map.get(args, to_string(key)) || Map.get(args, key) end