Skip to content
Open
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
7 changes: 7 additions & 0 deletions .changeset/yutori-navigator-cua.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@browserbasehq/stagehand": patch
---

Add Yutori Navigator n1.5 as a computer-use (CUA) agent provider, usable via `stagehand.agent({ mode: "cua", model: "yutori/n1.5-latest" })` (auth via `YUTORI_API_KEY`). OpenAI-compatible computer-use model integrated alongside the existing OpenAI/Anthropic/Google/Microsoft CUA clients, with no new dependencies.

Defaults to Navigator's expanded tool set (`browser_tools_expanded-20260403`): on top of the coordinate tools, the model gets the accessibility-backed DOM tools `extract_elements`, `find`, `set_element_value`, and `execute_js` (built on Stagehand's hybrid a11y snapshot + `deepLocator`), and the coordinate tools can target an element by `ref` (resolved to its on-screen center). Pass `toolSet: "browser_tools_core-20260403"` for coordinate-only behavior.
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ GROQ_API_KEY=""
BROWSERBASE_API_KEY=""
BRAINTRUST_API_KEY=""
ANTHROPIC_API_KEY=""
YUTORI_API_KEY=""
HEADLESS=false
ENABLE_CACHING=false
EVAL_MODELS="gpt-4o,claude-sonnet-4-6"
Expand Down
80 changes: 80 additions & 0 deletions packages/core/examples/yutori-navigator-example.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/**
* This example shows how to use the Yutori Navigator n1.5 computer-use model
* as a Stagehand CUA agent to navigate a web page and complete a task.
*
* Navigator n1.5 is served via an OpenAI-compatible Chat Completions API at
* https://api.yutori.com/v1 and reasons over screenshots with coordinate-based
* actions in a normalized 1000x1000 space.
*
* Setup:
* export YUTORI_API_KEY=yt-...
*
* @see https://docs.yutori.com/reference/n1-5.md
*
* NOTE: Configure browser dimensions when using a computer use agent.
*/
import { Stagehand } from "../lib/v3/index.js";
import chalk from "chalk";

async function main() {
console.log(`\n${chalk.bold("Stagehand 🤘 Yutori Navigator n1.5 Demo")}\n`);

const stagehand = new Stagehand({
env: "LOCAL",
verbose: 2,
localBrowserLaunchOptions: {
viewport: { width: 1280, height: 800 },
deviceScaleFactor: 1,
},
});
await stagehand.init();

try {
const page = stagehand.context.pages()[0];

const agent = stagehand.agent({
mode: "cua",
model: {
modelName: "yutori/n1.5-latest",
// Defaults to process.env.YUTORI_API_KEY and https://api.yutori.com/v1.
apiKey: process.env.YUTORI_API_KEY,
// baseURL: "https://api.yutori.com/v1",
// Optional Navigator tuning:
// Defaults to the expanded tool set (adds extract_elements/find/
// set_element_value/execute_js). Pass core for coordinate-only:
// toolSet: "browser_tools_core-20260403",
// disableTools: ["mouse_down", "mouse_up"],
// User context defaults to San Francisco / America/Los_Angeles —
// override for location- or time-sensitive tasks:
// userTimezone: "America/New_York",
// userLocation: "New York, NY, US",
},
});

await page.goto("https://www.yutori.com");

const instruction = "List the names of the team members on this site.";
console.log(`Instruction: ${chalk.white(instruction)}`);

const result = await agent.execute({
instruction,
maxSteps: 30,
});

console.log(`${chalk.green("✓")} Execution complete`);
console.log(`${chalk.yellow("⤷")} Result:`);
console.log(chalk.white(JSON.stringify(result, null, 2)));
} catch (error) {
console.log(`${chalk.red("✗")} Error: ${error}`);
if (error instanceof Error && error.stack) {
console.log(chalk.dim(error.stack.split("\n").slice(1).join("\n")));
}
} finally {
await stagehand.close();
}
}

main().catch((error) => {
console.log(`${chalk.red("✗")} Unhandled error in main function`);
console.log(chalk.red(error));
});
1 change: 1 addition & 0 deletions packages/core/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,7 @@ export const providerEnvVarMap: Partial<
azure: "AZURE_API_KEY",
xai: "XAI_API_KEY",
google_legacy: "GOOGLE_API_KEY",
yutori: "YUTORI_API_KEY",
};

const providersWithoutApiKey = new Set(["bedrock", "ollama"]);
Expand Down
11 changes: 10 additions & 1 deletion packages/core/lib/v3/agent/AgentProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { AnthropicCUAClient } from "./AnthropicCUAClient.js";
import { OpenAICUAClient } from "./OpenAICUAClient.js";
import { GoogleCUAClient } from "./GoogleCUAClient.js";
import { MicrosoftCUAClient } from "./MicrosoftCUAClient.js";
import { YutoriCUAClient } from "./YutoriCUAClient.js";

// Map model names to their provider types
export const modelToAgentProviderMap: Record<string, AgentProviderType> = {
Expand All @@ -31,6 +32,7 @@ export const modelToAgentProviderMap: Record<string, AgentProviderType> = {
"gemini-3-flash-preview": "google",
"gemini-3-pro-preview": "google",
"fara-7b": "microsoft",
"n1.5-latest": "yutori",
};

/**
Expand Down Expand Up @@ -99,9 +101,16 @@ export class AgentProvider {
userProvidedInstructions,
clientOptions,
);
case "yutori":
return new YutoriCUAClient(
type,
modelName,
userProvidedInstructions,
clientOptions,
);
default:
throw new UnsupportedModelProviderError(
["openai", "anthropic", "google", "microsoft"],
["openai", "anthropic", "google", "microsoft", "yutori"],
"Computer Use Agent",
);
}
Expand Down
Loading
Loading