ToolStore Agent
feat: toolsets with @tool decorator, in-process execution, no auto-install
c37b3fd
Raw
History Blame Contribute Delete
7.34 kB
"""
pdf‑toolkit β€” Extract text, read metadata, list form fields, and merge PDFs.
================================================================================
Handles the messy reality of PDF parsing (multi‑column layouts,
Unicode, encrypted files) so agents don't have to guess.
"""
import io
import json
import sys
from pathlib import Path
try:
from toolstore.toolset import tool
except ImportError:
def tool(fn):
return fn # no‑op when toolstore package not installed
# ── pdf_extract ────────────────────────────────────────────────────────
@tool
def pdf_extract(*, filepath: str, first_page: int = 1,
last_page: int = 0) -> dict:
"""Extract readable text from a PDF using layout‑aware extraction.
Args:
filepath: Path to the PDF file.
first_page: First page to extract (1‑based, default 1).
last_page: Last page to extract (0 = all pages).
Returns:
dict with keys:
text β€” extracted text (pages separated by form‑feeds)
pages β€” number of pages processed
total β€” total pages in the document
"""
try:
import pdfplumber
except ImportError:
return {"error": "pdfplumber not installed β€” run: pip install pdfplumber"}
p = Path(filepath).expanduser().resolve()
if not p.exists():
return {"error": f"File not found: {p}"}
try:
pdf = pdfplumber.open(p)
except Exception as exc:
return {"error": f"Cannot open PDF: {exc}"}
total = len(pdf.pages)
if last_page == 0:
last_page = total
first_page = max(1, first_page)
last_page = min(last_page, total)
texts = []
for i in range(first_page - 1, last_page):
try:
page_text = pdf.pages[i].extract_text() or ""
texts.append(page_text)
except Exception:
texts.append("")
pdf.close()
return {"text": "\n\n".join(texts), "pages": len(texts),
"total": total, "range": f"{first_page}-{last_page}"}
# ── pdf_meta ───────────────────────────────────────────────────────────
@tool
def pdf_meta(*, filepath: str) -> dict:
"""Read metadata from a PDF file without extracting content.
Args:
filepath: Path to the PDF file.
Returns:
dict with keys:
filename, pages, title, author, subject, creator,
producer, encrypted, file_size_bytes
"""
p = Path(filepath).expanduser().resolve()
if not p.exists():
return {"error": f"File not found: {p}"}
file_size = p.stat().st_size
try:
from PyPDF2 import PdfReader
except ImportError:
return {"error": "PyPDF2 not installed β€” run: pip install PyPDF2"}
try:
reader = PdfReader(p)
except Exception as exc:
return {"error": f"Cannot read PDF: {exc}"}
info = reader.metadata or {}
return {
"filename": p.name,
"pages": len(reader.pages),
"title": (info.get("/Title") or "").strip() or None,
"author": (info.get("/Author") or "").strip() or None,
"subject": (info.get("/Subject") or "").strip() or None,
"creator": (info.get("/Creator") or "").strip() or None,
"producer": (info.get("/Producer") or "").strip() or None,
"encrypted": reader.is_encrypted,
"file_size_bytes": file_size,
}
# ── pdf_merge ──────────────────────────────────────────────────────────
@tool
def pdf_merge(*, inputs: list, output: str) -> dict:
"""Merge multiple PDF files into a single output file.
Args:
inputs: List of file paths to merge (order is preserved).
output: Path for the merged output PDF.
Returns:
dict with "written" (absolute path), "pages", and "sources" count.
"""
try:
from PyPDF2 import PdfReader, PdfWriter
except ImportError:
return {"error": "PyPDF2 not installed β€” run: pip install PyPDF2"}
if not inputs:
return {"error": "No input files provided"}
writer = PdfWriter()
total_pages = 0
sources = 0
for path_str in inputs:
p = Path(path_str).expanduser().resolve()
if not p.exists():
return {"error": f"Input not found: {p}"}
try:
reader = PdfReader(p)
except Exception as exc:
return {"error": f"Cannot read {p.name}: {exc}"}
for page in reader.pages:
writer.add_page(page)
total_pages += len(reader.pages)
sources += 1
out = Path(output).expanduser().resolve()
out.parent.mkdir(parents=True, exist_ok=True)
with open(out, "wb") as f:
writer.write(f)
return {"written": str(out), "pages": total_pages, "sources": sources}
# ── pdf_form_fields ────────────────────────────────────────────────────
@tool
def pdf_form_fields(*, filepath: str) -> dict:
"""List all fillable form fields in a PDF with their types and current values.
Args:
filepath: Path to the PDF file.
Returns:
dict with:
has_fields β€” True if the PDF contains any form fields
count β€” total number of fields found
fields β€” list of dicts: {name, type, value, flags}
"""
try:
from PyPDF2 import PdfReader
except ImportError:
return {"error": "PyPDF2 not installed β€” run: pip install PyPDF2"}
p = Path(filepath).expanduser().resolve()
if not p.exists():
return {"error": f"File not found: {p}"}
try:
reader = PdfReader(p)
except Exception as exc:
return {"error": f"Cannot read PDF: {exc}"}
fields = []
try:
raw_fields = reader.get_fields()
except Exception:
raw_fields = None
if raw_fields:
for name, info in raw_fields.items():
if info is None:
continue
field = {
"name": name,
"type": str(info.get("/FT", "unknown")).lstrip("/"),
"value": str(info.get("/V", "")) if info.get("/V") else None,
"flags": [],
}
ff = info.get("/Ff", 0)
if isinstance(ff, int):
if ff & 1: field["flags"].append("readOnly")
if ff & 2: field["flags"].append("required")
if ff & 4: field["flags"].append("noExport")
if ff & 0x10000: field["flags"].append("multiLine")
if ff & 0x20000: field["flags"].append("password")
opts = info.get("/Opt")
if opts:
field["options"] = [str(o) if not isinstance(o, list) else str(o[0])
for o in opts]
fields.append(field)
return {
"has_fields": len(fields) > 0,
"count": len(fields),
"fields": fields,
"filename": p.name,
}