browser-use-typescript
Version:
A TypeScript-based browser automation framework
478 lines • 16.5 kB
JavaScript
import { HistoryTreeProcessor } from "../domHIstory/historyTypes";
import { randomUUID } from "crypto";
import * as fs from 'fs';
import * as path from 'path';
import { MessageManagerState } from "./message_manager/types";
class AgentSettings {
use_vision = true;
use_vision_for_planner = false;
save_conversation_path = undefined;
save_conversation_path_encoding = 'utf-8';
max_failures = 3;
retry_delay = 10;
max_input_tokens = 128000;
validate_output = false;
message_context = undefined;
generate_gif = false;
available_file_paths = undefined;
override_system_message = undefined;
extend_system_message = undefined;
include_attributes = [
'title',
'type',
'name',
'role',
'tabindex',
'aria-label',
'placeholder',
'value',
'alt',
'aria-expanded',
];
max_actions_per_step = 10;
tool_calling_method = 'auto';
page_extraction_llm = undefined;
planner_llm = undefined;
planner_interval = 1;
constructor(params) {
this.use_vision = params.use_vision;
this.use_vision_for_planner = params.use_vision_for_planner;
this.save_conversation_path = params.save_conversation_path;
this.save_conversation_path_encoding = params.save_conversation_path_encoding;
this.max_failures = params.max_failures;
this.retry_delay = params.retry_delay;
this.max_input_tokens = params.max_input_tokens;
this.validate_output = params.validate_output;
this.message_context = params.message_context;
this.generate_gif = params.generate_gif;
this.available_file_paths = params.available_file_paths;
this.override_system_message = params.override_system_message;
this.extend_system_message = params.extend_system_message;
this.include_attributes = params.include_attributes;
this.max_actions_per_step = params.max_actions_per_step;
this.tool_calling_method = params.tool_calling_method;
this.page_extraction_llm = params.page_extraction_llm;
this.planner_llm = params.planner_llm;
this.planner_interval = params.planner_interval;
}
}
class AgentState {
agent_id = randomUUID();
n_steps = 1;
consecutive_failures = 0;
last_result = null;
history = new AgentHistoryList();
last_plan = null;
paused = false;
stopped = false;
message_manager_state = new MessageManagerState();
}
class AgentStepInfo {
step_number;
max_steps;
constructor(step_number, max_steps) {
this.step_number = step_number;
this.max_steps = max_steps;
}
is_last_step() {
return this.step_number >= this.max_steps - 1;
}
}
class ActionResult {
isDone = false;
success = null;
extractedContent = null;
error = null;
includeInMemory = false;
constructor(params) {
Object.assign(this, params);
}
// Helper methods for serialization
toJSON() {
const result = {};
if (this.isDone !== undefined)
result.isDone = this.isDone;
if (this.success !== undefined)
result.success = this.success;
if (this.extractedContent !== undefined)
result.extractedContent = this.extractedContent;
if (this.error !== undefined)
result.error = this.error;
result.includeInMemory = this.includeInMemory;
return result;
}
}
class StepMetadata {
step_start_time;
step_end_time;
input_tokens;
step_number;
constructor(step_start_time, step_end_time, input_tokens, step_number) {
this.step_start_time = step_start_time;
this.step_end_time = step_end_time;
this.input_tokens = input_tokens;
this.step_number = step_number;
}
get duration_seconds() {
return this.step_end_time - this.step_start_time;
}
// Helper method for serialization
toJSON() {
return {
step_start_time: this.step_start_time,
step_end_time: this.step_end_time,
input_tokens: this.input_tokens,
step_number: this.step_number
};
}
}
class AgentBrain {
evaluation_previous_goal;
memory;
next_goal;
constructor(fields) {
this.evaluation_previous_goal = fields.evaluation_previous_goal;
this.memory = fields.memory;
this.next_goal = fields.next_goal;
}
}
class AgentOutput {
action;
current_state;
constructor(fields) {
this.action = fields.action || [];
this.current_state = fields.current_state || new AgentBrain({
evaluation_previous_goal: '',
memory: '',
next_goal: ''
});
}
/**
* Generate a complete JSON schema for the AgentOutput
* This matches the structure created by Pydantic's model_json_schema() in Python
*/
toJson() {
try {
// Create the $defs section with models
const defs = {
"AgentBrain": {
"properties": {
"evaluation_previous_goal": {
"title": "Evaluation Previous Goal",
"type": "string",
"description": "Evaluation of how the previous goal was met"
},
"memory": {
"title": "Memory",
"type": "string",
"description": "Memory or context to keep between steps"
},
"next_goal": {
"title": "Next Goal",
"type": "string",
"description": "Next goal to achieve"
}
},
"required": ["evaluation_previous_goal", "memory", "next_goal"],
"title": "AgentBrain",
"type": "object",
"description": "Agent brain state"
}
};
// Get action properties and definitions
if (this.action && this.action.length > 0 && this.action[0]) {
// Create ActionModel definition
defs["ActionModel"] = {
"properties": this.action[0].toJson(),
"title": "ActionModel",
"type": "object",
"description": "Model for actions to be executed"
};
// Add action-specific definitions
const actionDefs = this.action[0].getActionDefs();
Object.assign(defs, actionDefs);
}
// Create the complete schema structure
return {
"$defs": defs,
"properties": {
"current_state": {
"$ref": "#/$defs/AgentBrain"
},
"action": {
"type": "array",
"items": {
"$ref": "#/$defs/ActionModel"
},
"minItems": 1,
"title": "Action",
"description": "List of actions to execute"
}
},
"required": ["current_state", "action"],
"title": "AgentOutput",
"type": "object",
"description": "AgentOutput model with custom actions"
};
}
catch (error) {
console.error("Error generating JSON schema:", error);
return {};
}
}
}
class AgentHistory {
model_output;
result;
state;
metadata;
constructor(model_output = null, result = [], state, metadata = null) {
this.model_output = model_output;
this.result = result;
this.state = state;
this.metadata = metadata;
}
// Helper method to get interacted elements
static async getInteractedElement(model_output, selector_map) {
const elements = [];
for (const action of model_output.action) {
const index = "get_index" in action ? action.get_index() : null;
if (index !== null && index in selector_map) {
const el = await selector_map[index];
elements.push(await HistoryTreeProcessor.convertDomElementToHistoryElement(el));
}
else {
elements.push(null);
}
}
return elements;
}
// Helper method for serialization
toJSON() {
// Handle action serialization
let model_output_dump = null;
if (this.model_output) {
model_output_dump = this.model_output;
}
return {
model_output: model_output_dump,
result: this.result.map(r => r.toJSON()),
state: this.state.toDict() ? JSON.parse(this.state.toDict().toString()) : this.state,
metadata: this.metadata ? this.metadata.toJSON() : null
};
}
}
class AgentHistoryList {
history = [];
constructor(history = []) {
this.history = history;
}
totalDurationSeconds() {
let total = 0;
for (const h of this.history) {
if (h.metadata) {
total += h.metadata.duration_seconds;
}
}
return total;
}
totalInputTokens() {
let total = 0;
for (const h of this.history) {
if (h.metadata) {
total += h.metadata.input_tokens;
}
}
return total;
}
inputTokenUsage() {
return this.history
.filter(h => h.metadata)
.map(h => h.metadata.input_tokens);
}
toString() {
return `AgentHistoryList(all_results=${JSON.stringify(this.actionResults())}, all_model_outputs=${JSON.stringify(this.modelActions())})`;
}
saveToFile(filepath) {
fs.mkdirSync(path.dirname(filepath), { recursive: true });
const data = JSON.stringify(this.toJSON(), null, 2);
fs.writeFileSync(filepath, data, 'utf-8');
}
toJSON() {
return {
history: this.history.map(h => h.toJSON())
};
}
static loadFromFile(filepath) {
const data = JSON.parse(fs.readFileSync(filepath, 'utf-8'));
// Process the data to convert it to our model objects
for (const h of data.history) {
if (h.model_output) {
if (typeof h.model_output === 'object') {
// In a real implementation, we would validate and convert this properly
// But for simplicity, we're just setting it directly
}
else {
h.model_output = null;
}
}
if (!h.state.interacted_element) {
h.state.interacted_element = null;
}
}
// In a real implementation, we would validate the data structure here
return new AgentHistoryList(data.history);
}
lastAction() {
if (this.history.length > 0 && this.history[this.history.length - 1].model_output) {
const lastOutput = this.history[this.history.length - 1].model_output;
if (lastOutput.action.length > 0) {
const lastAction = lastOutput.action[lastOutput.action.length - 1];
return lastAction;
}
}
return null;
}
errors() {
const errors = [];
for (const h of this.history) {
const stepErrors = h.result
.filter(r => r.error)
.map(r => r.error);
// Each step can have only one error
errors.push(stepErrors.length > 0 ? stepErrors[0] : null);
}
return errors;
}
finalResult() {
if (this.history.length > 0 && this.history[this.history.length - 1].result.length > 0) {
const lastResult = this.history[this.history.length - 1].result[this.history[this.history.length - 1].result.length - 1];
return lastResult.extractedContent;
}
return null;
}
isDone() {
if (this.history.length > 0 && this.history[this.history.length - 1].result.length > 0) {
const lastResult = this.history[this.history.length - 1].result[this.history[this.history.length - 1].result.length - 1];
return lastResult.isDone === true;
}
return false;
}
isSuccessful() {
if (this.history.length > 0 && this.history[this.history.length - 1].result.length > 0) {
const lastResult = this.history[this.history.length - 1].result[this.history[this.history.length - 1].result.length - 1];
if (lastResult.isDone === true) {
return lastResult.success;
}
}
return null;
}
hasErrors() {
return this.errors().some(error => error !== null);
}
urls() {
return this.history.map(h => h.state.url || null);
}
screenshots() {
return this.history.map(h => h.state.screenshot || null);
}
actionNames() {
const actionNames = [];
for (const action of this.modelActions()) {
const keys = Object.keys(action);
if (keys.length > 0) {
actionNames.push(keys[0]);
}
}
return actionNames;
}
modelThoughts() {
return this.history
.filter(h => h.model_output !== null)
.map(h => h.model_output.current_state);
}
modelOutputs() {
return this.history
.filter(h => h.model_output !== null)
.map(h => h.model_output);
}
modelActions() {
const outputs = [];
for (const h of this.history) {
if (h.model_output) {
for (let i = 0; i < h.model_output.action.length; i++) {
const action = h.model_output.action[i];
const output = action;
outputs.push(output);
}
}
}
return outputs;
}
actionResults() {
const results = [];
for (const h of this.history) {
results.push(...h.result.filter(r => r));
}
return results;
}
extractedContent() {
const content = [];
for (const h of this.history) {
for (const r of h.result) {
if (r.extractedContent) {
content.push(r.extractedContent);
}
}
}
return content;
}
modelActionsFiltered(include = []) {
const outputs = this.modelActions();
const result = [];
for (const output of outputs) {
const keys = Object.keys(output);
for (const includeItem of include) {
if (keys.length > 0 && includeItem === keys[0]) {
result.push(output);
}
}
}
return result;
}
numberOfSteps() {
return this.history.length;
}
}
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = 'ValidationError';
}
}
class RateLimitError extends Error {
constructor(message) {
super(message);
this.name = 'RateLimitError';
}
}
class AgentError {
static VALIDATION_ERROR = 'Invalid model output format. Please follow the correct schema.';
static RATE_LIMIT_ERROR = 'Rate limit reached. Waiting before retry.';
static NO_VALID_ACTION = 'No valid action found';
static formatError(error, includeTrace = false) {
if (error instanceof ValidationError) {
return `${AgentError.VALIDATION_ERROR}\nDetails: ${error.message}`;
}
if (error instanceof RateLimitError) {
return AgentError.RATE_LIMIT_ERROR;
}
if (includeTrace) {
return `${error.message}\nStacktrace:\n${error.stack || ''}`;
}
return error.message;
}
}
// Export the classes and interfaces for use in other files
export { AgentSettings, AgentState, AgentStepInfo, ActionResult, StepMetadata, AgentBrain, AgentOutput, AgentHistory, AgentHistoryList, AgentError, ValidationError, RateLimitError, MessageManagerState, };
//# sourceMappingURL=types.js.map