compare_documents
Compare two DOCX documents to generate a tracked-changes output showing differences between original and revised versions.
Instructions
Compare two DOCX documents and produce a tracked-changes output document. Provide original_file_path + revised_file_path for standalone comparison, or file_path to compare session edits against the original.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| original_file_path | No | Path to the original DOCX file. | |
| revised_file_path | No | Path to the revised DOCX file. | |
| file_path | No | Path to the DOCX file. | |
| save_to_local_path | Yes | Path to save the tracked-changes DOCX output. | |
| author | No | Author name for track changes. Default: 'Comparison'. | |
| engine | No | Comparison engine. Default: 'auto'. |
Implementation Reference
- The `compare_documents_tool` function is the handler that manages the comparison logic. It supports two modes: comparing two specific files (two-file mode) or comparing a current document session state (session mode). It validates input, prepares document buffers, runs the comparison using `@usejunior/docx-core`'s `compareDocuments` function, and saves the resulting document.
export async function compareDocuments_tool( manager: SessionManager, params: { original_file_path?: string; revised_file_path?: string; file_path?: string; save_to_local_path: string; author?: string; engine?: string; }, ): Promise<ToolResponse> { try { const hasOriginal = typeof params.original_file_path === 'string' && params.original_file_path.trim().length > 0; const hasRevised = typeof params.revised_file_path === 'string' && params.revised_file_path.trim().length > 0; const hasSession = typeof params.file_path === 'string' && params.file_path.trim().length > 0; // Determine mode const twoFileMode = hasOriginal && hasRevised; const sessionMode = !twoFileMode && hasSession; if (!twoFileMode && !sessionMode) { return err( 'MISSING_PARAMS', 'Provide original_file_path + revised_file_path for two-file comparison, or file_path for session comparison.', 'Two-file mode compares two DOCX files. Session mode compares the current session state against the original.', ); } // Validate engine const engine = params.engine ?? 'auto'; if (engine !== 'auto' && engine !== 'atomizer') { if (engine === 'wmlcomparer') { return err('INVALID_ENGINE', "Engine 'wmlcomparer' is not supported.", "Use 'auto' or 'atomizer'."); } return err('INVALID_ENGINE', `Invalid engine: ${String(engine)}`, "Use 'auto' or 'atomizer'."); } const compareEngine: CompareOptions['engine'] = engine; const author = params.author ?? 'Comparison'; let originalBuffer: Buffer; let revisedBuffer: Buffer; let sessionMetadata: Record<string, unknown> = {}; let originalFilePath: string | undefined; let revisedFilePath: string | undefined; if (twoFileMode) { // Mode 1: two file paths const originalLoaded = await validateAndLoadDocxFromPath(manager, params.original_file_path!); if (!originalLoaded.ok) return originalLoaded.response; const revisedLoaded = await validateAndLoadDocxFromPath(manager, params.revised_file_path!); if (!revisedLoaded.ok) return revisedLoaded.response; originalBuffer = originalLoaded.content; revisedBuffer = revisedLoaded.content; originalFilePath = originalLoaded.normalizedPath; revisedFilePath = revisedLoaded.normalizedPath; } else { // Mode 2: session edits const resolved = await resolveSessionForTool(manager, params, { toolName: 'compare_documents' }); if (!resolved.ok) return resolved.response; const { session, metadata } = resolved; sessionMetadata = metadata; // Lazily generate comparison baselines if not yet available. await manager.ensureBaselines(session); originalBuffer = session.comparisonBaselineWithBookmarks ?? session.originalBuffer; const revised = await session.doc.toBuffer({ cleanBookmarks: false }); revisedBuffer = revised.buffer; originalFilePath = manager.normalizePath(session.originalPath); } // Run comparison const result = await runWithoutConsoleLog(() => compareDocuments(originalBuffer, revisedBuffer, { author, engine: compareEngine, reconstructionMode: DEFAULT_RECONSTRUCTION_MODE, }), ); // Validate and write output const savePath = expandPath(params.save_to_local_path); const writePolicy = await enforceWritePathPolicy(savePath); if (!writePolicy.ok) return writePolicy.response; await fs.mkdir(path.dirname(savePath), { recursive: true }); await fs.writeFile(savePath, new Uint8Array(result.document)); const response: Record<string, unknown> = { mode: twoFileMode ? 'two_file' : 'session', original_file_path: originalFilePath, revised_file_path: revisedFilePath, saved_to: savePath, size_bytes: result.document.length, engine_requested: compareEngine, engine_used: result.engine, author, stats: result.stats, reconstruction_mode_requested: result.reconstructionModeRequested, reconstruction_mode_used: result.reconstructionModeUsed, fallback_reason: result.fallbackReason, message: twoFileMode ? `Redline comparing '${path.basename(originalFilePath!)}' vs '${path.basename(revisedFilePath!)}' saved to ${savePath}` : `Redline of session edits saved to ${savePath}`, }; if (sessionMode) { return ok(mergeSessionResolutionMetadata(response, sessionMetadata)); } return ok(response); } catch (e: unknown) { const msg = errorMessage(e); if (String(errorCode(e) ?? '').toUpperCase() === 'EACCES') { return err('PERMISSION_DENIED', `Cannot write to: ${params.save_to_local_path}`, 'Try saving to ~/Downloads/ or ~/Documents/ instead.'); } return err('COMPARE_ERROR', `Comparison failed: ${msg}`); } }