SPB Git

spb/khaelor Public

KHAELOR — a terminal-native autonomous engineering agent powered by Anthropic.

TypeScript 82.9% HTML 14.9% CSS 1.1% JavaScript 0.7%
2.9 KB · 81 lines typescript
Raw Blame History
1/**2 * KHAELOR3 * File: src/tools/schema.ts4 * Description: JSON-schema subset for tool inputs and the validator that produces repair prose (TOOL_PROTOCOL §1.2).5 *6 * Author: Simon-Pierre Boucher7 * Contact: contact@spboucher.ai8 */910/** One tool parameter. The subset of JSON Schema the seven tools need. */11export interface ToolPropertySchema {12  type: "string" | "integer" | "boolean";13  description: string;14  enum?: string[];15}1617/** A tool's input_schema — always a flat object with ≤5 properties (ADR-8). */18export interface ToolJsonSchema {19  type: "object";20  properties: Record<string, ToolPropertySchema>;21  required: string[];22}2324export type ParsedInput<P> = { ok: true; value: P } | { ok: false; error: string };2526function repair(toolName: string, problem: string): string {27  return `Invalid input for tool "${toolName}": ${problem}. Please rewrite the input so it satisfies the expected schema.`;28}2930/**31 * Validate a decoded tool_use input against the tool's schema. Failures32 * return model-facing repair prose (never thrown to the kernel).33 */34export function validateToolInput<P>(35  toolName: string,36  schema: ToolJsonSchema,37  input: unknown,38): ParsedInput<P> {39  if (typeof input !== "object" || input === null || Array.isArray(input)) {40    return { ok: false, error: repair(toolName, "the input must be a JSON object") };41  }42  const record = input as Record<string, unknown>;4344  for (const name of schema.required) {45    if (record[name] === undefined || record[name] === null) {46      return { ok: false, error: repair(toolName, `parameter "${name}" is required`) };47    }48  }4950  for (const [name, value] of Object.entries(record)) {51    if (value === undefined || value === null) continue;52    const prop = schema.properties[name];53    if (prop === undefined) continue; // Unknown params are tolerated (lenient decode).54    if (prop.type === "string" && typeof value !== "string") {55      return { ok: false, error: repair(toolName, `parameter "${name}" must be a string`) };56    }57    if (prop.type === "boolean" && typeof value !== "boolean") {58      return { ok: false, error: repair(toolName, `parameter "${name}" must be a boolean`) };59    }60    if (prop.type === "integer" && (typeof value !== "number" || !Number.isInteger(value))) {61      return { ok: false, error: repair(toolName, `parameter "${name}" must be an integer`) };62    }63    if (prop.enum !== undefined && typeof value === "string" && !prop.enum.includes(value)) {64      return {65        ok: false,66        error: repair(67          toolName,68          `parameter "${name}" must be one of ${prop.enum.map((v) => `"${v}"`).join(", ")}`,69        ),70      };71    }72  }7374  return { ok: true, value: record as P };75}7677/** Repair prose for conditionally-required parameters (e.g. process start without command). */78export function missingConditionalParam(toolName: string, param: string, when: string): string {79  return repair(toolName, `parameter "${param}" is required ${when}`);80}81