npm.io
0.1.0 • Published yesterday

agent-browser-control

Licence
MIT
Version
0.1.0
Deps
0
Size
105 kB
Vulns
0
Weekly
0

AgentBrowser — TypeScript/JavaScript SDK

The official TypeScript/JavaScript client for AgentBrowser — a browser that acts on your agents' behalf. Zero runtime dependencies — built on Node's native fetch. Node.js 18+.

Install

npm install ./sdk/js          # from this repo, until published to npm

Once published (see the "Publishing" note below — not done as part of this change):

npm install agent-browser-control

Quickstart

import { AgentBrowser } from "agent-browser-control";

const ab = new AgentBrowser("gbk_..."); // from the console → API keys

const s = await ab.session({ url: "https://example.com", record: true });
try {
  const png = await s.screenshot(); // -> Uint8Array (PNG)
  const pdf = await s.pdf(); // -> Uint8Array (PDF)
  const shot = await s.screenshot({ asArtifact: true }); // -> ArtifactRef (durable)
  const data = await s.extractAll({ title: "h1", price: ".price" });
  console.log(data); // { title: "...", price: "..." }
} finally {
  await s.close(); // because record:true the bundle is persisted server-side
}

// Use a separate non-recorded session for credentials. Once a hosted credential
// is filled, page, cookie, screenshot, and PDF reads are refused for that session.
const login = await ab.session({ url: "https://example.com/login" });
try {
  await login.login("app-login"); // hosted vault; may await approval
  await login.click("#submit");
} finally {
  await login.close();
}

// Later, fetch the recording:
for (const rec of await ab.recordings()) {
  const bytes = await ab.downloadRecording(rec.id);
  // e.g. writeFileSync("session.tar.gz", bytes)
}

Session also supports await using (TypeScript 5.2+ / Node 20.11+ with the explicit-resource- management flag, or a transpile target that supports it) for auto-close:

await using s = await ab.session({ url: "https://example.com" });
await s.navigate("https://example.com/pricing");
// s.close() runs automatically at the end of scope

Bring your own framework (raw CDP)

const s = await ab.session({ cdp: true, url: "https://example.com", profile: "mobile" });
console.log(s.cdpUrl); // wss://app.getagentbrowser.com/api/sessions/<id>/cdp?ticket=...
// The URL is ready to use — it carries a short-lived ticket scoped to this one
// session, so your account key never ends up in a log. Just:
//   const browser = await chromium.connectOverCDP(s.cdpUrl); // Playwright

The dedicated browser honors url and profile ("desktop" or "mobile") at launch. All hosted traffic is forced through a node-owned public-only proxy that re-resolves and validates every HTTP request and HTTPS tunnel; loopback, private, link-local, metadata, mixed public/private DNS answers, and non-HTTP navigation are rejected. Caller proxy/proxyBypass options are not exposed because they would bypass that boundary. Do not combine cdp: true with record, dom, or noVideo; raw-CDP capture is owned by your Playwright/Puppeteer/CDP client.

Use the returned cdpUrl unchanged: its ticket is short-lived and scoped to that session. A client that constructs the session CDP endpoint itself must send Authorization: Bearer gbk_... with a key carrying sessions:write. Never append a reusable API key as ?key=.

Structured action output has fixed safety budgets: 1 MiB for snapshot, page text, extraction, evaluation, and cookies; 32 MiB for screenshots; and 64 MiB for PDFs. The service returns a typed 422 action_output_too_large response instead of truncating JSON or binary data. Retry after reducing the requested page/output scope; a 429 action_output_busy means the node's single heavy output slot is occupied and the request should be retried with backoff.

Scheduled jobs & webhooks

await ab.createJob("price-check", "https://shop.example/item", "screenshot", 3600); // hourly
await ab.createWebhook("https://your-app.com/hooks", ["job.completed", "session.recording_ready"]);

Webhook deliveries are HMAC-signed — verify X-AgentBrowser-Signature (sha256=<hex> over the raw body) with the secret returned on creation.

