feat(file-tool): add djvu conversion and extraction actions

This commit is contained in:
Apple
2026-02-15 03:11:55 -08:00
parent 3a565fd910
commit b2be937fbb
3 changed files with 102 additions and 1 deletions

View File

@@ -13,6 +13,7 @@ import hashlib
import base64
import csv
import tempfile
import subprocess
import httpx
from typing import Dict, List, Any, Optional
from dataclasses import dataclass
@@ -330,6 +331,7 @@ TOOL_DEFINITIONS = [
"pptx_create", "pptx_update",
"ods_create", "ods_update", "parquet_create", "parquet_update",
"csv_create", "csv_update", "pdf_fill", "pdf_merge", "pdf_split", "pdf_update",
"djvu_to_pdf", "djvu_extract_text",
"json_export", "yaml_export", "zip_bundle",
"text_create", "text_update", "markdown_create", "markdown_update",
"xml_export", "html_export",
@@ -671,6 +673,10 @@ class ToolManager:
return self._file_pdf_update(args)
if action == "pdf_fill":
return self._file_pdf_fill(args)
if action == "djvu_to_pdf":
return self._file_djvu_to_pdf(args)
if action == "djvu_extract_text":
return self._file_djvu_extract_text(args)
return ToolResult(success=False, result=None, error=f"Action not implemented yet: {action}")
@@ -2102,6 +2108,89 @@ class ToolManager:
file_name=file_name,
file_mime="application/pdf",
)
def _file_djvu_to_pdf(self, args: Dict[str, Any]) -> ToolResult:
src_b64 = args.get("file_base64")
if not src_b64:
return ToolResult(success=False, result=None, error="file_base64 is required for djvu_to_pdf")
file_name = self._sanitize_file_name(args.get("file_name"), "converted.pdf", force_ext=".pdf")
timeout_sec = max(5, min(int(args.get("timeout_sec") or 60), 300))
with tempfile.TemporaryDirectory(prefix="djvu2pdf_") as tmpdir:
src = os.path.join(tmpdir, "input.djvu")
out_pdf = os.path.join(tmpdir, "output.pdf")
with open(src, "wb") as f:
f.write(self._bytes_from_b64(src_b64))
try:
proc = subprocess.run(
["ddjvu", "-format=pdf", src, out_pdf],
capture_output=True,
text=True,
timeout=timeout_sec,
check=False,
)
except FileNotFoundError:
return ToolResult(success=False, result=None, error="ddjvu not found in runtime image")
except subprocess.TimeoutExpired:
return ToolResult(success=False, result=None, error=f"DJVU conversion timed out ({timeout_sec}s)")
if proc.returncode != 0 or not os.path.exists(out_pdf):
stderr = (proc.stderr or "").strip()
return ToolResult(success=False, result=None, error=f"ddjvu failed: {stderr or 'unknown error'}")
data = open(out_pdf, "rb").read()
if not data:
return ToolResult(success=False, result=None, error="ddjvu produced empty PDF")
return ToolResult(
success=True,
result={"message": f"DJVU converted to PDF: {file_name}"},
file_base64=self._b64_from_bytes(data),
file_name=file_name,
file_mime="application/pdf",
)
def _file_djvu_extract_text(self, args: Dict[str, Any]) -> ToolResult:
src_b64 = args.get("file_base64")
if not src_b64:
return ToolResult(success=False, result=None, error="file_base64 is required for djvu_extract_text")
file_name = self._sanitize_file_name(args.get("file_name"), "extracted.txt", force_ext=".txt")
timeout_sec = max(5, min(int(args.get("timeout_sec") or 60), 300))
with tempfile.TemporaryDirectory(prefix="djvutxt_") as tmpdir:
src = os.path.join(tmpdir, "input.djvu")
with open(src, "wb") as f:
f.write(self._bytes_from_b64(src_b64))
try:
proc = subprocess.run(
["djvutxt", src],
capture_output=True,
text=True,
timeout=timeout_sec,
check=False,
)
except FileNotFoundError:
return ToolResult(success=False, result=None, error="djvutxt not found in runtime image")
except subprocess.TimeoutExpired:
return ToolResult(success=False, result=None, error=f"DJVU text extraction timed out ({timeout_sec}s)")
if proc.returncode != 0:
stderr = (proc.stderr or "").strip()
return ToolResult(success=False, result=None, error=f"djvutxt failed: {stderr or 'unknown error'}")
text = proc.stdout or ""
msg = f"DJVU text extracted: {file_name}"
if not text.strip():
msg = f"DJVU has no extractable text layer, returned empty text file: {file_name}"
payload = text.encode("utf-8")
return ToolResult(
success=True,
result={"message": msg},
file_base64=self._b64_from_bytes(payload),
file_name=file_name,
file_mime="text/plain",
)
async def _memory_search(self, args: Dict, agent_id: str = None, chat_id: str = None, user_id: str = None) -> ToolResult:
"""Search in Qdrant vector memory using Router's memory_retrieval - PRIORITY 1"""