blob: 79ad0b63dd2eab5b76677b28024d45c2cf8b5067 [file]
// Copyright (C) 2026 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {z, type ZodObject, type ZodRawShape} from 'zod';
import type {ToolRegistration} from './api';
/**
* A registered tool, normalised from a ToolRegistration: the args shape is
* wrapped in a ZodObject and the callback takes unknown args (validated on
* call).
*/
export interface ToolDef {
readonly name: string;
readonly description: string;
readonly inputSchema: ZodObject<ZodRawShape>;
/** Whether the tool mutates UI state (vs. read-only). */
readonly mutating: boolean;
readonly callback: (args: unknown) => Promise<string>;
}
/**
* The concrete registry behind IntellettoToolRegistrar.registerTool. Core and
* plugin-contributed tools both land here; the agent reads it to expose tools
* to the model.
*/
export class ToolRegistry {
private readonly tools = new Map<string, ToolDef>();
/** Register a tool. Throws if the name is already registered. */
registerTool<S extends ZodRawShape>(opts: ToolRegistration<S>): void {
if (this.tools.has(opts.name)) {
throw new Error(`Tool "${opts.name}" already registered`);
}
const inputSchema = z.object(opts.shape);
this.tools.set(opts.name, {
name: opts.name,
description: opts.description,
inputSchema: inputSchema as unknown as ZodObject<ZodRawShape>,
mutating: opts.mutating ?? false,
callback: opts.callback as (args: unknown) => Promise<string>,
});
}
/** Look up a tool by name, or undefined if not registered. */
get(name: string): ToolDef | undefined {
return this.tools.get(name);
}
/** All registered tools, in registration order. */
list(): readonly ToolDef[] {
return Array.from(this.tools.values());
}
/**
* Validate args against the tool's schema, then invoke. Throws on unknown
* tool or invalid args so the agent loop can fold it back as a tool-result
* error.
*/
async call(name: string, rawArgs: unknown): Promise<string> {
const tool = this.tools.get(name);
if (tool === undefined) throw new Error(`Unknown tool "${name}"`);
const parsed = tool.inputSchema.safeParse(rawArgs ?? {});
if (!parsed.success) {
throw new Error(
`Invalid arguments for "${name}": ${parsed.error.message}`,
);
}
return tool.callback(parsed.data);
}
}