| """Feishu Document Tool -- read document content via Feishu/Lark API. |
| |
| Provides ``feishu_doc_read`` for reading document content as plain text. |
| Uses the same lazy-import + BaseRequest pattern as feishu_comment.py. |
| """ |
|
|
| import json |
| import logging |
| import threading |
|
|
| from tools.registry import registry, tool_error, tool_result |
|
|
| logger = logging.getLogger(__name__) |
|
|
| |
| _local = threading.local() |
|
|
|
|
| def set_client(client): |
| """Store a lark client for the current thread (called by feishu_comment).""" |
| _local.client = client |
|
|
|
|
| def get_client(): |
| """Return the lark client for the current thread, or None.""" |
| return getattr(_local, "client", None) |
|
|
|
|
| |
| |
| |
|
|
| _RAW_CONTENT_URI = "/open-apis/docx/v1/documents/:document_id/raw_content" |
|
|
| FEISHU_DOC_READ_SCHEMA = { |
| "name": "feishu_doc_read", |
| "description": ( |
| "Read the full content of a Feishu/Lark document as plain text. " |
| "Useful when you need more context beyond the quoted text in a comment." |
| ), |
| "parameters": { |
| "type": "object", |
| "properties": { |
| "doc_token": { |
| "type": "string", |
| "description": "The document token (from the document URL or comment context).", |
| }, |
| }, |
| "required": ["doc_token"], |
| }, |
| } |
|
|
|
|
| def _check_feishu(): |
| try: |
| import lark_oapi |
| return True |
| except ImportError: |
| return False |
|
|
|
|
| def _handle_feishu_doc_read(args: dict, **kwargs) -> str: |
| doc_token = args.get("doc_token", "").strip() |
| if not doc_token: |
| return tool_error("doc_token is required") |
|
|
| client = get_client() |
| if client is None: |
| return tool_error("Feishu client not available (not in a Feishu comment context)") |
|
|
| try: |
| from lark_oapi import AccessTokenType |
| from lark_oapi.core.enum import HttpMethod |
| from lark_oapi.core.model.base_request import BaseRequest |
| except ImportError: |
| return tool_error("lark_oapi not installed") |
|
|
| request = ( |
| BaseRequest.builder() |
| .http_method(HttpMethod.GET) |
| .uri(_RAW_CONTENT_URI) |
| .token_types({AccessTokenType.TENANT}) |
| .paths({"document_id": doc_token}) |
| .build() |
| ) |
|
|
| |
| |
| response = client.request(request) |
|
|
| code = getattr(response, "code", None) |
| if code != 0: |
| msg = getattr(response, "msg", "unknown error") |
| return tool_error(f"Failed to read document: code={code} msg={msg}") |
|
|
| raw = getattr(response, "raw", None) |
| if raw and hasattr(raw, "content"): |
| try: |
| body = json.loads(raw.content) |
| content = body.get("data", {}).get("content", "") |
| return tool_result(success=True, content=content) |
| except (json.JSONDecodeError, AttributeError): |
| pass |
|
|
| |
| data = getattr(response, "data", None) |
| if data: |
| if isinstance(data, dict): |
| content = data.get("content", "") |
| else: |
| content = getattr(data, "content", str(data)) |
| return tool_result(success=True, content=content) |
|
|
| return tool_error("No content returned from document API") |
|
|
|
|
| |
| |
| |
|
|
| registry.register( |
| name="feishu_doc_read", |
| toolset="feishu_doc", |
| schema=FEISHU_DOC_READ_SCHEMA, |
| handler=_handle_feishu_doc_read, |
| check_fn=_check_feishu, |
| requires_env=[], |
| is_async=False, |
| description="Read Feishu document content", |
| emoji="\U0001f4c4", |
| ) |
|
|