Skip to main content
A tool is a function the model can call during a run. You describe it as a class implementing ITool: a name, a description, an optional input schema, and a handler. The agent loop exposes the tool to the model, validates the arguments the model supplies against your schema, runs your handler, and feeds the result back into the conversation.

Defining a tool

Implement ITool<P, R>, where P is the validated input and R is the handler’s return type. Register the class with decorator.tool():
import { inject } from "@ooneex/container";
import { Assert, type AssertType } from "@ooneex/validation";
import { decorator } from "@ooneex/ai";
import type { ITool } from "@ooneex/ai";

type WeatherInput = { city: string; units?: "metric" | "imperial" };

@decorator.tool()
class WeatherTool implements ITool<WeatherInput, Promise<string>> {
  public constructor(@inject(WeatherClient) private readonly client: WeatherClient) {}

  public getName = (): string => "get_weather";

  public getDescription = (): string =>
    "Get the current weather for a city. Returns a short human-readable summary.";

  public getInputSchema = (): AssertType =>
    Assert({
      city: "string > 0",
      "units?": "'metric' | 'imperial'",
    });

  public handler = async (param: WeatherInput): Promise<string> => {
    const report = await this.client.current(param.city, param.units ?? "metric");
    return `${report.summary}, ${report.temperature}°`;
  };
}
Add it to any agent’s getTools, or pass it per request:
public getTools = (): AiToolClassType[] => [WeatherTool];

// or, for a single call:
await chat.run<string>({ prompt: "Weather in Paris?", tools: [WeatherTool] });

The ITool contract

MemberRequiredPurpose
getNameUnique tool name the model uses to call it.
getDescriptionPlain-language description of when and how to use it — the model reads this.
handlerRuns the tool. Receives the validated input, returns the result.
getInputSchemaArkType assertion validating the model’s arguments before handler runs.
onBeforeCallHook that runs before the tool is invoked; can short-circuit the call.
onAfterCallHook that runs after the tool returns.
A clear getDescription and a precise getInputSchema are what make a tool reliable — the model decides whether and how to call the tool entirely from those two. Describe the input fields in the schema and the tool’s purpose in the description.

Validated input

When getInputSchema is present, the agent loop validates the model’s arguments against it before your handler runs. Inside handler, the input already matches the schema — no manual checks needed. The schema is an ArkType assertion built with Assert from @ooneex/validation:
public getInputSchema = (): AssertType =>
  Assert({
    query: "string > 0",
    "limit?": "number > 0",
    "tags?": "string[]",
  });
Omit getInputSchema for a tool that takes no arguments.

Call hooks

A tool can observe or gate its own invocation with two optional hooks. When any tool on a run declares either hook, the agent loop bridges them onto the run automatically.

onBeforeCall

Runs before the handler. Return a decision to allow, modify, or block the call — useful for permission checks or argument rewriting:
public onBeforeCall = (ctx, hookCtx) => {
  if (!ctx.context?.currentUser) {
    return { action: "deny", reason: "Authentication required" };
  }
  return { action: "allow" };
};

onAfterCall

Runs after the handler returns — for logging, metrics, or auditing:
public onAfterCall = async (ctx, info) => {
  await ctx.context.audit.write({ tool: this.getName(), result: info.result });
};
Both hooks receive the run’s ChatMiddlewareContext as their first argument, so they can read ctx.context — the runtime context passed to the call.

Built-in tools

The package ships ready-made tools for web search, Wikipedia, PubMed, and Linear. See Built-in Tools for the full catalog and the keys they need.