API surface

  • new AgentBrowser(apiKey, baseUrl = "https://app.getagentbrowser.com", timeout = 75)session(), recordings(), downloadRecording(), deleteRecording(), webhooks(), createWebhook(), deleteWebhook(), jobs(), createJob(), deleteJob(), artifacts(), downloadArtifact(), deleteArtifact() (artifacts covers recordings, asArtifact:true screenshots/PDFs, and durably-persisted downloads under one listing).
  • Sessionnavigate, click, type, select, check, hover, press, scroll, back, forward, reload, clickAt, moveTo, drag, readPage, extract, extractAll, evaluate, waitFor, snapshot, mark, login, screenshot / pdf (pass { asArtifact: true } to persist a durable ArtifactRef instead of inlined bytes), getCookies, setCookies, setFileInput, share, status, cancel, stream (an async generator yielding live NDJSON event envelopes; inactivity timeout does not count consumer processing time), close, plus:
    • Downloads: waitForDownload, getDownload, getDownloadInfo, listDownloads, cancelDownload
    • Dialogs: waitForDialog, handleDialog, getDialog
    • Tabs: tabs, currentTab, switchTab, newTab, closeTab, waitForPopup
    • Network: waitForResponse, getResponseBody, blockRequests, setHeaderOverrides, unblockRequests
    • Human takeover: requestHumanTakeover, resumeFromTakeover
    • Storage state: exportStorageState, importStorageState
    • Any verb the server doesn't yet have a typed wrapper for: act(verb, params)

Errors raise AgentBrowserError (.status, .body, and .code when the server's error body carries a machine-readable one, e.g. action_output_too_large). 500/502 responses from gb-server and gb-noded carry a typed error envelope (see internal/platform/errenvelope.go in the main repo): in addition to .code, AgentBrowserError exposes .retryable (boolean — whether the identical request is safe to retry as-is), .requestId and .sessionId (correlation ids for support/log grepping), and .details (an object with error-specific context, e.g. a debug_bundle_id). All four default to false/undefined for older or not-yet-converted error bodies.

Transport

The per-call / client timeout covers the whole attempt, including reading the response body (JSON, error payloads, and artifact downloads) — not just until headers arrive.

GET requests (read-only, side-effect-free) automatically retry up to twice with exponential backoff on a connection failure or a 502/503/504.

Session.act() (which every action helper — click, navigate, type, login, ... — goes through) retries the same way, but only when it's provably safe: a read-only verb (extract, screenshot, readPage, ...) retries freely, exactly like a GET. Every other (write) verb retries only behind an idempotency_key (still the wire's snake_case field, passed inside act's params object) — the server (P1-111) caches that action's terminal result per key, so a retried call replays the original result instead of executing again. Pass your own idempotency_key to act() to control it yourself (e.g. to make your OWN later retry, after your process restarts, safe too); if you don't, act() generates one automatically per call so the built-in retry is never a bare, unprotected write retry.

Every other write (AgentBrowser.session(), createWebhook, createJob, delete*, ...) is not auto-retried — those endpoints have no idempotency-key support server-side, so retrying one on a transport failure could double-create or double-delete. Session.close() keeps its own narrow, deliberate exception: a second close() call is always safe (a 404 on delete is treated as already-closed).

Redirects are followed manually rather than relying on fetch's own opaque "follow" mode, so the Authorization header is dropped on any cross-origin hop and kept on same-origin ones — the same policy as the Python SDK's _OriginSafeRedirectHandler.

Development

npm install
npm run typecheck   # tsc --noEmit, strict
npm run build        # emits dist/ (ESM + .d.ts)
npm test              # compiles src+test to dist-test/, runs node --test against it

No mocked HTTP layer — the test suite spins up real node:http servers per test (see test/testutil.ts) and exercises the transport, retry/idempotency, redirect-credential-stripping, and every typed convenience method end-to-end.

Publishing

Not part of this change — publishing to the real npm registry requires a maintainer's own npm account/token. This package builds, typechecks, and passes its full test suite; it has not been published anywhere.

Keywords