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:truescreenshots/PDFs, and durably-persisted downloads under one listing).Session—navigate,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 durableArtifactRefinstead 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)
- Downloads:
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.