$ cat node-template.py
D
Drive Document Reader
// Reads one or more documents from Drive and returns their text content. Multiple documents are merged into a single output.
Input
Storage
#drive#storage#document#text
template.py
1"""drive-document-reader — read one or more documents' text from Drive.23Migrated from the legacy monolithic template to the new multi-file format.4Privileged Drive IO is delegated to the gais.drive SDK (Gais.drive.read_text);5this node imports no HTTP client. Multiple documents are deduplicated and6merged; a single item that fails (e.g. a folder or an unprocessed doc) is7soft-skipped so one bad id does not fail the whole batch.8"""910from __future__ import annotations1112import json13import re14import sys15import traceback16from typing import Any1718from gais import Gais, RemoteError, BackendTimeout1920MAX_DOCUMENTS = 10021_SEPARATOR = "\n\n--- Document Separator ---\n\n"2223_UUID_RE = re.compile(24 r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",25 re.IGNORECASE,26)272829def _normalize_drive_item_id(value: Any) -> str:30 """Accept a bare UUID or a mention://drive_{file,folder}/<uuid> URI."""31 if not isinstance(value, str):32 raise ValueError(f"Drive item id must be a string, got {type(value).__name__}")33 s = value.strip()34 for prefix in ("mention://drive_file/", "mention://drive_folder/"):35 if s.startswith(prefix):36 s = s[len(prefix):]37 break38 if not _UUID_RE.match(s):39 raise ValueError(f"Drive item id is not a valid UUID: {value!r}")40 return s414243def process(inputs: dict[str, Any]) -> dict[str, Any]:44 """Read + merge text for one or many documents via gais.drive.read_text."""45 raw = inputs.get("driveItemId")46 if not raw:47 raise ValueError("Required input 'driveItemId' not provided")4849 if isinstance(raw, str):50 candidate_ids = [raw]51 elif isinstance(raw, list):52 candidate_ids = raw53 else:54 raise ValueError(55 f"driveItemId must be a string or array, got {type(raw).__name__}"56 )5758 # Normalize (strip mention:// prefix, validate UUID) and dedupe.59 seen: set[str] = set()60 unique_ids: list[str] = []61 for candidate in candidate_ids:62 if not candidate:63 continue64 item_id = _normalize_drive_item_id(candidate)65 if item_id not in seen:66 seen.add(item_id)67 unique_ids.append(item_id)6869 if not unique_ids:70 raise ValueError("No valid document IDs provided")71 if len(unique_ids) > MAX_DOCUMENTS:72 raise ValueError(f"Too many documents: {len(unique_ids)} (max {MAX_DOCUMENTS})")7374 # Per-item soft-skip on a Drive API error (folders, unprocessed docs, etc.)75 # OR a per-item timeout, so one bad/slow id does not fail the whole batch.76 # BackendTimeout has no http_status, so record "timeout" for those.77 all_contents: list[str] = []78 skipped: list[dict[str, Any]] = []79 for item_id in unique_ids:80 try:81 result = Gais.drive.read_text(item_id=item_id)82 text = (result.text or "").strip()83 if text:84 all_contents.append(text)85 except (RemoteError, BackendTimeout) as e:86 skipped.append({"id": item_id, "status": getattr(e, "http_status", "timeout")})87 continue8889 if skipped:90 print(91 json.dumps({"warning": f"Skipped {len(skipped)} item(s)", "skipped": skipped}),92 file=sys.stderr,93 )9495 if not all_contents:96 failed = [s["id"] for s in skipped] if skipped else unique_ids97 raise ValueError(f"No documents could be read. Failed items: {failed}")9899 if len(unique_ids) > 1:100 return {"content": _SEPARATOR.join(all_contents)}101 return {"content": all_contents[0]}102103104def main() -> None:105 try:106 envelope = json.loads(sys.stdin.read() or "{}")107 if not isinstance(envelope, dict):108 envelope = {}109 inputs = envelope.get("inputs", {}) or {}110 json.dump(process(inputs), sys.stdout)111 except Exception as e:112 print(113 json.dumps({114 "error": str(e),115 "errorType": type(e).__name__,116 "traceback": traceback.format_exc(),117 }),118 file=sys.stderr,119 )120 sys.exit(1)121122123if __name__ == "__main__":124 main()$ git log --oneline
v1.2.0
HEAD
2026-08-18v1.0.22026-05-22
v1.0.12026-05-07
v1.0.02026-04-09