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
19 changes: 11 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,11 @@ const result = await agent.executeTask(
);
console.log(result.output);

// Use page.ai and page.extract
// Use page.ai, page.perform, and page.extract
const page = await agent.newPage();
await page.goto("https://flights.google.com", { waitUntil: "load" });
await page.ai("search for flights from Rio to LAX from July 16 to July 22");
await page.perform("click the search button");
const res = await page.extract(
"give me the flight options",
z.object({
Expand All @@ -110,7 +111,9 @@ await agent.closeAgent();

HyperAgent provides two complementary APIs optimized for different use cases:

### 🎯 `page.aiAction()` - Single Granular Actions
### 🎯 `page.perform()` - Single Granular Actions

> `page.aiAction()` is deprecated and remains available as an alias; prefer `page.perform()` going forward.

**Best for**: Single, specific actions like "click login", "fill email with test@example.com"

Expand All @@ -126,9 +129,9 @@ const page = await agent.newPage();
await page.goto("https://example.com/login");

// Fast, reliable single actions
await page.aiAction("fill email with user@example.com");
await page.aiAction("fill password with mypassword");
await page.aiAction("click the login button");
await page.perform("fill email with user@example.com");
await page.perform("fill password with mypassword");
await page.perform("click the login button");
```

### 🧠 `page.ai()` - Complex Multi-Step Tasks
Expand Down Expand Up @@ -164,9 +167,9 @@ await page.ai("search for flights from Miami to New Orleans on July 16", {
Combine both APIs for optimal performance:

```typescript
// Use aiAction for fast, reliable individual actions
await page.aiAction("click the search button");
await page.aiAction("type laptop into search");
// Use perform for fast, reliable individual actions
await page.perform("click the search button");
await page.perform("type laptop into search");

// Use ai() for complex, multi-step workflows
await page.ai("filter results by price under $1000 and sort by rating");
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@hyperbrowser/agent",
"version": "1.0.7",
"version": "1.0.8",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bump major version to 1.1.0?

"description": "Hyperbrowsers Web Agent",
"author": "",
"main": "dist/index.js",
Expand Down
65 changes: 41 additions & 24 deletions src/agent/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,11 +323,7 @@ export class HyperAgent<T extends BrowserProviders = "Local"> {
const lastPage = pages[pages.length - 1];
// If the last page is different and not closed, switch to it
// We prefer the newest page as it's likely the result of the user's last action
if (
lastPage &&
!lastPage.isClosed() &&
lastPage !== this._currentPage
) {
if (lastPage && !lastPage.isClosed() && lastPage !== this._currentPage) {
if (this.debug) {
console.log(
`[HyperAgent] Polling detected new page, switching focus: ${lastPage.url()}`
Expand Down Expand Up @@ -441,16 +437,18 @@ export class HyperAgent<T extends BrowserProviders = "Local"> {
cleanup();
// Retrieve the correct state to update
const failedTaskState = this.tasks[taskId];
if (failedTaskState) {
failedTaskState.status = TaskStatus.FAILED;
failedTaskState.error = error.message;
// Emit error on the central emitter, including the taskId
this.errorEmitter.emit("error", error);
} else {
// Fallback if task state somehow doesn't exist
console.error(`Task state ${taskId} not found during error handling.`);
}
});
if (failedTaskState) {
failedTaskState.status = TaskStatus.FAILED;
failedTaskState.error = error.message;
// Emit error on the central emitter, including the taskId
this.errorEmitter.emit("error", error);
} else {
// Fallback if task state somehow doesn't exist
console.error(
`Task state ${taskId} not found during error handling.`
);
}
});
return this.getTaskControl(taskId);
}

Expand Down Expand Up @@ -753,7 +751,7 @@ export class HyperAgent<T extends BrowserProviders = "Local"> {

/**
* Execute a single granular action using a11y mode
* Internal method used by page.aiAction()
* Internal method used by page.perform() (and deprecated page.aiAction())
*
* Architecture: Simple examine->act flow
* - 1 LLM call (examineDom finds element and suggests method)
Expand All @@ -763,7 +761,7 @@ export class HyperAgent<T extends BrowserProviders = "Local"> {
* @param page The page to execute the action on
* @returns A promise that resolves to the task output
*/
private async executeSingleAction(
public async executeSingleAction(
instruction: string,
pageOrGetter: Page | (() => Page),
_params?: TaskParams
Expand Down Expand Up @@ -799,7 +797,10 @@ export class HyperAgent<T extends BrowserProviders = "Local"> {

// Check if page context switched during findElement (e.g. new tab opened by previous action)
if (getPage() !== initialPage) {
throw new HyperagentError("Page context switched during execution", 409);
throw new HyperagentError(
"Page context switched during execution",
409
);
}

domState = foundDomState;
Expand Down Expand Up @@ -840,7 +841,10 @@ export class HyperAgent<T extends BrowserProviders = "Local"> {

// Check context switch again before action
if (getPage() !== initialPage) {
throw new HyperagentError("Page context switched during execution", 409);
throw new HyperagentError(
"Page context switched during execution",
409
);
}

// Create a context object compatible with performAction
Expand Down Expand Up @@ -934,7 +938,10 @@ export class HyperAgent<T extends BrowserProviders = "Local"> {
// If page switched during execution, prioritize that over the error
// This catches cases where findElement failed because the old page closed/navigated
if (getPage() !== initialPage) {
throw new HyperagentError("Page context switched during execution", 409);
throw new HyperagentError(
"Page context switched during execution",
409
);
}

// Write debug data on error
Expand Down Expand Up @@ -1184,10 +1191,10 @@ export class HyperAgent<T extends BrowserProviders = "Local"> {
page.context().off("page", onPage);
};

hyperPage.ai = (task: string, params?: TaskParams) =>
this.executeTask(task, params, getActivePage());

hyperPage.aiAction = async (instruction: string, params?: TaskParams) => {
const executeSingleActionWithRetry = async (
instruction: string,
params?: TaskParams
) => {
const maxRetries = 3;
for (let i = 0; i < maxRetries; i++) {
try {
Expand Down Expand Up @@ -1219,6 +1226,16 @@ export class HyperAgent<T extends BrowserProviders = "Local"> {
);
};

hyperPage.ai = (task: string, params?: TaskParams) =>
this.executeTask(task, params, getActivePage());

hyperPage.perform = (instruction: string, params?: TaskParams) =>
executeSingleActionWithRetry(instruction, params);

hyperPage.aiAction = async (instruction: string, params?: TaskParams) => {
return executeSingleActionWithRetry(instruction, params);
};

// aiAsync tasks run in background, so we just use the current scope start point.
// The task itself has internal auto-following logic (from executeTaskAsync implementation).
hyperPage.aiAsync = (task: string, params?: TaskParams) =>
Expand Down
10 changes: 7 additions & 3 deletions src/types/agent/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ import { Page } from "playwright-core";
import { ErrorEmitter } from "@/utils";

export const AgentOutputFn = (
actionsSchema: z.ZodUnion<
readonly [z.ZodType<any>, ...z.ZodType<any>[]]
>
actionsSchema: z.ZodUnion<readonly [z.ZodType<any>, ...z.ZodType<any>[]]>
) =>
z.object({
thoughts: z
Expand Down Expand Up @@ -100,6 +98,12 @@ export interface HyperPage extends Page {
* Best for: Single actions like "click login", "fill email with test@example.com"
* Mode: Always a11y (accessibility tree, faster and more reliable)
*/
perform: (instruction: string, params?: TaskParams) => Promise<TaskOutput>;

/**
* @deprecated: use perform() instead.
* Execute a single granular action using a11y mode
*/
aiAction: (instruction: string, params?: TaskParams) => Promise<TaskOutput>;

aiAsync: (task: string, params?: TaskParams) => Promise<Task>;
Expand Down