Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions packages/sdk/examples/browser/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,21 @@ node retrieval/01-catalog-extraction.ts
- All examples need `UPSTASH_BOX_API_KEY`.
- Examples marked **AI** below use metered browser AI (`act`, `extract`,
`observe`) and need a model provider key configured on the box or account.
- `agentic/01-in-box-agent.ts` is different: it drives the browser through the
box's coding agent, so it needs an agent harness + key and bills coding-agent
tokens, not browser-AI metering.
- Everything else runs with the Box key alone.

## agentic/ — goal-driven browsing (replacing the removed agent loop)

Stagehand v4 removed the built-in agent loop. Two replacements, matching v4's
guidance:

| File | AI | What it shows |
| --- | --- | --- |
| `01-in-box-agent.ts` | coding agent | Hand a goal to the box's in-sandbox agent; `browser: true` wires the chrome-devtools MCP, so it drives the browser itself and writes results to the box. Needs an agent harness + key |
| `02-build-your-own-loop.ts` | yes | A live `observe` then `act(action)` then `extract` loop you own, bounded by a step budget with `extract` as the stop check. Model in the loop |

## automation/ — forms, files, and durable sessions

| File | AI | What it shows |
Expand Down
51 changes: 51 additions & 0 deletions packages/sdk/examples/browser/agentic/01-in-box-agent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Box, Agent } from "@upstash/box";

// Agentic browsing, in-box agent.
// Stagehand v4 removed the built-in agent loop. On Box the replacement is not a
// client-side loop: enabling the browser (`browser: true`) auto-wires the
// chrome-devtools MCP (chrome-devtools-mcp on 127.0.0.1:9222, Chromium already
// warmed) into the box's coding agent. So `box.agent.run` hands the goal to an
// agent that lives IN the sandbox — it writes and runs the browsing work itself
// and iterates until done. Same job as Stagehand v4 "code mode", different
// primitive (an in-box coding agent + MCP, not an SDK call you script).
//
// Cost note: this bills the coding agent's model tokens, not browser-AI metering
// (`act`/`extract`/`observe`). It needs an agent harness + key, not just the box.

const box = await Box.create({
apiKey: process.env.UPSTASH_BOX_API_KEY!,
runtime: "node",
browser: true,
agent: {
harness: Agent.ClaudeCode,
model: "anthropic/claude-sonnet-4-5",
apiKey: process.env.CLAUDE_KEY!,
},
});

try {
// No tab.create first: Chromium is warmed for browser boxes, and the agent
// drives it through the chrome-devtools MCP.
const run = await box.agent.run({
prompt: [
"Use the browser to scrape https://books.toscrape.com/.",
"Open the catalogue, visit the first 5 product pages, and for each one record",
"{ title, price, stock }.",
"Write the array as JSON to books.json in the working directory.",
"Verify the file parses and has 5 entries before you finish.",
].join(" "),
});

console.log("run:", run.status, run.cost);
console.log(run.result); // the agent's final message

if (run.status !== "completed") {
console.error("agent run did not complete; not reading books.json");
} else {
// The agent produced the file inside the box; read + parse it back out.
const books = JSON.parse(await box.files.read("books.json"));
console.log(books);
}
} finally {
await box.delete();
}
58 changes: 58 additions & 0 deletions packages/sdk/examples/browser/agentic/02-build-your-own-loop.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { writeFile } from "node:fs/promises";
import { Box } from "@upstash/box";
import { z } from "zod/v3";

// Agentic browsing, your own loop.
// Stagehand v4's other replacement for the removed agent loop: keep the model in
// the loop and own the control flow. Unlike 02-ai-compiled-scraper (which reads
// the layout ONCE, then scrapes deterministically with Playwright and no more
// AI), the model still runs here on every page. Resolve the product links once
// with observe, then for each: reset to the listing (deterministic goto, no
// browser-AI tokens), replay the resolved click with no LLM (act(action)), and
// extract the product. extract is the loop's stop check.

const START = "https://books.toscrape.com/";
const GOAL = 5;

const box = await Box.create({
apiKey: process.env.UPSTASH_BOX_API_KEY!,
runtime: "node",
browser: true,
});

try {
const tab = await box.browser.tab.create(START, { waitUntil: "domcontentloaded" });

// Resolve the product links ONCE (metered), keeping those with a selector so
// act() can replay each click. The listing exposes two links per book (cover +
// title), so we dedupe by title and stop once we have GOAL distinct books.
const { elements } = await tab.observe("the book product links in the listing");
const actions = elements.filter((e) => e.selector);

const books: Array<{ title: string; price: string; stock: string }> = [];
const seen = new Set<string>();
for (const action of actions) {
if (books.length >= GOAL) break; // stop once we have enough distinct books

await tab.goto(START); // deterministic reset, no browser-AI tokens
await tab.act(action); // replay the resolved click: no LLM, no tokens

const book = await tab.extract(
"the book title, price, and availability text on this product page",
z.object({
title: z.string().nullable(),
price: z.string().nullable(),
stock: z.string().nullable(),
}),
);
if (!book.title || !book.price) continue; // not a product page
if (seen.has(book.title)) continue; // skip the duplicate cover/title link
seen.add(book.title);
books.push({ title: book.title, price: book.price, stock: book.stock ?? "" });
}

await writeFile("books.json", JSON.stringify(books, null, 2));
console.log(`collected ${books.length}/${GOAL} books`);
} finally {
await box.delete();
}
Loading