Basic Function Registration

Functions can be either sync or async. Keep them focused and single-purpose.
import { ChatOpenAI } from "@langchain/openai";
import { Agent, Controller } from "browsernode";
import * as fs from "fs";
import { z } from "zod";
import { dirname, join } from "path";
import { fileURLToPath } from "url";

function getCurrentDirPath() {
	const __filename = fileURLToPath(import.meta.url);
	return dirname(__filename);
}

// Initialize controller first
const customController = new Controller();

// Use zod to define the action schema
const SaveTextFileAction = z.object({
	content: z.string(),
});
// Generate output directory once at startup
customController.action("Save content to text file", {
	paramModel: SaveTextFileAction,
})(async function saveTextFile(params: z.infer<typeof SaveTextFileAction>) {
	const content = params.content;
	fs.appendFileSync(
		join(getCurrentDirPath(), "hacker_news.txt"),
		typeof content === "string" ? content : JSON.stringify(content),
	);
	return `Saved news to hacker_news.txt`;
});

const agent = new Agent({
	task: task,
	model: model,
	controller: customController,
});
agent.run();

Basic Controller has all basic functionality you might need to interact with the browser already implemented.
//... then pass controller to the agent
const agent = new Agent(
	task: task,
	model: model,
	controller: customController,
});
Keep the function name and description short and concise. The Agent use the function solely based on the name and description. The stringified output of the action is passed to the Agent.

Browser-Aware Functions

For actions that need browser access, simply add the browser parameter inside the function parameters:
import { Browser, Controller, ActionResult } from "browsernode";

const customController = new Controller();

// Use zod to define the action schema
const OpenWebsiteAction = z.object({
	url: z.string(),
});
// Generate output directory once at startup
customController.action("Open website", {
	paramModel: OpenWebsiteAction,
})(async function openWebsite(params: z.infer<typeof OpenWebsiteAction>) {
	const url = params.url;
    const page = browser.getCurrentPage();
    await page.goto(url);
    return new ActionResult({
        extractedContent: 'Website opened'
    });
});

Structured Parameters with Zod

For complex actions, you can define parameter schemas using Zod:
import { Controller, ActionResult, Browser } from "browsernode";

const customController = new Controller();

const JobDetailsAction = z.object({
	title: z.string(),
	company: z.string(),
	jobLink: z.string(),
	salary: z.string().nullable(),
});
customController.action('Save job details which you found on page', {
    jobDetails: JobDetailsAction
})(async function saveJob(params: { jobDetails: z.infer<typeof JobDetailsAction> }, browser: Browser) {
    console.log(`Saving job: ${params.jobDetails.title} at ${params.jobDetails.company}`);
    // Access browser if needed
    const page = browser.getCurrentPage();
    await page.goto(params.jobDetails.jobLink);
});

Using Custom Actions with multiple agents

You can use the same controller for multiple agents.
const customController = new Controller();

// ... register actions to the controller

const agent = new Agent(
  {
    task: "Go to website X and find the latest news",
    llm: llm,
    controller:customController
  }
);

// Run the agent
await agent.run();

const agent2 = new Agent(
  {
    task: "Go to website Y and find the latest news",
    llm: llm,
    controller:customController
  }
});

await agent2.run()
The controller is stateless and can be used to register multiple actions and multiple agents.

Exclude functions

If you want less actions to be used by the agent, you can exclude them from the controller.
const customController = new Controller({
	excludeActions: ["openTab", "searchGoogle"],
});
For more examples like file upload or notifications, visit examples/custom-functions.