/** * Model-facing read, write, and edit tools over `ctx.fs`. This package owns schemas, validation, * read windows, formatting, and observation events, never a concrete provider. An optional * event policy supplies mutation guards; without one the tools use unconditional provider calls. * @module @deepseek-ai/dsh-tool-fs */ import type { Context } from 'cordis' import z from 'schemastery' import type {} from '@deepseek-ai/dsh-user-approval' import { applyReadTool, READ_LIMIT, STREAM_MIN_SIZE } from './read.ts' import { applyWriteTool } from './write.ts' import { applyEditTool } from './edit.ts' import { READ_MAX_BYTES, READ_MAX_LINE_LENGTH } from './read-render.ts' import { FsSandboxSurface } from './sandbox.ts' /** Cordis plugin name used by loader diagnostics. */ export const name = 'tool-fs' /** Services required by the filesystem tool suite. */ export const inject = ['tools', 'fs', 'systemPrompt'] /** Plugin config (all optional — `Config` supplies the defaults). */ export interface Config { /** Default and maximum number of lines returned by one `read` call. */ readLimit?: number /** Maximum characters returned for a single line before truncation. */ readMaxLineLength?: number /** Maximum bytes returned for the selected lines of one `read` call. */ readMaxBytes?: number /** Files at or above this size stream instead of loading whole into memory. */ readStreamMinSize?: number } export const Config: z = z.object({ readLimit: z.number().default(READ_LIMIT), readMaxLineLength: z.number().default(READ_MAX_LINE_LENGTH), readMaxBytes: z.number().default(READ_MAX_BYTES), readStreamMinSize: z.number().default(STREAM_MIN_SIZE), }) /** The shape after schemastery applied the defaults. */ type ResolvedConfig = Required /** Every read cap counts lines/chars/bytes — a positive integer, or windowing arithmetic misbehaves silently. */ function assertPositiveInteger(name: string, value: number): void { if (!Number.isInteger(value) || value < 1) { throw new Error(`tool-fs: ${name} must be a positive integer`) } } /** Register the full `read`/`write`/`edit` filesystem tool suite. */ export function apply(ctx: Context, config: Config): void { // schemastery (Config) has already filled every defaulted field. const resolved = config as ResolvedConfig assertPositiveInteger('readLimit', resolved.readLimit) assertPositiveInteger('readMaxLineLength', resolved.readMaxLineLength) assertPositiveInteger('readMaxBytes', resolved.readMaxBytes) assertPositiveInteger('readStreamMinSize', resolved.readStreamMinSize) applyReadTool(ctx, { limit: resolved.readLimit, maxLineLength: resolved.readMaxLineLength, maxBytes: resolved.readMaxBytes, streamMinSize: resolved.readStreamMinSize, }) // One escalation surface shared by both mutating tools: advertisement gating, // per-call mode stamping, and denial-marker mapping, all keyed off whether // the mounted ctx.fs confines (ctx.fs.sandboxMode). const sandbox = new FsSandboxSurface(ctx) applyWriteTool(ctx, sandbox) applyEditTool(ctx, sandbox) }