Files
BrowserOS/reference-code/old-lib/tools/base/NxtscapeTool.ts
Felarof 8245dfe0ff Rewrite Agent Loop (#7)
* clean-up bunch of files for re-write

* more clean-up and adding basic agent

* Minor fix moved types into respective files.

* Deleted bunch of old files

backup

Update gitignore

Deleted a bunch of files

Remove message manager

Deleted old docs

Update rules

rename Profiler to profiler

* Temporarily adding old code

* Adding two small things back

* backup

* Implemented LangChainProvider and updated cursor rules

backup

LangChainProvider

curosr rules

* Implement tests for LangChainProvider -- unit test and integration test

integration test passes

integration test backup

* Tool Design

Tools Desing

tools design

* NavigationTool ready

NavigationTool ready

NavigationTool ready

NaivgationTool ready

backup

* MessageManager

MessageManager

backup

* Fixed integration test

* Agent design new

Updated agent design and added bunch of /NTN commands

agent new design

* Delete old agent design

* MessageManagerReadOnly class

* PlannerTool ready

PlannerTool almost ready

* ToolManager and DoneTool

* Integration of BrowserAgent

* BrowserAgent implementation v0.1

* BrowserAgent small fix v0.2

* Tool calling design

too call design

tool design claude

* Update agent tool design with // NTN

* add zod-to-json npm install

* BrowserAGent v0.3

* BrowserAgent v0.4

* BrowserAgent v0.5

* fixes

* Build error fixes in my NEWLY added code

build errors fix

* Build error fixes in old code (integration work)

backup

* Comment StreamEventProcessor for now, it is not used

* Small build error fix

* Small rename

* Added integration test to check structuredLLM and changed to 4o-mini

change default to nxtscape

integration test

* Small docstring

* Simplified BrowserAgent code and added integration test

Simplified BrowserAgent code

BrowserAGent integrationt est

* Update CLAUDE.md with project memory and instructions on how to write code

Update CLAUDE.md with project memory and instructions on how to write code

Project Memory

* Just a mova.. Moved ToolManager outside. Build works.

* TabOperations tool

TabOperations Tool and fixing some test

tab operations

* Update CLAUDE.md

* Added ClassificationTool

classifiction tool

classification prommpt

* Refactored and simplified PlannerTool unit test and integration test

* Updated Plnnaer tool

* Update CLAUDE.md

* BrowserAgent modified to do classification

BrowserAgent with classification

* minor fix to ToolManager

* Instead of ToolCall and ToolResult -- just updating message manager once

* minor fix to BrowserAgent integration test

* Changed done to "done_tool"

* Updated CLAUDE.md to reflect understanding of claude

* Uncommented stream event processor

* Renamed EventBus to StreamEventBus

* Commented StreamEventProcessor

* Event Processor

* Integrated EventProcessor with BrowserAgent

Added EventProcessor to BrowserAgetn

* Renamed StreamEventBus to EventBus

* Made EventBus required parameter in ExecutionContext

* PlanGenerator rewrite

PlanGenerator rewrite

backup

* For simple task, explicitly tell it to call done tool

* Max attempts for simple task

* backup

* Revert "backup"

This reverts commit 7d79a3d4d5774bfef79ec9827878b74edad3593f.

* Consolidating where EventBus and EventProcessor are created and initialized

backup

* Update CLAUDE.md

Update CLAUDE.md

* Improving agent loop code

Cleaned up processTooCall

classification task

* Create test-writer subAgent

test-agent-prompt

test agent prompt

test-agent-prompt

Update test-writer.md

* BrowserAgent test

Browseragent test

BrowserAgent test

* BrowserAgent refactor

backup

backup

* Minor fixes

* Minor fix

* minor change -- NEW AGENT LOOP IS WORKING WELL

* Update cursor rules

* Small change

* Improved BrowserAgent integration test

Improved BrowserAgent integration test

* Small change

* Update CLAUDE.md

* Different tools

* FindElementTool is ready

Find element update

backup

find element backup

* Updated to test strings to say "tests..."

* ScrollTool is ready

* RefreshStateTool is updated as well

* MessageManager updated

* SearchTool is ready

backup

* Interaction Element is also ready

* Add debugMessage emitter

* ValidatorTool ready and tests are passing

Validation Tool

validator tool

backup

backup

* GroupTabs tool ready

* Registered all the tools

* Planning changed to 5 steps

* BrowserAgent integration test fix

* Minor string changes

* backup

* Removed too many confusing events in EventProcessor -- there is only event.info right now

* Abort control implemented

backup

Abort

* Formatter for toolResult

Formatter for toolResult

backup

* Always render using Markdown

* Minor fix

---------

Co-authored-by: Nikhil Sonti <nikhilsv92@gmail.com>
2025-07-29 08:14:45 -07:00

217 lines
6.8 KiB
TypeScript

import { z } from "zod";
import { tool as createLangChainTool } from "@langchain/core/tools";
import { BaseChatModel } from "@langchain/core/language_models/chat_models";
import { ExecutionContext } from "@/lib/runtime/ExecutionContext";
import { ToolConfig } from "./ToolConfig";
import { LangChainProviderFactory, LLMOverrides } from "@/lib/llm";
import { Logging } from "@/lib/utils/Logging";
import BrowserContext from "@/lib/browser/BrowserContext";
import { profileStart, profileEnd } from "@/lib/utils/Profiler";
/**
* Abstract base class for all Nxtscape tools
*/
export abstract class NxtscapeTool<TInput = any, TOutput = any> {
protected executionContext: ExecutionContext;
protected browserContext: BrowserContext;
protected config: ToolConfig<TInput, TOutput>;
private llmInstance?: BaseChatModel; // Cached LLM instance
private llmPromise?: Promise<BaseChatModel>; // Promise to prevent concurrent initialization
constructor(
config: ToolConfig<TInput, TOutput>,
executionContext: ExecutionContext,
) {
this.config = config;
this.executionContext = executionContext;
this.browserContext = executionContext.browserContext;
}
/**
* Get an LLM instance for this tool based on user's browser settings
*
* The base instance (without overrides) is cached after first creation to avoid multiple
* initializations and API calls to browser preferences.
*
* @param overrides - Optional overrides for model/temperature
* Note: When overrides are provided, a new instance is created without caching
* @returns Promise resolving to the LLM instance
*
* @example
* // Get default LLM (cached)
* const llm = await this.getLLM();
*
* @example
* // Get LLM with custom temperature (not cached)
* const customLLM = await this.getLLM({ temperature: 0.7 });
*/
protected async getLLM(overrides?: LLMOverrides): Promise<BaseChatModel> {
// If we have custom overrides, create a new instance without caching
if (overrides && (overrides.model || overrides.temperature !== undefined)) {
Logging.log(
this.config.name,
"Creating LLM with custom overrides (not cached)",
);
return LangChainProviderFactory.createLLM(overrides);
}
// Return cached instance if available
if (this.llmInstance) {
return this.llmInstance;
}
// If already initializing, wait for the existing promise
if (this.llmPromise) {
return this.llmPromise;
}
// Initialize LLM with proper error handling and concurrency protection
this.llmPromise = LangChainProviderFactory.createLLM()
.then((llm) => {
this.llmInstance = llm;
Logging.log(this.config.name, "LLM initialized and cached for tool");
return llm;
})
.catch((error) => {
// Clear the promise on error so it can be retried
this.llmPromise = undefined;
const errorMessage =
error instanceof Error ? error.message : String(error);
Logging.log(
this.config.name,
`Failed to initialize LLM: ${errorMessage}`,
"error",
);
throw error;
});
return this.llmPromise;
}
/**
* Execute the tool with input validation and output formatting
*/
protected abstract execute(input: TInput): Promise<TOutput>;
/**
* Format tool output for UI display
* Each tool MUST implement this method to format its own output
* @param output - The validated tool output
* @returns Formatted string ready for UI display
*/
abstract FormatResultForUI(output: TOutput): string;
/**
* Get the tool configuration
*/
getConfig(): ToolConfig<TInput, TOutput> {
return this.config;
}
/**
* Get the LangChain-compatible tool with enhanced display result
*/
getLangChainTool() {
return createLangChainTool(
async (input: any) => {
const profileLabel = `Tool.${this.config.name}`;
profileStart(profileLabel);
try {
// Validate input
const validatedInput = this.config.inputSchema.parse(input);
// Execute tool
const result = await this.execute(validatedInput);
// Validate output
const validatedOutput = this.config.outputSchema.parse(result);
// Create enhanced result with display formatting
const enhancedResult = {
...validatedOutput,
_displayResult: this.FormatResultForUI(validatedOutput), // Add display-ready result
_toolName: this.config.name, // Add tool name for identification
};
// Return as JSON string for LangChain compatibility
profileEnd(profileLabel);
return JSON.stringify(enhancedResult);
} catch (error) {
profileEnd(profileLabel);
// Handle validation errors
if (error instanceof z.ZodError) {
const errorMessage = `Validation error: ${error.errors.map((e) => `${e.path.join(".")}: ${e.message}`).join(", ")}`;
const errorResult = {
success: false,
error: errorMessage,
_displayResult: `${errorMessage}`,
_toolName: this.config.name,
};
return JSON.stringify(errorResult);
}
// Handle other errors
const errorMessage =
error instanceof Error ? error.message : String(error);
const errorResult = {
success: false,
error: errorMessage,
_displayResult: `${errorMessage}`,
_toolName: this.config.name,
};
return JSON.stringify(errorResult);
}
},
{
name: this.config.name,
description: this.config.description,
schema: this.config.inputSchema,
},
);
}
/**
* Get streaming display configuration
*/
getUIConfig() {
return (
this.config.streamingConfig || {
displayName: this.config.name,
icon: "🔧",
progressMessage: `Running ${this.config.name}...`,
}
);
}
/**
* Generate contextual display message based on tool arguments
* Each tool MUST implement this method to provide specific messages based on their input
* @param args - Tool arguments of type TInput
* @returns Contextual display message
*/
abstract getProgressMessage(args: TInput): string;
/**
* Get display information for streaming
* @param args - Tool arguments of type TInput
* @returns Complete display information
*/
getToolMetadata(args: TInput): {
displayName: string;
icon: string;
description: string;
} {
const streamingConfig = this.getUIConfig();
return {
displayName: streamingConfig.displayName || this.config.name,
icon: streamingConfig.icon || "🔧",
description: this.getProgressMessage(args),
};
}
}