Files
flynn/src/tools/registry.ts
T
William Valentin 6090508bad style: auto-fix ESLint issues (curly braces and formatting)
- Add curly braces to all if/else/for/while statements
- Fix indentation and trailing spaces
- Auto-fixed 372 linting errors using eslint --fix
- Remaining issues are warnings only (non-null assertions, explicit any types)
2026-02-11 10:30:24 -08:00

128 lines
3.5 KiB
TypeScript

import type { Tool, ToolInputSchema } from './types.js';
import type { ToolPolicy, ToolPolicyContext } from './policy.js';
export interface AnthropicToolDef {
name: string;
description: string;
input_schema: ToolInputSchema;
}
export interface OpenAIToolDef {
type: 'function';
function: {
name: string;
description: string;
parameters: ToolInputSchema;
};
}
export class ToolRegistry {
private tools: Map<string, Tool> = new Map();
private _policy?: ToolPolicy;
/** Sanitize a tool name for API compatibility (dots → underscores). */
static sanitizeToolName(name: string): string {
return name.replace(/\./g, '_');
}
register(tool: Tool): void {
if (this.tools.has(tool.name)) {
throw new Error(`Tool '${tool.name}' is already registered`);
}
this.tools.set(tool.name, tool);
}
unregister(name: string): boolean {
return this.tools.delete(name);
}
/** Replace an existing tool with a new implementation. Throws if not registered. */
replace(tool: Tool): void {
if (!this.tools.has(tool.name)) {
throw new Error(`Tool '${tool.name}' is not registered — cannot replace`);
}
this.tools.set(tool.name, tool);
}
/** Create a shallow clone of this registry (new Map, same Tool objects + policy). */
clone(): ToolRegistry {
const cloned = new ToolRegistry();
for (const tool of this.tools.values()) {
cloned.register(tool);
}
if (this._policy) {
cloned.setPolicy(this._policy);
}
return cloned;
}
get(name: string): Tool | undefined {
return this.tools.get(name);
}
/** Resolve a tool by its API-sanitized name (underscores → dots fallback). */
getByApiName(name: string): Tool | undefined {
return this.tools.get(name) ?? this.tools.get(name.replace(/_/g, '.'));
}
list(): Tool[] {
return Array.from(this.tools.values());
}
/** Set the tool policy for filtering. */
setPolicy(policy: ToolPolicy): void {
this._policy = policy;
}
/** Get the tool policy (if set). */
getPolicy(): ToolPolicy | undefined {
return this._policy;
}
/** Return tools filtered by the policy for a given context. */
filteredList(context?: ToolPolicyContext): Tool[] {
if (!this._policy) {return this.list();}
return this._policy.filterTools(this.list(), context);
}
toAnthropicFormat(): AnthropicToolDef[] {
return this.list().map(t => ({
name: ToolRegistry.sanitizeToolName(t.name),
description: t.description,
input_schema: t.inputSchema,
}));
}
/** Return Anthropic-format tools filtered by policy. */
filteredToAnthropicFormat(context?: ToolPolicyContext): AnthropicToolDef[] {
return this.filteredList(context).map(t => ({
name: ToolRegistry.sanitizeToolName(t.name),
description: t.description,
input_schema: t.inputSchema,
}));
}
toOpenAIFormat(): OpenAIToolDef[] {
return this.list().map(t => ({
type: 'function' as const,
function: {
name: ToolRegistry.sanitizeToolName(t.name),
description: t.description,
parameters: t.inputSchema,
},
}));
}
/** Return OpenAI-format tools filtered by policy. */
filteredToOpenAIFormat(context?: ToolPolicyContext): OpenAIToolDef[] {
return this.filteredList(context).map(t => ({
type: 'function' as const,
function: {
name: ToolRegistry.sanitizeToolName(t.name),
description: t.description,
parameters: t.inputSchema,
},
}));
}
}