import { Type } from "@sinclair/typebox"; // Keep this policy in sync with packages/vera-session-tools/extensions/read-cap/policy.ts. export const MAX_READ_LINES = 200; const READ_CAP_DESCRIPTION = "Read a slice of a text file. Both `offset` (1-indexed start line) and `limit` (max 200) are REQUIRED. Reading whole files is forbidden — supply a tight range. If the file is larger than expected, call read again with a new offset."; const READ_CAP_PROMPT_SNIPPET = "read with required offset+limit (limit ≤ 200)."; const readCapParameters = Type.Object({ path: Type.String({ description: "Path to the file to read (relative or absolute)" }), offset: Type.Number({ minimum: 1, description: "Line number to start reading from (1-indexed)" }), limit: Type.Number({ minimum: 1, maximum: MAX_READ_LINES, description: "Maximum number of lines to read" }), }); function rejected(reason: string) { return { isError: true, content: [{ type: "text", text: `read rejected: ${reason}` }], }; } function validateReadCapParams(params: any): string | null { if (typeof params?.offset !== "number") return "missing required `offset` (1-indexed start line)"; if (typeof params?.limit !== "number") return "missing required `limit` (max 200 lines)"; if (params.offset < 1) return "`offset` must be >= 1"; if (params.limit < 1) return "`limit` must be >= 1"; if (params.limit > MAX_READ_LINES) { return `\`limit\` ${params.limit} exceeds cap ${MAX_READ_LINES} — call read multiple times with adjacent offsets to read more`; } return null; } export function wrapReadWithLineCap(builtinRead: any) { return { ...builtinRead, description: READ_CAP_DESCRIPTION, promptSnippet: READ_CAP_PROMPT_SNIPPET, parameters: readCapParameters, async execute(toolCallId: any, params: any, signal: any, onUpdate: any, ctx: any) { const rejectionReason = validateReadCapParams(params); if (rejectionReason) return rejected(rejectionReason); return builtinRead.execute(toolCallId, params, signal, onUpdate, ctx); }, }; }