All files / src/chat Tool.ts

87.5% Statements 7/8
50% Branches 6/12
100% Functions 3/3
100% Lines 7/7

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92                                                                                                                    5x 5x 5x               11x       11x               11x             5x        
import { z } from "zod";
import { toJsonSchema } from "../schema/to-json-schema.js";
 
export interface ToolCall {
  id: string;
  type: "function";
  function: {
    name: string;
    arguments: string;
  };
}
 
export interface ToolDefinition {
  type: "function";
  function: {
    name: string;
    description?: string;
    parameters: Record<string, unknown>;
  };
  handler?: (args: unknown) => Promise<string>;
}
 
/**
 * Anything that can be resolved into a ToolDefinition.
 */
export type ToolResolvable = Tool | { new (): Tool } | ToolDefinition;
 
/**
 * Subclass this to create tools with auto-generated schemas and type safety.
 */
export abstract class Tool<T = Record<string, unknown>> {
  /**
   * The name of the tool (must match [a-zA-Z0-9_-]+).
   */
  public abstract name: string;
 
  /**
   * A clear description of what the tool does, used by the LLM to decide when to call it.
   */
  public abstract description: string;
 
  /**
   * Parameters the tool accepts.
   * Can be a Zod object (for auto-schema + type safety) or a raw JSON Schema.
   */
  public abstract schema: z.ZodObject<z.ZodRawShape> | Record<string, unknown>;
 
  /**
   * The core logic for the tool.
   * 'args' will be parsed and validated based on 'schema'.
   */
  public abstract execute(args: T): Promise<unknown>;
 
  /**
   * Internal handler to bridge with LLM providers.
   * Converts any result to a string (usually JSON).
   */
  public async handler(args: T): Promise<string> {
    const result = await this.execute(args);
    Iif (result === undefined || result === null) return "";
    return typeof result === "string" ? result : JSON.stringify(result);
  }
 
  /**
   * Converts the tool definition and logic into a standard ToolDefinition interface.
   * This is called automatically by NodeLLM when registering tools.
   */
  public toLLMTool(): ToolDefinition {
    const rawSchema = toJsonSchema(this.schema);
 
    // We want the 'properties' and 'required' parts, not the full JSON Schema wrapper if present
    const parameters =
      (rawSchema as { type: string }).type === "object"
        ? rawSchema
        : {
            type: "object",
            properties: (rawSchema as { properties?: Record<string, unknown> }).properties || {},
            required: (rawSchema as { required?: string[] }).required || []
          };
 
    return {
      type: "function",
      function: {
        name: this.name,
        description: this.description,
        parameters: parameters as Record<string, unknown>
      },
      handler: (args: unknown) => this.handler(args as T)
    };
  }
}