feat(tools): add ToolRegistry, ToolSearchTool and ExpertTool to support on-demand tool visibility, discovery, activation, and expert/subagent tooling; extend DualAgentOrchestrator API and interfaces to manage tool lifecycle
This commit is contained in:
188
ts/smartagent.classes.toolregistry.ts
Normal file
188
ts/smartagent.classes.toolregistry.ts
Normal file
@@ -0,0 +1,188 @@
|
||||
import * as interfaces from './smartagent.interfaces.js';
|
||||
import { BaseToolWrapper } from './smartagent.tools.base.js';
|
||||
|
||||
/**
|
||||
* ToolRegistry - Manages tool registration, visibility, and lifecycle
|
||||
*
|
||||
* Responsibilities:
|
||||
* - Track all registered tools with their metadata
|
||||
* - Manage visibility (initial vs on-demand)
|
||||
* - Handle activation of on-demand tools
|
||||
* - Provide search functionality
|
||||
*/
|
||||
export class ToolRegistry {
|
||||
private tools: Map<string, BaseToolWrapper> = new Map();
|
||||
private metadata: Map<string, interfaces.IToolMetadata> = new Map();
|
||||
private activated: Set<string> = new Set();
|
||||
|
||||
/**
|
||||
* Register a tool with optional visibility settings
|
||||
*/
|
||||
register(tool: BaseToolWrapper, options: interfaces.IToolRegistrationOptions = {}): void {
|
||||
const visibility = options.visibility ?? 'initial';
|
||||
|
||||
this.tools.set(tool.name, tool);
|
||||
this.metadata.set(tool.name, {
|
||||
name: tool.name,
|
||||
description: tool.description,
|
||||
actions: tool.actions,
|
||||
visibility,
|
||||
isActivated: visibility === 'initial',
|
||||
isInitialized: false,
|
||||
tags: options.tags,
|
||||
category: options.category,
|
||||
});
|
||||
|
||||
if (visibility === 'initial') {
|
||||
this.activated.add(tool.name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tools visible to the Driver (initial + activated on-demand)
|
||||
*/
|
||||
getVisibleTools(): BaseToolWrapper[] {
|
||||
return Array.from(this.tools.entries())
|
||||
.filter(([name]) => this.activated.has(name))
|
||||
.map(([, tool]) => tool);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all tools (for search results)
|
||||
*/
|
||||
getAllTools(): BaseToolWrapper[] {
|
||||
return Array.from(this.tools.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific tool by name
|
||||
*/
|
||||
getTool(name: string): BaseToolWrapper | undefined {
|
||||
return this.tools.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get metadata for a tool
|
||||
*/
|
||||
getMetadata(name: string): interfaces.IToolMetadata | undefined {
|
||||
return this.metadata.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all metadata
|
||||
*/
|
||||
getAllMetadata(): interfaces.IToolMetadata[] {
|
||||
return Array.from(this.metadata.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Search tools by query (matches name, description, tags, action names)
|
||||
*/
|
||||
search(query: string): interfaces.IToolMetadata[] {
|
||||
const q = query.toLowerCase();
|
||||
return this.getAllMetadata().filter((meta) => {
|
||||
if (meta.name.toLowerCase().includes(q)) return true;
|
||||
if (meta.description.toLowerCase().includes(q)) return true;
|
||||
if (meta.tags?.some((t) => t.toLowerCase().includes(q))) return true;
|
||||
if (meta.category?.toLowerCase().includes(q)) return true;
|
||||
if (
|
||||
meta.actions.some(
|
||||
(a) => a.name.toLowerCase().includes(q) || a.description.toLowerCase().includes(q)
|
||||
)
|
||||
)
|
||||
return true;
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate an on-demand tool
|
||||
*/
|
||||
async activate(name: string): Promise<{ success: boolean; error?: string }> {
|
||||
const tool = this.tools.get(name);
|
||||
const meta = this.metadata.get(name);
|
||||
|
||||
if (!tool || !meta) {
|
||||
return { success: false, error: `Tool "${name}" not found` };
|
||||
}
|
||||
|
||||
if (this.activated.has(name)) {
|
||||
return { success: true }; // Already activated
|
||||
}
|
||||
|
||||
// Initialize if not already initialized
|
||||
if (!meta.isInitialized) {
|
||||
await tool.initialize();
|
||||
meta.isInitialized = true;
|
||||
}
|
||||
|
||||
this.activated.add(name);
|
||||
meta.isActivated = true;
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a tool is activated
|
||||
*/
|
||||
isActivated(name: string): boolean {
|
||||
return this.activated.has(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize all initial (visible) tools
|
||||
*/
|
||||
async initializeVisibleTools(): Promise<void> {
|
||||
const promises: Promise<void>[] = [];
|
||||
|
||||
for (const [name, tool] of this.tools) {
|
||||
const meta = this.metadata.get(name);
|
||||
if (meta && this.activated.has(name) && !meta.isInitialized) {
|
||||
promises.push(
|
||||
tool.initialize().then(() => {
|
||||
meta.isInitialized = true;
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup all initialized tools
|
||||
*/
|
||||
async cleanup(): Promise<void> {
|
||||
const promises: Promise<void>[] = [];
|
||||
|
||||
for (const [name, tool] of this.tools) {
|
||||
const meta = this.metadata.get(name);
|
||||
if (meta?.isInitialized) {
|
||||
promises.push(tool.cleanup());
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a tool exists in the registry
|
||||
*/
|
||||
has(name: string): boolean {
|
||||
return this.tools.has(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of registered tools
|
||||
*/
|
||||
get size(): number {
|
||||
return this.tools.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of activated tools
|
||||
*/
|
||||
get activatedCount(): number {
|
||||
return this.activated.size;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user