Something went wrong. Try again.
Something went wrong. Try again.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980/** * write — built-in tool to create/overwrite files (ARCHITECTURE §4.1). * * Writes `content` to `path` (resolved relative to cwd), creating parent * directories as needed. Overwrites existing files. Returns a status message * with byte count on success, or an `isError` result on permission/disk failure. * Never throws across the seam. */import { Type, type Static } from '@sinclair/typebox';import { Value } from '@sinclair/typebox/value';import { resolve, dirname } from 'node:path';import { mkdir } from 'node:fs/promises';import type { Tool, ToolContext, ToolResult } from '@/contracts/tool';import type { RenderDescriptor } from '@/contracts/render-descriptor';
// ── Input schema ──────────────────────────────────────────────────────────────
export const WriteInputSchema = Type.Object({ path: Type.String({ description: 'File path, resolved relative to cwd' }), content: Type.String({ description: 'Content to write to the file' }),});
export type WriteInput = Static<typeof WriteInputSchema>;
// ── Tool definition ───────────────────────────────────────────────────────────
export const writeTool: Tool<typeof WriteInputSchema> = { name: 'write', description: 'Write content to a file. Creates parent directories automatically. Overwrites if the file exists.', inputSchema: WriteInputSchema, isReadOnly: false,
async execute(input: WriteInput, ctx: ToolContext): Promise<ToolResult> { // Validate input against schema if (!Value.Check(WriteInputSchema, input)) { return { content: { kind: 'error', message: `Invalid input for write tool: ${JSON.stringify(input)}` }, isError: true, }; }
const filePath = resolve(ctx.cwd, input.path);
try { // Ensure parent directory exists await mkdir(dirname(filePath), { recursive: true });
await Bun.write(filePath, input.content);
const byteLength = new TextEncoder().encode(input.content).length; return { content: { kind: 'text', text: `wrote ${byteLength} bytes to ${input.path}` }, isError: false, // duoh76.6: absolute path for the FileMutationNotifier. The error paths below // don't set this (no mutation happened). mutatedFiles: [filePath], }; } catch (err) { const message = err instanceof Error ? err.message : String(err); return { content: { kind: 'error', message: `Failed to write ${filePath}: ${message}` }, isError: true, }; } },
onStart(input: WriteInput): RenderDescriptor { return { kind: 'status', label: `writing ${input.path}`, tone: 'info' }; },
onResult(input: WriteInput, result: ToolResult): RenderDescriptor { if (result.isError) { const content = result.content as { kind: 'error'; message: string }; return { kind: 'status', label: `error writing ${input.path}: ${content.message}`, tone: 'error' }; } return { kind: 'status', label: `wrote ${input.path}`, tone: 'info' }; },};