fix: list_documents crashes when context.documents is a list
When EspoCRM returns documents as a flat list (legacy shape) instead of
the expected {totalFiles, folders, ...} dict, list_documents crashed with
"list object has no attribute 'get'". Now handles both shapes gracefully.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -19,12 +19,83 @@ def register_document_tools(
|
||||
):
|
||||
"""Register document-related tools."""
|
||||
|
||||
async def list_documents() -> str:
|
||||
async def list_documents(refresh: bool = False) -> str:
|
||||
docs = context.get("documents", {})
|
||||
if refresh and case_id:
|
||||
try:
|
||||
docs = await crm.post("SmartAssistant/action/listDocuments", {"caseId": case_id})
|
||||
context["documents"] = docs
|
||||
except Exception as e:
|
||||
return fail(f"שגיאה ברענון רשימת מסמכים: {e}")
|
||||
|
||||
# Handle legacy shape where documents is a flat list of files
|
||||
if isinstance(docs, list):
|
||||
if not docs:
|
||||
return ok("לא נמצאו מסמכים בתיק.")
|
||||
lines = [f"נמצאו {len(docs)} מסמכים:"]
|
||||
for f in docs:
|
||||
if isinstance(f, dict):
|
||||
lines.append(f'📄 {f.get("name", "?")} — {f.get("path", "?")}')
|
||||
else:
|
||||
lines.append(f'📄 {f}')
|
||||
return ok("\n".join(lines))
|
||||
|
||||
if not docs.get("totalFiles"):
|
||||
return ok("לא נמצאו מסמכים")
|
||||
parts = ["לא נמצאו מסמכים בתיק."]
|
||||
resolved = docs.get("resolvedPath")
|
||||
source = docs.get("pathSource")
|
||||
exists = docs.get("folderExists")
|
||||
diag = docs.get("diagnostic")
|
||||
if resolved:
|
||||
parts.append(f"נתיב מחושב: '{resolved}' (מקור: {source or 'unknown'}, קיים: {exists}).")
|
||||
else:
|
||||
parts.append("לא ניתן לחשב נתיב תיקייה לתיק.")
|
||||
if diag:
|
||||
parts.append(f"סיבה: {diag}")
|
||||
parts.append("הצעה: find_case_folder(query=<שם איש קשר או מספר תיק>) כדי לאתר את התיקייה, ואז set_case_folder_path(path=...) לשמור.")
|
||||
return ok(" ".join(parts))
|
||||
return ok(json.dumps(docs, ensure_ascii=False))
|
||||
|
||||
async def browse_folder(path: str) -> str:
|
||||
try:
|
||||
result = await crm.post("SmartAssistant/action/browseFolder", {"path": path})
|
||||
entries = result.get("entries", [])
|
||||
if not entries:
|
||||
return ok(f'תיקייה "{path}" ריקה או לא נמצאה.')
|
||||
lines = [f'תוכן "{path}" ({len(entries)} פריטים):']
|
||||
for e in entries:
|
||||
kind = "📁" if e.get("type") == "folder" else "📄"
|
||||
size = f' ({e["size"]} bytes)' if e.get("size") else ""
|
||||
lines.append(f'{kind} {e.get("name")} — {e.get("path")}{size}')
|
||||
return ok("\n".join(lines))
|
||||
except Exception as e:
|
||||
return fail(f"שגיאה בסריקת תיקייה: {e}")
|
||||
|
||||
async def find_case_folder(query: str, limit: int = 20) -> str:
|
||||
try:
|
||||
result = await crm.post("SmartAssistant/action/findCaseFolder", {"query": query, "limit": limit})
|
||||
matches = result.get("matches", [])
|
||||
if not matches:
|
||||
return ok(f'לא נמצאו תיקיות התואמות "{query}" ב-root של האחסון.')
|
||||
lines = [f'נמצאו {len(matches)} תיקיות התואמות "{query}":']
|
||||
for m in matches:
|
||||
lines.append(f'• {m.get("name")} — {m.get("path")} (רמה {m.get("level")}, score={m.get("score")})')
|
||||
return ok("\n".join(lines))
|
||||
except Exception as e:
|
||||
return fail(f"שגיאה בחיפוש תיקייה: {e}")
|
||||
|
||||
async def set_case_folder_path(path: str) -> str:
|
||||
if not case_id:
|
||||
return fail("צריך להיות בתוך תיק כדי להגדיר נתיב תיקייה.")
|
||||
try:
|
||||
result = await crm.post("SmartAssistant/action/setCaseFolderPath", {"caseId": case_id, "path": path})
|
||||
if not result.get("success"):
|
||||
return fail(f"לא הצלחתי לשמור נתיב: {result}")
|
||||
context["documents"] = {}
|
||||
return ok(f'נתיב התיקייה של התיק עודכן ל-"{path}". הפעל list_documents(refresh=true) כדי לטעון את הרשימה.')
|
||||
except Exception as e:
|
||||
return fail(f"שגיאה בשמירת נתיב: {e}")
|
||||
|
||||
async def read_document(filePath: str) -> str:
|
||||
try:
|
||||
result = await crm.post("SmartAssistant/action/readDocument", {"filePath": filePath})
|
||||
@@ -87,11 +158,53 @@ def register_document_tools(
|
||||
|
||||
# Register
|
||||
tools["list_documents"] = {
|
||||
"description": "List documents in the case folder. Shows folder structure with file names and paths.",
|
||||
"parameters": {"type": "object", "properties": {}},
|
||||
"description": "List documents in the case folder. Shows folder structure with file names and paths. When totalFiles is 0, returns a diagnostic explaining why (missing path, folder doesn't exist, etc.). Pass refresh=true to re-fetch from EspoCRM after the folder path changes.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"refresh": {"type": "boolean", "description": "Re-query EspoCRM instead of using cached context (default false)"},
|
||||
},
|
||||
},
|
||||
"handler": list_documents,
|
||||
}
|
||||
|
||||
tools["browse_folder"] = {
|
||||
"description": "List the immediate contents of an arbitrary folder path on network storage. Escape hatch when list_documents returns no results — e.g. browse_folder('30-שוחר תום') to see what's really there.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"path": {"type": "string", "description": "Folder path relative to the storage root (e.g. '33-שוחר תום' or 'Documents/2026')"},
|
||||
},
|
||||
"required": ["path"],
|
||||
},
|
||||
"handler": browse_folder,
|
||||
}
|
||||
|
||||
tools["find_case_folder"] = {
|
||||
"description": "Search network-storage for folders whose name contains the query (e.g. contact name or case number). Scans root + one level. Returns ranked matches. Use when list_documents shows the wrong or missing path.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {"type": "string", "description": "Substring to match (Hebrew or English; case-insensitive)."},
|
||||
"limit": {"type": "integer", "description": "Max matches to return (default 20, max 50)"},
|
||||
},
|
||||
"required": ["query"],
|
||||
},
|
||||
"handler": find_case_folder,
|
||||
}
|
||||
|
||||
tools["set_case_folder_path"] = {
|
||||
"description": "Persist the given path as the case's networkStorageFolderPath. After this, list_documents(refresh=true) will read from the new path. Use only after find_case_folder or browse_folder confirms the path exists and matches this case.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"path": {"type": "string", "description": "Storage-relative folder path confirmed to exist."},
|
||||
},
|
||||
"required": ["path"],
|
||||
},
|
||||
"handler": set_case_folder_path,
|
||||
}
|
||||
|
||||
tools["read_document"] = {
|
||||
"description": "Read and extract text content from a document (PDF, DOCX, TXT). Requires the file path from list_documents.",
|
||||
"parameters": {
|
||||
|
||||
Reference in New Issue
Block a user