import type { StudioToolDefinition, StudioToolResult } from '../domain/types' import type { StudioRuntimeBackedToolContext } from '../runtime/tool-runtime-context' import { truncateToolText, toWorkspaceRelativePath } from './workspace-paths' import { applyWorkspacePatch } from './workspace-edits' interface ApplyPatchToolInput { path?: string file?: string patches?: Array<{ search: string; replace: string; replaceAll?: boolean }> } export function createStudioApplyPatchTool(): StudioToolDefinition { return { name: 'apply_patch', description: 'Apply structured search/replace patches to a workspace file.', category: 'edit', permission: 'apply_patch', allowedAgents: ['builder'], requiresTask: false, execute: async (input, context) => executeApplyPatchTool(input, context as StudioRuntimeBackedToolContext) } } async function executeApplyPatchTool( input: ApplyPatchToolInput, context: StudioRuntimeBackedToolContext ): Promise { const target = input.path ?? input.file if (!target || !Array.isArray(input.patches) || input.patches.length === 0) { throw new Error('Apply_patch tool requires "path" and non-empty "patches"') } const result = await applyWorkspacePatch({ baseDirectory: context.session.directory, targetPath: target, patches: input.patches }) const relativePath = toWorkspaceRelativePath(context.session.directory, result.absolutePath).replace(/\\/g, '/') const output = truncateToolText(result.content) return { title: `Patched ${relativePath}`, output: output.text, metadata: { path: relativePath, absolutePath: result.absolutePath, replacements: result.replacements, patchCount: input.patches.length, truncated: output.truncated } } }