Просмотр исходного кода

feat: add Mingdao HAP tool, implemented read and maintain HAP application worksheet data. (#6257)

Co-authored-by: takatost <takatost@gmail.com>
Ryan Tian 9 месяцев назад
Родитель
Сommit
5fcc2caeed

+ 16 - 0
api/core/tools/provider/builtin/hap/_assets/icon.svg

@@ -0,0 +1,16 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="80" height="80" viewBox="0 0 80 80">
+  <defs>
+    <clipPath id="clip-画板_16">
+      <rect width="80" height="80"/>
+    </clipPath>
+  </defs>
+  <g id="画板_16" data-name="画板 – 16" clip-path="url(#clip-画板_16)">
+    <g id="组_27" data-name="组 27">
+      <circle id="椭圆_13" data-name="椭圆 13" cx="40" cy="40" r="40" fill="#3e96f3"/>
+      <g id="组_26" data-name="组 26" transform="translate(16.361 16.674)">
+        <path id="路径_63230" data-name="路径 63230" d="M92.52,179.361c5.974,0,8.592-2.307,8.592-8.2,0-6.428.067-32.5.067-32.5h-.738a5.221,5.221,0,0,0-4.005,1.344H86.076A5.3,5.3,0,0,0,82,138.6h-.582v32.855c0,5.487,2.461,7.906,8.413,7.906Zm-5.348-33.93h8.189c-.022,4.457-.022,8.04-.022,10.952H87.15C87.15,153.471,87.173,149.888,87.173,145.431Zm-.067,25.823c0-2.016,0-3.315.022-9.451h8.189c-.022,6.316-.022,7.547-.022,9.429,0,1.792-1.052,2.71-2.842,2.71H89.7A2.447,2.447,0,0,1,87.106,171.253Z" transform="translate(-81.4 -137.973)" fill="#fff"/>
+        <path id="路径_63231" data-name="路径 63231" d="M172.508,168.341h12.373v6.495c0,2.508-.873,3.27-3.468,3.27H178.84v4.972h4.027c5.437,0,7.988-2.419,7.988-7.637V143.549c0-5.33-2.17-7.749-8.323-7.749h-7.719c-6.22,0-8.323,2.441-8.323,7.7v27.413c0,3.942-1.611,6.293-4.99,7.525v4.614c7.652-.851,10.986-4.838,10.986-12.027C172.508,170.178,172.508,169.26,172.508,168.341Zm0-24.255c0-2.038.85-2.844,3.132-2.844h6.332c1.991,0,2.909.9,2.909,2.934v4.994H172.508Zm0,10.5h12.373v8.331H172.508Z" transform="translate(-143.578 -135.8)" fill="#fff"/>
+      </g>
+    </g>
+  </g>
+</svg>

+ 8 - 0
api/core/tools/provider/builtin/hap/hap.py

@@ -0,0 +1,8 @@
+from typing import Any
+
+from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController
+
+
+class HapProvider(BuiltinToolProviderController):
+    def _validate_credentials(self, credentials: dict[str, Any]) -> None:
+        pass

+ 15 - 0
api/core/tools/provider/builtin/hap/hap.yaml

@@ -0,0 +1,15 @@
+identity:
+  author: Mingdao
+  name: hap
+  label:
+    en_US: HAP
+    zh_Hans: HAP
+    pt_BR: HAP
+  description:
+    en_US: "Hyper application platform that is particularly friendly to AI"
+    zh_Hans: "对 AI 特别友好的超级应用平台"
+    pt_BR: "Plataforma de aplicação hiper que é particularmente amigável à IA"
+  icon: icon.svg
+  tags:
+    - productivity
+credentials_for_provider:

+ 53 - 0
api/core/tools/provider/builtin/hap/tools/add_worksheet_record.py

@@ -0,0 +1,53 @@
+import json
+from typing import Any, Union
+
+import httpx
+
+from core.tools.entities.tool_entities import ToolInvokeMessage
+from core.tools.tool.builtin_tool import BuiltinTool
+
+
+class AddWorksheetRecordTool(BuiltinTool):
+
+    def _invoke(self, user_id: str, tool_parameters: dict[str, Any]
+                ) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
+        
+        appkey = tool_parameters.get('appkey', '')
+        if not appkey:
+            return self.create_text_message('Invalid parameter App Key')
+        sign = tool_parameters.get('sign', '')
+        if not sign:
+            return self.create_text_message('Invalid parameter Sign')
+        worksheet_id = tool_parameters.get('worksheet_id', '')
+        if not worksheet_id:
+            return self.create_text_message('Invalid parameter Worksheet ID')
+        record_data = tool_parameters.get('record_data', '')
+        if not record_data:
+            return self.create_text_message('Invalid parameter Record Row Data')
+        
+        host = tool_parameters.get('host', '')
+        if not host:
+            host = 'https://api.mingdao.com'
+        elif not host.startswith(("http://", "https://")):
+            return self.create_text_message('Invalid parameter Host Address')
+        else:
+            host = f"{host[:-1] if host.endswith('/') else host}/api"
+
+        url = f"{host}/v2/open/worksheet/addRow"
+        headers = {'Content-Type': 'application/json'}
+        payload = {"appKey": appkey, "sign": sign, "worksheetId": worksheet_id}
+
+        try:
+            payload['controls'] = json.loads(record_data)
+            res = httpx.post(url, headers=headers, json=payload, timeout=60)
+            res.raise_for_status()
+            res_json = res.json()
+            if res_json.get('error_code') != 1:
+                return self.create_text_message(f"Failed to add the new record. {res_json['error_msg']}")
+            return self.create_text_message(f"New record added successfully. The record ID is {res_json['data']}.")
+        except httpx.RequestError as e:
+            return self.create_text_message(f"Failed to add the new record, request error: {e}")
+        except json.JSONDecodeError as e:
+            return self.create_text_message(f"Failed to parse JSON response: {e}")
+        except Exception as e:
+            return self.create_text_message(f"Failed to add the new record, unexpected error: {e}")

+ 78 - 0
api/core/tools/provider/builtin/hap/tools/add_worksheet_record.yaml

@@ -0,0 +1,78 @@
+identity:
+  name: add_worksheet_record
+  author: Ryan Tian
+  label:
+    en_US: Add Worksheet Record
+    zh_Hans: 新增一条工作表记录
+description:
+  human:
+    en_US: Adds a new record to the specified worksheet
+    zh_Hans: 向指定的工作表新增一条记录数据
+  llm: A tool to append a new data entry into a specified worksheet.
+parameters:
+  - name: appkey
+    type: secret-input
+    required: true
+    label:
+      en_US: App Key
+      zh_Hans: App Key
+    human_description:
+      en_US: The AppKey parameter for the HAP application, typically found in the application's API documentation.
+      zh_Hans: HAP 应用的 AppKey 参数,可以从应用 API 文档中查找到
+    llm_description: the AppKey parameter for the HAP application
+    form: form
+
+  - name: sign
+    type: secret-input
+    required: true
+    label:
+      en_US: Sign
+      zh_Hans: Sign
+    human_description:
+      en_US: The Sign parameter for the HAP application
+      zh_Hans: HAP 应用的 Sign 参数
+    llm_description: the Sign parameter for the HAP application
+    form: form
+
+  - name: worksheet_id
+    type: string
+    required: true
+    label:
+      en_US: Worksheet ID
+      zh_Hans: 工作表 ID
+    human_description:
+      en_US: The ID of the specified worksheet
+      zh_Hans: 要获取字段信息的工作表 ID
+    llm_description: The ID of the specified worksheet which to get the fields information.
+    form: llm
+
+  - name: record_data
+    type: string
+    required: true
+    label:
+      en_US: Record Row Data
+      zh_Hans: 记录数据
+    human_description:
+      en_US: The fields with data of the specified record
+      zh_Hans: 要新增的记录数据,JSON 对象数组格式。数组元素属性:controlId-字段ID,value-字段值
+    llm_description: |
+      The fields with data of the specified record which to be created. It is in the format of an array of JSON objects, and the structure is defined as follows:
+      ```
+      type RowData = {
+        controlId: string; // Field ID to be updated
+        value: string; // Field value to be updated
+      }[];
+      ```
+    form: llm
+
+  - name: host
+    type: string
+    required: false
+    label:
+      en_US: Host Address
+      zh_Hans: 服务器地址
+    human_description:
+      en_US: The address for the privately deployed HAP server.
+      zh_Hans: 私有部署 HAP 服务器地址,公有云无需填写
+    llm_description: the address for the privately deployed HAP server.
+    form: form

+ 49 - 0
api/core/tools/provider/builtin/hap/tools/delete_worksheet_record.py

@@ -0,0 +1,49 @@
+from typing import Any, Union
+
+import httpx
+
+from core.tools.entities.tool_entities import ToolInvokeMessage
+from core.tools.tool.builtin_tool import BuiltinTool
+
+
+class DeleteWorksheetRecordTool(BuiltinTool):
+
+    def _invoke(self, user_id: str, tool_parameters: dict[str, Any]
+                ) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
+        
+        appkey = tool_parameters.get('appkey', '')
+        if not appkey:
+            return self.create_text_message('Invalid parameter App Key')
+        sign = tool_parameters.get('sign', '')
+        if not sign:
+            return self.create_text_message('Invalid parameter Sign')
+        worksheet_id = tool_parameters.get('worksheet_id', '')
+        if not worksheet_id:
+            return self.create_text_message('Invalid parameter Worksheet ID')
+        row_id = tool_parameters.get('row_id', '')
+        if not row_id:
+            return self.create_text_message('Invalid parameter Record Row ID')
+        
+        host = tool_parameters.get('host', '')
+        if not host:
+            host = 'https://api.mingdao.com'
+        elif not host.startswith(("http://", "https://")):
+            return self.create_text_message('Invalid parameter Host Address')
+        else:
+            host = f"{host[:-1] if host.endswith('/') else host}/api"
+
+        url = f"{host}/v2/open/worksheet/deleteRow"
+        headers = {'Content-Type': 'application/json'}
+        payload = {"appKey": appkey, "sign": sign, "worksheetId": worksheet_id, "rowId": row_id}
+
+        try:
+            res = httpx.post(url, headers=headers, json=payload, timeout=30)
+            res.raise_for_status()
+            res_json = res.json()
+            if res_json.get('error_code') != 1:
+                return self.create_text_message(f"Failed to delete the record. {res_json['error_msg']}")
+            return self.create_text_message("Successfully deleted the record.")
+        except httpx.RequestError as e:
+            return self.create_text_message(f"Failed to delete the record, request error: {e}")
+        except Exception as e:
+            return self.create_text_message(f"Failed to delete the record, unexpected error: {e}")

+ 71 - 0
api/core/tools/provider/builtin/hap/tools/delete_worksheet_record.yaml

@@ -0,0 +1,71 @@
+identity:
+  name: delete_worksheet_record
+  author: Ryan Tian
+  label:
+    en_US: Delete Worksheet Record
+    zh_Hans: 删除指定的一条工作表记录
+description:
+  human:
+    en_US: Deletes a single record from a worksheet based on the specified record row ID
+    zh_Hans: 根据指定的记录ID删除一条工作表记录数据
+  llm: A tool to remove a particular record from a worksheet by specifying its unique record identifier.
+parameters:
+  - name: appkey
+    type: secret-input
+    required: true
+    label:
+      en_US: App Key
+      zh_Hans: App Key
+    human_description:
+      en_US: The AppKey parameter for the HAP application, typically found in the application's API documentation.
+      zh_Hans: HAP 应用的 AppKey 参数,可以从应用 API 文档中查找到
+    llm_description: the AppKey parameter for the HAP application
+    form: form
+
+  - name: sign
+    type: secret-input
+    required: true
+    label:
+      en_US: Sign
+      zh_Hans: Sign
+    human_description:
+      en_US: The Sign parameter for the HAP application
+      zh_Hans: HAP 应用的 Sign 参数
+    llm_description: the Sign parameter for the HAP application
+    form: form
+
+  - name: worksheet_id
+    type: string
+    required: true
+    label:
+      en_US: Worksheet ID
+      zh_Hans: 工作表 ID
+    human_description:
+      en_US: The ID of the specified worksheet
+      zh_Hans: 要获取字段信息的工作表 ID
+    llm_description: The ID of the specified worksheet which to get the fields information.
+    form: llm
+
+  - name: row_id
+    type: string
+    required: true
+    label:
+      en_US: Record Row ID
+      zh_Hans: 记录 ID
+    human_description:
+      en_US: The row ID of the specified record
+      zh_Hans: 要删除的记录 ID
+    llm_description: The row ID of the specified record which to be deleted.
+    form: llm
+
+  - name: host
+    type: string
+    required: false
+    label:
+      en_US: Host Address
+      zh_Hans: 服务器地址
+    human_description:
+      en_US: The address for the privately deployed HAP server.
+      zh_Hans: 私有部署 HAP 服务器地址,公有云无需填写
+    llm_description: the address for the privately deployed HAP server.
+    form: form

+ 148 - 0
api/core/tools/provider/builtin/hap/tools/get_worksheet_fields.py

@@ -0,0 +1,148 @@
+import json
+from typing import Any, Union
+
+import httpx
+
+from core.tools.entities.tool_entities import ToolInvokeMessage
+from core.tools.tool.builtin_tool import BuiltinTool
+
+
+class GetWorksheetFieldsTool(BuiltinTool):
+
+    def _invoke(self, user_id: str, tool_parameters: dict[str, Any]
+                ) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
+        
+        appkey = tool_parameters.get('appkey', '')
+        if not appkey:
+            return self.create_text_message('Invalid parameter App Key')
+        sign = tool_parameters.get('sign', '')
+        if not sign:
+            return self.create_text_message('Invalid parameter Sign')
+        worksheet_id = tool_parameters.get('worksheet_id', '')
+        if not worksheet_id:
+            return self.create_text_message('Invalid parameter Worksheet ID')
+        
+        host = tool_parameters.get('host', '')
+        if not host:
+            host = 'https://api.mingdao.com'
+        elif not host.startswith(("http://", "https://")):
+            return self.create_text_message('Invalid parameter Host Address')
+        else:
+            host = f"{host[:-1] if host.endswith('/') else host}/api"
+
+        url = f"{host}/v2/open/worksheet/getWorksheetInfo"
+        headers = {'Content-Type': 'application/json'}
+        payload = {"appKey": appkey, "sign": sign, "worksheetId": worksheet_id}
+
+        try:
+            res = httpx.post(url, headers=headers, json=payload, timeout=60)
+            res.raise_for_status()
+            res_json = res.json()
+            if res_json.get('error_code') != 1:
+                return self.create_text_message(f"Failed to get the worksheet information. {res_json['error_msg']}")
+            
+            fields_json, fields_table = self.get_controls(res_json['data']['controls'])
+            result_type = tool_parameters.get('result_type', 'table')
+            return self.create_text_message(
+                text=json.dumps(fields_json, ensure_ascii=False) if result_type == 'json' else fields_table
+            )
+        except httpx.RequestError as e:
+            return self.create_text_message(f"Failed to get the worksheet information, request error: {e}")
+        except json.JSONDecodeError as e:
+            return self.create_text_message(f"Failed to parse JSON response: {e}")
+        except Exception as e:
+            return self.create_text_message(f"Failed to get the worksheet information, unexpected error: {e}")
+
+    def get_field_type_by_id(self, field_type_id: int) -> str:
+        field_type_map = {
+            2: "Text",
+            3: "Text-Phone",
+            4: "Text-Phone",
+            5: "Text-Email",
+            6: "Number",
+            7: "Text",
+            8: "Number",
+            9: "Option-Single Choice",
+            10: "Option-Multiple Choices",
+            11: "Option-Single Choice",
+            15: "Date",
+            16: "Date",
+            24: "Option-Region",
+            25: "Text",
+            26: "Option-Member",
+            27: "Option-Department",
+            28: "Number",
+            29: "Option-Linked Record",
+            30: "Unknown Type",
+            31: "Number",
+            32: "Text",
+            33: "Text",
+            35: "Option-Linked Record",
+            36: "Number-Yes1/No0",
+            37: "Number",
+            38: "Date",
+            40: "Location",
+            41: "Text",
+            46: "Time",
+            48: "Option-Organizational Role",
+            50: "Text",
+            51: "Query Record",
+        }
+        return field_type_map.get(field_type_id, '')
+
+    def get_controls(self, controls: list) -> dict:
+        fields = []
+        fields_list = ['|fieldId|fieldName|fieldType|fieldTypeId|description|options|','|'+'---|'*6]
+        for control in controls:
+            if control['type'] in self._get_ignore_types():
+                continue
+            field_type_id = control['type']
+            field_type = self.get_field_type_by_id(control['type'])
+            if field_type_id == 30:
+                source_type = control['sourceControl']['type']
+                if source_type in self._get_ignore_types():
+                    continue
+                else:
+                    field_type_id = source_type
+                    field_type = self.get_field_type_by_id(source_type)
+            field = {
+                'id': control['controlId'],
+                'name': control['controlName'],
+                'type': field_type,
+                'typeId': field_type_id,
+                'description': control['remark'].replace('\n', ' ').replace('\t', '  '),
+                'options': self._extract_options(control),
+            }
+            fields.append(field)
+            fields_list.append(f"|{field['id']}|{field['name']}|{field['type']}|{field['typeId']}|{field['description']}|{field['options'] if field['options'] else ''}|")
+
+        fields.append({
+            'id': 'ctime',
+            'name': 'Created Time',
+            'type': self.get_field_type_by_id(16),
+            'typeId': 16,
+            'description': '',
+            'options': []
+        })
+        fields_list.append("|ctime|Created Time|Date|16|||")
+        return fields, '\n'.join(fields_list)
+
+    def _extract_options(self, control: dict) -> list:
+        options = []
+        if control['type'] in [9, 10, 11]:
+            options.extend([{"key": opt['key'], "value": opt['value']} for opt in control.get('options', [])])
+        elif control['type'] in [28, 36]:
+            itemnames = control['advancedSetting'].get('itemnames')
+            if itemnames and itemnames.startswith('[{'):
+                try:
+                    options = json.loads(itemnames)
+                except json.JSONDecodeError:
+                    pass
+        elif control['type'] == 30:
+            source_type = control['sourceControl']['type']
+            if source_type not in self._get_ignore_types():
+                options.extend([{"key": opt['key'], "value": opt['value']} for opt in control.get('options', [])])
+        return options
+    
+    def _get_ignore_types(self):
+        return {14, 21, 22, 34, 42, 43, 45, 47, 49, 10010}

+ 80 - 0
api/core/tools/provider/builtin/hap/tools/get_worksheet_fields.yaml

@@ -0,0 +1,80 @@
+identity:
+  name: get_worksheet_fields
+  author: Ryan Tian
+  label:
+    en_US: Get Worksheet Fields
+    zh_Hans: 获取工作表字段结构
+description:
+  human:
+    en_US: Get fields information of the worksheet
+    zh_Hans: 获取指定工作表的所有字段结构信息
+  llm: A tool to get fields information of the specific worksheet.
+parameters:
+  - name: appkey
+    type: secret-input
+    required: true
+    label:
+      en_US: App Key
+      zh_Hans: App Key
+    human_description:
+      en_US: The AppKey parameter for the HAP application, typically found in the application's API documentation.
+      zh_Hans: HAP 应用的 AppKey 参数,可以从应用 API 文档中查找到
+    llm_description: the AppKey parameter for the HAP application
+    form: form
+
+  - name: sign
+    type: secret-input
+    required: true
+    label:
+      en_US: Sign
+      zh_Hans: Sign
+    human_description:
+      en_US: The Sign parameter for the HAP application
+      zh_Hans: HAP 应用的 Sign 参数
+    llm_description: the Sign parameter for the HAP application
+    form: form
+
+  - name: worksheet_id
+    type: string
+    required: true
+    label:
+      en_US: Worksheet ID
+      zh_Hans: 工作表 ID
+    human_description:
+      en_US: The ID of the specified worksheet
+      zh_Hans: 要获取字段信息的工作表 ID
+    llm_description: The ID of the specified worksheet which to get the fields information.
+    form: llm
+
+  - name: host
+    type: string
+    required: false
+    label:
+      en_US: Host Address
+      zh_Hans: 服务器地址
+    human_description:
+      en_US: The address for the privately deployed HAP server.
+      zh_Hans: 私有部署 HAP 服务器地址,公有云无需填写
+    llm_description: the address for the privately deployed HAP server.
+    form: form
+
+  - name: result_type
+    type: select
+    required: true
+    options:
+      - value: table
+        label:
+          en_US: table text
+          zh_Hans: 表格文本
+      - value: json
+        label:
+          en_US: json text
+          zh_Hans: JSON文本
+    default: table
+    label:
+      en_US: Result type
+      zh_Hans: 结果类型
+    human_description:
+      en_US: used for selecting the result type, table styled text or json text
+      zh_Hans: 用于选择结果类型,使用表格格式文本还是JSON格式文本
+    form: form

+ 130 - 0
api/core/tools/provider/builtin/hap/tools/get_worksheet_pivot_data.py

@@ -0,0 +1,130 @@
+import json
+from typing import Any, Union
+
+import httpx
+
+from core.tools.entities.tool_entities import ToolInvokeMessage
+from core.tools.tool.builtin_tool import BuiltinTool
+
+
+class GetWorksheetPivotDataTool(BuiltinTool):
+
+    def _invoke(self, user_id: str, tool_parameters: dict[str, Any]
+                ) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
+        
+        appkey = tool_parameters.get('appkey', '')
+        if not appkey:
+            return self.create_text_message('Invalid parameter App Key')
+        sign = tool_parameters.get('sign', '')
+        if not sign:
+            return self.create_text_message('Invalid parameter Sign')
+        worksheet_id = tool_parameters.get('worksheet_id', '')
+        if not worksheet_id:
+            return self.create_text_message('Invalid parameter Worksheet ID')
+        x_column_fields = tool_parameters.get('x_column_fields', '')
+        if not x_column_fields or not x_column_fields.startswith('['):
+            return self.create_text_message('Invalid parameter Column Fields')
+        y_row_fields = tool_parameters.get('y_row_fields', '')
+        if y_row_fields and not y_row_fields.strip().startswith('['):
+            return self.create_text_message('Invalid parameter Row Fields')
+        elif not y_row_fields:
+            y_row_fields = '[]'
+        value_fields = tool_parameters.get('value_fields', '')
+        if not value_fields or not value_fields.strip().startswith('['):
+            return self.create_text_message('Invalid parameter Value Fields')
+        
+        host = tool_parameters.get('host', '')
+        if not host:
+            host = 'https://api.mingdao.com'
+        elif not host.startswith(("http://", "https://")):
+            return self.create_text_message('Invalid parameter Host Address')
+        else:
+            host = f"{host[:-1] if host.endswith('/') else host}/api"
+
+        url = f"{host}/report/getPivotData"
+        headers = {'Content-Type': 'application/json'}
+        payload = {"appKey": appkey, "sign": sign, "worksheetId": worksheet_id, "options": {"showTotal": True}}
+
+        try:
+            x_column_fields = json.loads(x_column_fields)
+            payload['columns'] = x_column_fields
+            y_row_fields = json.loads(y_row_fields)
+            if y_row_fields: payload['rows'] = y_row_fields
+            value_fields = json.loads(value_fields)
+            payload['values'] = value_fields
+            sort_fields = tool_parameters.get('sort_fields', '')
+            if not sort_fields: sort_fields = '[]'
+            sort_fields = json.loads(sort_fields)
+            if sort_fields: payload['options']['sort'] = sort_fields
+            res = httpx.post(url, headers=headers, json=payload, timeout=60)
+            res.raise_for_status()
+            res_json = res.json()
+            if res_json.get('status') != 1:
+                return self.create_text_message(f"Failed to get the worksheet pivot data. {res_json['msg']}")
+            
+            pivot_json = self.generate_pivot_json(res_json['data'])
+            pivot_table = self.generate_pivot_table(res_json['data'])
+            result_type = tool_parameters.get('result_type', '')
+            text = pivot_table if result_type == 'table' else json.dumps(pivot_json, ensure_ascii=False)
+            return self.create_text_message(text)
+        except httpx.RequestError as e:
+            return self.create_text_message(f"Failed to get the worksheet pivot data, request error: {e}")
+        except json.JSONDecodeError as e:
+            return self.create_text_message(f"Failed to parse JSON response: {e}")
+        except Exception as e:
+            return self.create_text_message(f"Failed to get the worksheet pivot data, unexpected error: {e}")
+
+    def generate_pivot_table(self, data: dict[str, Any]) -> str:
+        columns = data['metadata']['columns']
+        rows = data['metadata']['rows']
+        values = data['metadata']['values']
+
+        rows_data = data['data']
+
+        header = ([row['displayName'] for row in rows] if rows else []) + [column['displayName'] for column in columns] + [value['displayName'] for value in values]
+        line = (['---'] * len(rows) if rows else []) + ['---'] * len(columns) + ['--:'] * len(values)
+
+        table = [header, line]
+        for row in rows_data:
+            row_data = [self.replace_pipe(row['rows'][r['controlId']]) for r in rows] if rows else []
+            row_data.extend([self.replace_pipe(row['columns'][column['controlId']]) for column in columns])
+            row_data.extend([self.replace_pipe(str(row['values'][value['controlId']])) for value in values])
+            table.append(row_data)
+
+        return '\n'.join([('|'+'|'.join(row) +'|') for row in table])
+    
+    def replace_pipe(self, text: str) -> str:
+        return text.replace('|', '▏').replace('\n', ' ')
+    
+    def generate_pivot_json(self, data: dict[str, Any]) -> dict:
+        fields = {
+            "x-axis": [
+                {"fieldId": column["controlId"], "fieldName": column["displayName"]}
+                for column in data["metadata"]["columns"]
+            ],
+            "y-axis": [
+                {"fieldId": row["controlId"], "fieldName": row["displayName"]}
+                for row in data["metadata"]["rows"]
+            ] if data["metadata"]["rows"] else [],
+            "values": [
+                {"fieldId": value["controlId"], "fieldName": value["displayName"]}
+                for value in data["metadata"]["values"]
+            ]
+        }
+        # fields = ([
+        #     {"fieldId": row["controlId"], "fieldName": row["displayName"]}
+        #     for row in data["metadata"]["rows"]
+        # ] if data["metadata"]["rows"] else []) + [
+        #     {"fieldId": column["controlId"], "fieldName": column["displayName"]}
+        #     for column in data["metadata"]["columns"]
+        # ] + [
+        #     {"fieldId": value["controlId"], "fieldName": value["displayName"]}
+        #     for value in data["metadata"]["values"]
+        # ]
+        rows = []
+        for row in data["data"]:
+            row_data = row["rows"] if row["rows"] else {}
+            row_data.update(row["columns"])
+            row_data.update(row["values"])
+            rows.append(row_data)
+        return {"fields": fields, "rows": rows, "summary": data["metadata"]["totalRow"]}

+ 248 - 0
api/core/tools/provider/builtin/hap/tools/get_worksheet_pivot_data.yaml

@@ -0,0 +1,248 @@
+identity:
+  name: get_worksheet_pivot_data
+  author: Ryan Tian
+  label:
+    en_US: Get Worksheet Pivot Data
+    zh_Hans: 获取工作表统计透视数据
+description:
+  human:
+    en_US: Retrieve statistical pivot table data from a specified worksheet
+    zh_Hans: 从指定的工作表中检索统计透视表数据
+  llm: A tool for extracting statistical pivot table data from a specific worksheet, providing summarized information for analysis and reporting purposes.
+parameters:
+  - name: appkey
+    type: secret-input
+    required: true
+    label:
+      en_US: App Key
+      zh_Hans: App Key
+    human_description:
+      en_US: The AppKey parameter for the HAP application, typically found in the application's API documentation.
+      zh_Hans: HAP 应用的 AppKey 参数,可以从应用 API 文档中查找到
+    llm_description: the AppKey parameter for the HAP application
+    form: form
+
+  - name: sign
+    type: secret-input
+    required: true
+    label:
+      en_US: Sign
+      zh_Hans: Sign
+    human_description:
+      en_US: The Sign parameter for the HAP application
+      zh_Hans: HAP 应用的 Sign 参数
+    llm_description: the Sign parameter for the HAP application
+    form: form
+
+  - name: worksheet_id
+    type: string
+    required: true
+    label:
+      en_US: Worksheet ID
+      zh_Hans: 工作表 ID
+    human_description:
+      en_US: The ID of the specified worksheet
+      zh_Hans: 要获取字段信息的工作表 ID
+    llm_description: The ID of the specified worksheet which to get the fields information.
+    form: llm
+
+  - name: x_column_fields
+    type: string
+    required: true
+    label:
+      en_US: Columns (X-axis)
+      zh_Hans: 统计列字段(X轴)
+    human_description:
+      en_US: The column fields that make up the pivot table's X-axis groups or other dimensions for the X-axis in pivot charts
+      zh_Hans: 组成透视表的统计列或者统计图表的X轴分组及X轴其它维度。JSON 对象数组格式,数组元素属性:controlId-列ID,displayName-显示名称,particleSize(可选)-字段类型是日期或者地区时,通过此参数设置统计维度(日期时间:1-日,2-周,3-月;地区:1-全国,2-省,3-市)
+    llm_description: |
+      This parameter allows you to specify the columns that make up the pivot table's X-axis groups or other dimensions for the X-axis in pivot charts. It is formatted as a JSON array, with its structure defined as follows:
+      ```
+      type XColumnFields = { // X-axis or column object array
+        controlId: string; // fieldId
+        displayName: string; // displayName
+        particleSize?: number; // field type is date or area, set the statistical dimension (date time: 1-day, 2-week, 3-month; area: 1-nation, 2-province, 3-city)
+      }[];
+      ```
+    form: llm
+
+  - name: y_row_fields
+    type: string
+    required: false
+    label:
+      en_US: Rows (Y-axis)
+      zh_Hans: 统计行字段(Y轴)
+    human_description:
+      en_US: The row fields that make up the pivot table's Y-axis groups or other dimensions for the Y-axis in pivot charts
+      zh_Hans: 组成透视表的统计行或者统计图表的Y轴分组及Y轴其它维度。JSON 对象数组格式,数组元素属性:controlId-列ID,displayName-显示名称,particleSize(可选)-字段类型是日期或者地区时,通过此参数设置统计维度(日期时间:1-日,2-周,3-月;地区:1-全国,2-省,3-市)
+    llm_description: |
+      This parameter allows you to specify the rows that make up the pivot table's Y-axis groups or other dimensions for the Y-axis in pivot charts. It is formatted as a JSON array, with its structure defined as follows:
+      ```
+      type YRowFields = { // Y-axis or row object array
+        controlId: string; // fieldId
+        displayName: string; // displayName
+        particleSize?: number; // field type is date or area, set the statistical dimension (date time: 1-day, 2-week, 3-month; area: 1-nation, 2-province, 3-city)
+      }[];
+      ```
+    form: llm
+
+  - name: value_fields
+    type: string
+    required: true
+    label:
+      en_US: Aggregated Values
+      zh_Hans: 统计值字段
+    human_description:
+      en_US: The aggregated value fields in the pivot table
+      zh_Hans: 透视表中经过聚合计算后的统计值字段。JSON 对象数组格式,数组元素属性:controlId-列ID,displayName-显示名称,aggregation-聚合方式(SUM,AVG,MIN,MAX,COUNT)
+    llm_description: |
+      This parameter allows you to specify the aggregated value fields in the pivot table. It is formatted as a JSON array, with its structure defined as follows:
+      ```
+      type ValueFields = { // aggregated value object array
+        controlId: string; // fieldId
+        displayName: string; // displayName
+        aggregation: string; // aggregation method, e.g.: SUM, AVG, MIN, MAX, COUNT
+      }[];
+      ```
+    form: llm
+
+  - name: filters
+    type: string
+    required: false
+    label:
+      en_US: Filter Set
+      zh_Hans: 筛选器组合
+    human_description:
+      en_US: A combination of filters applied to query records, formatted as a JSON array. See the application's API documentation for details on its structure and usage.
+      zh_Hans: 查询记录的筛选条件组合,格式为 JSON 数组,可以从应用 API 文档中了解参数结构详情
+    llm_description: |
+      This parameter allows you to specify a set of conditions that records must meet to be included in the result set. It is formatted as a JSON array, with its structure defined as follows:
+      ```
+      type Filters = { // filter object array
+        controlId: string; // fieldId
+        dataType: number; // fieldTypeId
+        spliceType: number; // condition concatenation method, 1: And, 2: Or
+        filterType: number; // expression type, refer to the <FilterTypeEnum Reference> for enumerable values
+        values?: string[]; // values in the condition, for option-type fields, multiple values can be passed
+        value?: string; // value in the condition, a single value can be passed according to the field type
+        dateRange?: number; // date range, mandatory when filterType is 17 or 18, refer to the <DateRangeEnum Reference> for enumerable values
+        minValue?: string; // minimum value for custom range
+        maxValue?: string; // maximum value for custom range
+        isAsc?: boolean; // ascending order, false: descending, true: ascending
+      }[];
+      ```
+      For option-type fields, if this option field has `options`, then you need to get the corresponding `key` value from the `options` in the current field information via `value`, and pass it into `values` in array format. Do not use the `options` value of other fields as input conditions.
+
+      ### FilterTypeEnum Reference
+      ```
+      Enum Value, Enum Character, Description
+      1, Like, Contains
+      2, Eq, Is (Equal)
+      3, Start, Starts With
+      4, End, Ends With
+      5, NotLike, Does Not Contain
+      6, Ne, Is Not (Not Equal)
+      7, IsEmpty, Empty
+      8, HasValue, Not Empty
+      11, Between, Within Range
+      12, NotBetween, Outside Range
+      13, Gt, Greater Than
+      14, Gte, Greater Than or Equal To
+      15, Lt, Less Than
+      16, Lte, Less Than or Equal To
+      17, DateEnum, Date Is
+      18, NotDateEnum, Date Is Not
+      21, MySelf, Owned by Me
+      22, UnRead, Unread
+      23, Sub, Owned by Subordinate
+      24, RCEq, Associated Field Is
+      25, RCNe, Associated Field Is Not
+      26, ArrEq, Array Equals
+      27, ArrNe, Array Does Not Equal
+      31, DateBetween, Date Within Range (can only be used with minValue and maxValue)
+      32, DateNotBetween, Date Not Within Range (can only be used with minValue and maxValue)
+      33, DateGt, Date Later Than
+      34, DateGte, Date Later Than or Equal To
+      35, DateLt, Date Earlier Than
+      36, DateLte, Date Earlier Than or Equal To
+      ```
+
+      ### DateRangeEnum Reference
+      ```
+      Enum Value, Enum Character, Description
+      1, Today, Today
+      2, Yesterday, Yesterday
+      3, Tomorrow, Tomorrow
+      4, ThisWeek, This Week
+      5, LastWeek, Last Week
+      6, NextWeek, Next Week
+      7, ThisMonth, This Month
+      8, LastMonth, Last Month
+      9, NextMonth, Next Month
+      12, ThisQuarter, This Quarter
+      13, LastQuarter, Last Quarter
+      14, NextQuarter, Next Quarter
+      15, ThisYear, This Year
+      16, LastYear, Last Year
+      17, NextYear, Next Year
+      18, Customize, Custom
+      21, Last7Day, Past 7 Days
+      22, Last14Day, Past 14 Days
+      23, Last30Day, Past 30 Days
+      31, Next7Day, Next 7 Days
+      32, Next14Day, Next 14 Days
+      33, Next33Day, Next 33 Days
+      ```
+    form: llm
+
+  - name: sort_fields
+    type: string
+    required: false
+    label:
+      en_US: Sort Fields
+      zh_Hans: 排序字段
+    human_description:
+      en_US: The fields to used for sorting
+      zh_Hans: 用于确定排序的字段,不超过3个
+    llm_description: |
+      This optional parameter specifies the unique identifier of the fields that will be used to sort the results. It is in the format of an array of JSON objects, and its structure is defined as follows:
+      ```
+      type SortByFields = {
+        controlId: string; // Field ID used for sorting
+        isAsc: boolean; // Sorting direction, true indicates ascending order, false indicates descending order
+      }[];
+      ```
+    form: llm
+
+  - name: host
+    type: string
+    required: false
+    label:
+      en_US: Host Address
+      zh_Hans: 服务器地址
+    human_description:
+      en_US: The address for the privately deployed HAP server.
+      zh_Hans: 私有部署 HAP 服务器地址,公有云无需填写
+    llm_description: the address for the privately deployed HAP server.
+    form: form
+
+  - name: result_type
+    type: select
+    required: true
+    options:
+      - value: table
+        label:
+          en_US: table text
+          zh_Hans: 表格文本
+      - value: json
+        label:
+          en_US: json text
+          zh_Hans: JSON文本
+    default: table
+    label:
+      en_US: Result type
+      zh_Hans: 结果类型
+    human_description:
+      en_US: used for selecting the result type, table styled text or json text
+      zh_Hans: 用于选择结果类型,使用表格格式文本还是JSON格式文本
+    form: form

+ 209 - 0
api/core/tools/provider/builtin/hap/tools/list_worksheet_records.py

@@ -0,0 +1,209 @@
+import json
+import re
+from typing import Any, Union
+
+import httpx
+
+from core.tools.entities.tool_entities import ToolInvokeMessage
+from core.tools.tool.builtin_tool import BuiltinTool
+
+
+class ListWorksheetRecordsTool(BuiltinTool):
+    def _invoke(self, user_id: str, tool_parameters: dict[str, Any]
+                ) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
+
+        appkey = tool_parameters.get('appkey', '')
+        if not appkey:
+            return self.create_text_message('Invalid parameter App Key')
+
+        sign = tool_parameters.get('sign', '')
+        if not sign:
+            return self.create_text_message('Invalid parameter Sign')
+
+        worksheet_id = tool_parameters.get('worksheet_id', '')
+        if not worksheet_id:
+            return self.create_text_message('Invalid parameter Worksheet ID')
+
+        host = tool_parameters.get('host', '')
+        if not host:
+            host = 'https://api.mingdao.com'
+        elif not (host.startswith("http://") or host.startswith("https://")):
+            return self.create_text_message('Invalid parameter Host Address')
+        else:
+            host = f"{host[:-1] if host.endswith('/') else host}/api"
+        
+        url_fields = f"{host}/v2/open/worksheet/getWorksheetInfo"
+        headers = {'Content-Type': 'application/json'}
+        payload = {"appKey": appkey, "sign": sign, "worksheetId": worksheet_id}
+
+        field_ids = tool_parameters.get('field_ids', '')
+
+        try:
+            res = httpx.post(url_fields, headers=headers, json=payload, timeout=30)
+            res_json = res.json()
+            if res.is_success:
+                if res_json['error_code'] != 1:
+                    return self.create_text_message("Failed to get the worksheet information. {}".format(res_json['error_msg']))
+                else:
+                    worksheet_name = res_json['data']['name']
+                    fields, schema, table_header = self.get_schema(res_json['data']['controls'], field_ids)
+            else:
+                return self.create_text_message(
+                    f"Failed to get the worksheet information, status code: {res.status_code}, response: {res.text}")
+        except Exception as e:
+            return self.create_text_message("Failed to get the worksheet information, something went wrong: {}".format(e))
+
+        if field_ids:
+            payload['controls'] = [v.strip() for v in field_ids.split(',')] if field_ids else []
+        filters = tool_parameters.get('filters', '')
+        if filters:
+            payload['filters'] = json.loads(filters)
+        sort_id = tool_parameters.get('sort_id', '')
+        sort_is_asc = tool_parameters.get('sort_is_asc', False)
+        if sort_id:
+            payload['sortId'] = sort_id
+            payload['isAsc'] = sort_is_asc
+        limit = tool_parameters.get('limit', 50)
+        payload['pageSize'] = limit
+        page_index = tool_parameters.get('page_index', 1)
+        payload['pageIndex'] = page_index
+        payload['useControlId'] = True
+        payload['listType'] = 1
+
+        url = f"{host}/v2/open/worksheet/getFilterRows"
+        try:
+            res = httpx.post(url, headers=headers, json=payload, timeout=90)
+            res_json = res.json()
+            if res.is_success:
+                if res_json['error_code'] != 1:
+                    return self.create_text_message("Failed to get the records. {}".format(res_json['error_msg']))
+                else:
+                    result = {
+                        "fields": fields,
+                        "rows": [],
+                        "total": res_json.get("data", {}).get("total"),
+                        "payload": {key: payload[key] for key in ['worksheetId', 'controls', 'filters', 'sortId', 'isAsc', 'pageSize', 'pageIndex'] if key in payload}
+                    }
+                    rows = res_json.get("data", {}).get("rows", [])
+                    result_type = tool_parameters.get('result_type', '')
+                    if not result_type: result_type = 'table'
+                    if result_type == 'json':
+                        for row in rows:
+                            result['rows'].append(self.get_row_field_value(row, schema))
+                        return self.create_text_message(json.dumps(result, ensure_ascii=False))
+                    else:
+                        result_text = f"Found {result['total']} rows in worksheet \"{worksheet_name}\"."
+                        if result['total'] > 0:
+                            result_text += f" The following are {result['total'] if result['total'] < limit else limit} pieces of data presented in a table format:\n\n{table_header}"
+                            for row in rows:
+                                result_values = []
+                                for f in fields:
+                                    result_values.append(self.handle_value_type(row[f['fieldId']], schema[f['fieldId']]))
+                                result_text += '\n|'+'|'.join(result_values)+'|'
+                        return self.create_text_message(result_text)
+            else:
+                return self.create_text_message(
+                    f"Failed to get the records, status code: {res.status_code}, response: {res.text}")
+        except Exception as e:
+            return self.create_text_message("Failed to get the records, something went wrong: {}".format(e))
+
+
+    def get_row_field_value(self, row: dict, schema: dict):
+        row_value = {"rowid": row["rowid"]}
+        for field in schema:
+            row_value[field] = self.handle_value_type(row[field], schema[field])
+        return row_value
+
+
+    def get_schema(self, controls: list, fieldids: str): 
+        allow_fields = {v.strip() for v in fieldids.split(',')} if fieldids else set()
+        fields = []
+        schema = {}
+        field_names = []
+        for control in controls:
+            control_type_id = self.get_real_type_id(control)
+            if (control_type_id in self._get_ignore_types()) or (allow_fields and not control['controlId'] in allow_fields):
+                continue
+            else:
+                fields.append({'fieldId': control['controlId'], 'fieldName': control['controlName']})
+                schema[control['controlId']] = {'typeId': control_type_id, 'options': self.set_option(control)}
+                field_names.append(control['controlName'])
+        if (not allow_fields or ('ctime' in allow_fields)):
+            fields.append({'fieldId': 'ctime', 'fieldName': 'Created Time'})
+            schema['ctime'] = {'typeId': 16, 'options': {}}
+            field_names.append("Created Time")
+        fields.append({'fieldId':'rowid', 'fieldName': 'Record Row ID'})
+        schema['rowid'] = {'typeId': 2, 'options': {}}
+        field_names.append("Record Row ID")
+        return fields, schema, '|'+'|'.join(field_names)+'|\n|'+'---|'*len(field_names)
+    
+    def get_real_type_id(self, control: dict) -> int:
+        return control['sourceControlType'] if control['type'] == 30 else control['type']
+    
+    def set_option(self, control: dict) -> dict:
+        options = {}
+        if control.get('options'):
+            options = {option['key']: option['value'] for option in control['options']}
+        elif control.get('advancedSetting', {}).get('itemnames'):
+            try:
+                itemnames = json.loads(control['advancedSetting']['itemnames'])
+                options = {item['key']: item['value'] for item in itemnames}
+            except json.JSONDecodeError:
+                pass
+        return options
+
+    def _get_ignore_types(self):
+        return {14, 21, 22, 34, 42, 43, 45, 47, 49, 10010}
+    
+    def handle_value_type(self, value, field):
+        type_id = field.get("typeId")
+        if type_id == 10:
+            value = value if isinstance(value, str) else "、".join(value)
+        elif type_id in [28, 36]:
+            value = field.get("options", {}).get(value, value)
+        elif type_id in [26, 27, 48, 14]:
+            value = self.process_value(value)
+        elif type_id in [35, 29]:
+            value = self.parse_cascade_or_associated(field, value)
+        elif type_id == 40:
+            value = self.parse_location(value)
+        return self.rich_text_to_plain_text(value) if value else ''
+
+    def process_value(self, value):
+        if isinstance(value, str):
+            if value.startswith("[{\"accountId\""):
+                value = json.loads(value)
+                value = ', '.join([item['fullname'] for item in value])
+            elif value.startswith("[{\"departmentId\""):
+                value = json.loads(value)
+                value = '、'.join([item['departmentName'] for item in value])
+            elif value.startswith("[{\"organizeId\""):
+                value = json.loads(value)
+                value = '、'.join([item['organizeName'] for item in value])
+            elif value.startswith("[{\"file_id\""):
+                value = ''
+            elif value == '[]':
+                value = ''
+        elif hasattr(value, 'accountId'):
+            value = value['fullname']
+        return value
+
+    def parse_cascade_or_associated(self, field, value):
+        if (field['typeId'] == 35 and value.startswith('[')) or (field['typeId'] == 29 and value.startswith('[{')):
+            value = json.loads(value)
+            value = value[0]['name'] if len(value) > 0 else ''
+        else:
+            value = ''
+        return value
+
+    def parse_location(self, value):
+        if len(value) > 10:
+            parsed_value = json.loads(value)
+            value = parsed_value.get("address", "")
+        else:
+            value = ""
+        return value
+
+    def rich_text_to_plain_text(self, rich_text):
+        text = re.sub(r'<[^>]+>', '', rich_text) if '<' in rich_text else rich_text
+        return text.replace("|", "▏").replace("\n", " ")

+ 226 - 0
api/core/tools/provider/builtin/hap/tools/list_worksheet_records.yaml

@@ -0,0 +1,226 @@
+identity:
+  name: list_worksheet_records
+  author: Ryan Tian
+  label:
+    en_US: List Worksheet Records
+    zh_Hans: 查询工作表记录数据
+description:
+  human:
+    en_US: List records from the worksheet
+    zh_Hans: 查询工作表的记录列表数据,一次最多1000行,可分页获取
+  llm: A tool to retrieve record data from the specific worksheet.
+parameters:
+  - name: appkey
+    type: secret-input
+    required: true
+    label:
+      en_US: App Key
+      zh_Hans: App Key
+    human_description:
+      en_US: The AppKey parameter for the HAP application, typically found in the application's API documentation.
+      zh_Hans: HAP 应用的 AppKey 参数,可以从应用 API 文档中查找到
+    llm_description: the AppKey parameter for the HAP application
+    form: form
+
+  - name: sign
+    type: secret-input
+    required: true
+    label:
+      en_US: Sign
+      zh_Hans: Sign
+    human_description:
+      en_US: The Sign parameter for the HAP application
+      zh_Hans: HAP 应用的 Sign 参数
+    llm_description: the Sign parameter for the HAP application
+    form: form
+
+  - name: worksheet_id
+    type: string
+    required: true
+    label:
+      en_US: Worksheet ID
+      zh_Hans: 工作表 ID
+    human_description:
+      en_US: The ID of the worksheet from which to retrieve record data
+      zh_Hans: 要获取记录数据的工作表 ID
+    llm_description: This parameter specifies the ID of the worksheet where the records are stored.
+    form: llm
+
+  - name: field_ids
+    type: string
+    required: false
+    label:
+      en_US: Field IDs
+      zh_Hans: 字段 ID 列表
+    human_description:
+      en_US: A comma-separated list of field IDs whose data to retrieve. If not provided, all fields' data will be fetched
+      zh_Hans: 要获取记录数据的字段 ID,多个 ID 间用英文逗号隔开,不传此参数则将获取所有字段的数据
+    llm_description: This optional parameter lets you specify a comma-separated list of field IDs. Unless the user explicitly requests to output the specified field in the question, this parameter should usually be omitted. If this parameter is omitted, the API will return data for all fields by default. When provided, only the data associated with these fields will be included in the response.
+    form: llm
+
+  - name: filters
+    type: string
+    required: false
+    label:
+      en_US: Filter Set
+      zh_Hans: 筛选器组合
+    human_description:
+      en_US: A combination of filters applied to query records, formatted as a JSON array. See the application's API documentation for details on its structure and usage.
+      zh_Hans: 查询记录的筛选条件组合,格式为 JSON 数组,可以从应用 API 文档中了解参数结构详情
+    llm_description: |
+      This parameter allows you to specify a set of conditions that records must meet to be included in the result set. It is formatted as a JSON array, with its structure defined as follows:
+      ```
+      type Filters = { // filter object array
+        controlId: string; // fieldId
+        dataType: number; // fieldTypeId
+        spliceType: number; // condition concatenation method, 1: And, 2: Or
+        filterType: number; // expression type, refer to the <FilterTypeEnum Reference> for enumerable values
+        values?: string[]; // values in the condition, for option-type fields, multiple values can be passed
+        value?: string; // value in the condition, a single value can be passed according to the field type
+        dateRange?: number; // date range, mandatory when filterType is 17 or 18, refer to the <DateRangeEnum Reference> for enumerable values
+        minValue?: string; // minimum value for custom range
+        maxValue?: string; // maximum value for custom range
+        isAsc?: boolean; // ascending order, false: descending, true: ascending
+      }[];
+      ```
+      For option-type fields, if this option field has `options`, then you need to get the corresponding `key` value from the `options` in the current field information via `value`, and pass it into `values` in array format. Do not use the `options` value of other fields as input conditions.
+
+      ### FilterTypeEnum Reference
+      ```
+      Enum Value, Enum Character, Description
+      1, Like, Contains(Include)
+      2, Eq, Is (Equal)
+      3, Start, Starts With
+      4, End, Ends With
+      5, NotLike, Does Not Contain(Not Include)
+      6, Ne, Is Not (Not Equal)
+      7, IsEmpty, Empty
+      8, HasValue, Not Empty
+      11, Between, Within Range(Belong to)
+      12, NotBetween, Outside Range(Not belong to)
+      13, Gt, Greater Than
+      14, Gte, Greater Than or Equal To
+      15, Lt, Less Than
+      16, Lte, Less Than or Equal To
+      17, DateEnum, Date Is
+      18, NotDateEnum, Date Is Not
+      24, RCEq, Associated Field Is
+      25, RCNe, Associated Field Is Not
+      26, ArrEq, Array Equals
+      27, ArrNe, Array Does Not Equal
+      31, DateBetween, Date Within Range (can only be used with minValue and maxValue)
+      32, DateNotBetween, Date Not Within Range (can only be used with minValue and maxValue)
+      33, DateGt, Date Later Than
+      34, DateGte, Date Later Than or Equal To
+      35, DateLt, Date Earlier Than
+      36, DateLte, Date Earlier Than or Equal To
+      ```
+
+      ### DateRangeEnum Reference
+      ```
+      Enum Value, Enum Character, Description
+      1, Today, Today
+      2, Yesterday, Yesterday
+      3, Tomorrow, Tomorrow
+      4, ThisWeek, This Week
+      5, LastWeek, Last Week
+      6, NextWeek, Next Week
+      7, ThisMonth, This Month
+      8, LastMonth, Last Month
+      9, NextMonth, Next Month
+      12, ThisQuarter, This Quarter
+      13, LastQuarter, Last Quarter
+      14, NextQuarter, Next Quarter
+      15, ThisYear, This Year
+      16, LastYear, Last Year
+      17, NextYear, Next Year
+      18, Customize, Custom
+      21, Last7Day, Past 7 Days
+      22, Last14Day, Past 14 Days
+      23, Last30Day, Past 30 Days
+      31, Next7Day, Next 7 Days
+      32, Next14Day, Next 14 Days
+      33, Next33Day, Next 33 Days
+      ```
+    form: llm
+
+  - name: sort_id
+    type: string
+    required: false
+    label:
+      en_US: Sort Field ID
+      zh_Hans: 排序字段 ID
+    human_description:
+      en_US: The ID of the field used for sorting
+      zh_Hans: 用以排序的字段 ID
+    llm_description: This optional parameter specifies the unique identifier of the field that will be used to sort the results. It should be set to the ID of an existing field within your data structure.
+    form: llm
+
+  - name: sort_is_asc
+    type: boolean
+    required: false
+    label:
+      en_US: Ascending Order
+      zh_Hans: 是否升序排列
+    human_description:
+      en_US: Determines whether the sorting is in ascending (true) or descending (false) order
+      zh_Hans: 排序字段的排序方式:true-升序,false-降序
+    llm_description: This optional parameter controls the direction of the sort. If set to true, the results will be sorted in ascending order; if false, they will be sorted in descending order.
+    form: llm
+
+  - name: limit
+    type: number
+    required: false
+    label:
+      en_US: Record Limit
+      zh_Hans: 记录数量限制
+    human_description:
+      en_US: The maximum number of records to retrieve
+      zh_Hans: 要获取的记录数量限制条数
+    llm_description: This optional parameter allows you to specify the maximum number of records that should be returned in the result set. When retrieving paginated record data, this parameter indicates the number of rows to fetch per page, and must be used in conjunction with the `page_index` parameter.
+    form: llm
+
+  - name: page_index
+    type: number
+    required: false
+    label:
+      en_US: Page Index
+      zh_Hans: 页码
+    human_description:
+      en_US: The page number when paginating through a list of records
+      zh_Hans: 分页读取记录列表时的页码
+    llm_description: This parameter is used when you need to paginate through a large set of records. The default value is 1, which refers to the first page. When it is used, the meaning of the `limit` parameter becomes the number of records per page.
+    form: llm
+
+  - name: host
+    type: string
+    required: false
+    label:
+      en_US: Host Address
+      zh_Hans: 服务器地址
+    human_description:
+      en_US: The address for the privately deployed HAP server.
+      zh_Hans: 私有部署 HAP 服务器地址,公有云无需填写
+    llm_description: the address for the privately deployed HAP server.
+    form: form
+
+  - name: result_type
+    type: select
+    required: true
+    options:
+      - value: table
+        label:
+          en_US: table text
+          zh_Hans: 表格文本
+      - value: json
+        label:
+          en_US: json text
+          zh_Hans: JSON文本
+    default: table
+    label:
+      en_US: Result type
+      zh_Hans: 结果类型
+    human_description:
+      en_US: used for selecting the result type, table styled text or json text
+      zh_Hans: 用于选择结果类型,使用表格格式文本还是JSON格式文本
+    form: form

+ 82 - 0
api/core/tools/provider/builtin/hap/tools/list_worksheets.py

@@ -0,0 +1,82 @@
+import json
+from typing import Any, Union
+
+import httpx
+
+from core.tools.entities.tool_entities import ToolInvokeMessage
+from core.tools.tool.builtin_tool import BuiltinTool
+
+
+class ListWorksheetsTool(BuiltinTool):
+
+    def _invoke(self, user_id: str, tool_parameters: dict[str, Any]
+                ) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
+
+        appkey = tool_parameters.get('appkey', '')
+        if not appkey:
+            return self.create_text_message('Invalid parameter App Key')
+        sign = tool_parameters.get('sign', '')
+        if not sign:
+            return self.create_text_message('Invalid parameter Sign')
+        
+        host = tool_parameters.get('host', '')
+        if not host:
+            host = 'https://api.mingdao.com'
+        elif not (host.startswith("http://") or host.startswith("https://")):
+            return self.create_text_message('Invalid parameter Host Address')
+        else:
+            host = f"{host[:-1] if host.endswith('/') else host}/api"
+        url = f"{host}/v1/open/app/get"
+
+        result_type = tool_parameters.get('result_type', '')
+        if not result_type:
+            result_type = 'table'
+
+        headers = { 'Content-Type': 'application/json' }
+        params = { "appKey": appkey, "sign": sign, }
+        try:
+            res = httpx.get(url, headers=headers, params=params, timeout=30)
+            res_json = res.json()
+            if res.is_success:
+                if res_json['error_code'] != 1:
+                    return self.create_text_message("Failed to access the application. {}".format(res_json['error_msg']))
+                else:
+                    if result_type == 'json':
+                        worksheets = []
+                        for section in res_json['data']['sections']:
+                            worksheets.extend(self._extract_worksheets(section, result_type))
+                        return self.create_text_message(text=json.dumps(worksheets, ensure_ascii=False))
+                    else:
+                        worksheets = '|worksheetId|worksheetName|description|\n|---|---|---|'
+                        for section in res_json['data']['sections']:
+                            worksheets += self._extract_worksheets(section, result_type)
+                        return self.create_text_message(worksheets)
+
+            else:
+                return self.create_text_message(
+                    f"Failed to list worksheets, status code: {res.status_code}, response: {res.text}")
+        except Exception as e:
+            return self.create_text_message("Failed to list worksheets, something went wrong: {}".format(e))
+
+    def _extract_worksheets(self, section, type):
+        items = []
+        tables = ''
+        for item in section.get('items', []):
+            if item.get('type') == 0 and (not 'notes' in item or item.get('notes') != 'NO'):
+                if type == 'json':
+                    filtered_item = {
+                        'id': item['id'],
+                        'name': item['name'],
+                        'notes': item.get('notes', '')
+                    }
+                    items.append(filtered_item)
+                else:
+                    tables += f"\n|{item['id']}|{item['name']}|{item.get('notes', '')}|"
+
+        for child_section in section.get('childSections', []):
+            if type == 'json':
+                items.extend(self._extract_worksheets(child_section, 'json'))
+            else:
+                tables += self._extract_worksheets(child_section, 'table')
+        
+        return items if type == 'json' else tables

+ 68 - 0
api/core/tools/provider/builtin/hap/tools/list_worksheets.yaml

@@ -0,0 +1,68 @@
+identity:
+  name: list_worksheets
+  author: Ryan Tian
+  label:
+    en_US: List Worksheets
+    zh_Hans: 获取应用下所有工作表
+description:
+  human:
+    en_US: List worksheets within an application
+    zh_Hans: 获取应用下的所有工作表和说明信息
+  llm: A tool to list worksheets info within an application, imported parameter is AppKey and Sign of the application.
+parameters:
+  - name: appkey
+    type: secret-input
+    required: true
+    label:
+      en_US: App Key
+      zh_Hans: App Key
+    human_description:
+      en_US: The AppKey parameter for the HAP application, typically found in the application's API documentation.
+      zh_Hans: HAP 应用的 AppKey 参数,可以从应用 API 文档中查找到
+    llm_description: the AppKey parameter for the HAP application
+    form: form
+
+  - name: sign
+    type: secret-input
+    required: true
+    label:
+      en_US: Sign
+      zh_Hans: Sign
+    human_description:
+      en_US: The Sign parameter for the HAP application
+      zh_Hans: HAP 应用的 Sign 参数
+    llm_description: the Sign parameter for the HAP application
+    form: form
+
+  - name: host
+    type: string
+    required: false
+    label:
+      en_US: Host Address
+      zh_Hans: 服务器地址
+    human_description:
+      en_US: The address for the privately deployed HAP server.
+      zh_Hans: 私有部署 HAP 服务器地址,公有云无需填写
+    llm_description: the address for the privately deployed HAP server.
+    form: form
+
+  - name: result_type
+    type: select
+    required: true
+    options:
+      - value: table
+        label:
+          en_US: table text
+          zh_Hans: 表格文本
+      - value: json
+        label:
+          en_US: json text
+          zh_Hans: JSON文本
+    default: table
+    label:
+      en_US: Result type
+      zh_Hans: 结果类型
+    human_description:
+      en_US: used for selecting the result type, table styled text or json text
+      zh_Hans: 用于选择结果类型,使用表格格式文本还是JSON格式文本
+    form: form

+ 56 - 0
api/core/tools/provider/builtin/hap/tools/update_worksheet_record.py

@@ -0,0 +1,56 @@
+import json
+from typing import Any, Union
+
+import httpx
+
+from core.tools.entities.tool_entities import ToolInvokeMessage
+from core.tools.tool.builtin_tool import BuiltinTool
+
+
+class UpdateWorksheetRecordTool(BuiltinTool):
+
+    def _invoke(self, user_id: str, tool_parameters: dict[str, Any]
+                ) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
+        
+        appkey = tool_parameters.get('appkey', '')
+        if not appkey:
+            return self.create_text_message('Invalid parameter App Key')
+        sign = tool_parameters.get('sign', '')
+        if not sign:
+            return self.create_text_message('Invalid parameter Sign')
+        worksheet_id = tool_parameters.get('worksheet_id', '')
+        if not worksheet_id:
+            return self.create_text_message('Invalid parameter Worksheet ID')
+        row_id = tool_parameters.get('row_id', '')
+        if not row_id:
+            return self.create_text_message('Invalid parameter Record Row ID')
+        record_data = tool_parameters.get('record_data', '')
+        if not record_data:
+            return self.create_text_message('Invalid parameter Record Row Data')
+        
+        host = tool_parameters.get('host', '')
+        if not host:
+            host = 'https://api.mingdao.com'
+        elif not host.startswith(("http://", "https://")):
+            return self.create_text_message('Invalid parameter Host Address')
+        else:
+            host = f"{host[:-1] if host.endswith('/') else host}/api"
+
+        url = f"{host}/v2/open/worksheet/editRow"
+        headers = {'Content-Type': 'application/json'}
+        payload = {"appKey": appkey, "sign": sign, "worksheetId": worksheet_id, "rowId": row_id}
+
+        try:
+            payload['controls'] = json.loads(record_data)
+            res = httpx.post(url, headers=headers, json=payload, timeout=60)
+            res.raise_for_status()
+            res_json = res.json()
+            if res_json.get('error_code') != 1:
+                return self.create_text_message(f"Failed to update the record. {res_json['error_msg']}")
+            return self.create_text_message("Record updated successfully.")
+        except httpx.RequestError as e:
+            return self.create_text_message(f"Failed to update the record, request error: {e}")
+        except json.JSONDecodeError as e:
+            return self.create_text_message(f"Failed to parse JSON response: {e}")
+        except Exception as e:
+            return self.create_text_message(f"Failed to update the record, unexpected error: {e}")

+ 90 - 0
api/core/tools/provider/builtin/hap/tools/update_worksheet_record.yaml

@@ -0,0 +1,90 @@
+identity:
+  name: update_worksheet_record
+  author: Ryan Tian
+  label:
+    en_US: Update Worksheet Record
+    zh_Hans: 更新指定的一条工作表记录
+description:
+  human:
+    en_US: Updates a single record in a worksheet based on the specified record row ID
+    zh_Hans: 根据指定的记录ID更新一条工作表记录数据
+  llm: A tool to modify existing information within a particular record of a worksheet by referencing its unique identifier.
+parameters:
+  - name: appkey
+    type: secret-input
+    required: true
+    label:
+      en_US: App Key
+      zh_Hans: App Key
+    human_description:
+      en_US: The AppKey parameter for the HAP application, typically found in the application's API documentation.
+      zh_Hans: HAP 应用的 AppKey 参数,可以从应用 API 文档中查找到
+    llm_description: the AppKey parameter for the HAP application
+    form: form
+
+  - name: sign
+    type: secret-input
+    required: true
+    label:
+      en_US: Sign
+      zh_Hans: Sign
+    human_description:
+      en_US: The Sign parameter for the HAP application
+      zh_Hans: HAP 应用的 Sign 参数
+    llm_description: the Sign parameter for the HAP application
+    form: form
+
+  - name: worksheet_id
+    type: string
+    required: true
+    label:
+      en_US: Worksheet ID
+      zh_Hans: 工作表 ID
+    human_description:
+      en_US: The ID of the specified worksheet
+      zh_Hans: 要获取字段信息的工作表 ID
+    llm_description: The ID of the specified worksheet which to get the fields information.
+    form: llm
+
+  - name: row_id
+    type: string
+    required: true
+    label:
+      en_US: Record Row ID
+      zh_Hans: 记录 ID
+    human_description:
+      en_US: The row ID of the specified record
+      zh_Hans: 要更新的记录 ID
+    llm_description: The row ID of the specified record which to be updated.
+    form: llm
+
+  - name: record_data
+    type: string
+    required: true
+    label:
+      en_US: Record Row Data
+      zh_Hans: 记录数据
+    human_description:
+      en_US: The fields with data of the specified record
+      zh_Hans: 要更新的记录数据,JSON 对象数组格式。数组元素属性:controlId-字段ID,value-字段值
+    llm_description: |
+      The fields with data of the specified record which to be updated. It is in the format of an array of JSON objects, and the structure is defined as follows:
+      ```
+      type RowData = {
+        controlId: string; // Field ID to be updated
+        value: string; // Field value to be updated
+      }[];
+      ```
+    form: llm
+
+  - name: host
+    type: string
+    required: false
+    label:
+      en_US: Host Address
+      zh_Hans: 服务器地址
+    human_description:
+      en_US: The address for the privately deployed HAP server.
+      zh_Hans: 私有部署 HAP 服务器地址,公有云无需填写
+    llm_description: the address for the privately deployed HAP server.
+    form: form