$ cat node-template.py
D
Drive Content Reader
// Reads the processed text content of one or more Drive files of any type: extracted text for documents (PDF, DOCX, TXT, MD) and speaker-labelled transcripts for audio and video. Multiple items are merged into a single output.
Input
Storage
#drive#storage#document#audio#video#transcript#text#content
template.py
1"""drive-content-reader — read the processed content of one or more Drive items.23Works for any processed file type: the llms.txt endpoint behind4Gais.drive.read_text returns MinerU markdown for PDF/DOCX, a WhisperX5transcript (speakers + timestamps) for audio/video, and the raw text for6TXT/MD. Formerly "Drive Document Reader" (same template id, in-place upgrade).78Privileged Drive IO is delegated to the gais.drive SDK; this node imports no9HTTP client. Multiple items are deduplicated and merged; a single item that10fails (e.g. a folder or an unprocessed file) is soft-skipped so one bad id11does not fail the whole batch.12"""1314from __future__ import annotations1516import json17import re18import sys19import traceback20from typing import Any2122from gais import Gais, RemoteError, BackendTimeout2324MAX_ITEMS = 10025# Kept as "Document Separator" for backward compatibility with downstream26# instructions that split on it.27_SEPARATOR = "\n\n--- Document Separator ---\n\n"2829_UUID_RE = re.compile(30 r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",31 re.IGNORECASE,32)333435def _normalize_drive_item_id(value: Any) -> str:36 """Accept a bare UUID or a mention://drive_{file,folder}/<uuid> URI."""37 if not isinstance(value, str):38 raise ValueError(f"Drive item id must be a string, got {type(value).__name__}")39 s = value.strip()40 for prefix in ("mention://drive_file/", "mention://drive_folder/"):41 if s.startswith(prefix):42 s = s[len(prefix):]43 break44 if not _UUID_RE.match(s):45 raise ValueError(f"Drive item id is not a valid UUID: {value!r}")46 return s474849def process(inputs: dict[str, Any]) -> dict[str, Any]:50 """Read + merge processed content for one or many items via gais.drive.read_text."""51 raw = inputs.get("driveItemId")52 if not raw:53 raise ValueError("Required input 'driveItemId' not provided")5455 if isinstance(raw, str):56 candidate_ids = [raw]57 elif isinstance(raw, list):58 candidate_ids = raw59 else:60 raise ValueError(61 f"driveItemId must be a string or array, got {type(raw).__name__}"62 )6364 # Normalize (strip mention:// prefix, validate UUID) and dedupe.65 seen: set[str] = set()66 unique_ids: list[str] = []67 for candidate in candidate_ids:68 if not candidate:69 continue70 item_id = _normalize_drive_item_id(candidate)71 if item_id not in seen:72 seen.add(item_id)73 unique_ids.append(item_id)7475 if not unique_ids:76 raise ValueError("No valid Drive item IDs provided")77 if len(unique_ids) > MAX_ITEMS:78 raise ValueError(f"Too many Drive items: {len(unique_ids)} (max {MAX_ITEMS})")7980 # Per-item soft-skip on a Drive API error (folders, unprocessed files, etc.)81 # OR a per-item timeout, so one bad/slow id does not fail the whole batch.82 # BackendTimeout has no http_status, so record "timeout" for those.83 all_contents: list[str] = []84 skipped: list[dict[str, Any]] = []85 for item_id in unique_ids:86 try:87 result = Gais.drive.read_text(item_id=item_id)88 text = (result.text or "").strip()89 if text:90 all_contents.append(text)91 except (RemoteError, BackendTimeout) as e:92 skipped.append({"id": item_id, "status": getattr(e, "http_status", "timeout")})93 continue9495 if skipped:96 print(97 json.dumps({"warning": f"Skipped {len(skipped)} item(s)", "skipped": skipped}),98 file=sys.stderr,99 )100101 if not all_contents:102 failed = [s["id"] for s in skipped] if skipped else unique_ids103 raise ValueError(f"No Drive items could be read. Failed items: {failed}")104105 if len(unique_ids) > 1:106 return {"content": _SEPARATOR.join(all_contents)}107 return {"content": all_contents[0]}108109110def main() -> None:111 try:112 envelope = json.loads(sys.stdin.read() or "{}")113 if not isinstance(envelope, dict):114 envelope = {}115 inputs = envelope.get("inputs", {}) or {}116 json.dump(process(inputs), sys.stdout)117 except Exception as e:118 print(119 json.dumps({120 "error": str(e),121 "errorType": type(e).__name__,122 "traceback": traceback.format_exc(),123 }),124 file=sys.stderr,125 )126 sys.exit(1)127128129if __name__ == "__main__":130 main()131$ git log --oneline
v1.3.0
HEAD
2026-10-08