diff --git a/api/core/tools/tool/api_tool.py b/api/core/tools/tool/api_tool.py index 781eff13b4..d5a4bf20bd 100644 --- a/api/core/tools/tool/api_tool.py +++ b/api/core/tools/tool/api_tool.py @@ -67,9 +67,9 @@ class ApiTool(Tool): if 'api_key_header_prefix' in credentials: api_key_header_prefix = credentials['api_key_header_prefix'] - if api_key_header_prefix == 'basic': + if api_key_header_prefix == 'basic' and credentials['api_key_value']: credentials['api_key_value'] = f'Basic {credentials["api_key_value"]}' - elif api_key_header_prefix == 'bearer': + elif api_key_header_prefix == 'bearer' and credentials['api_key_value']: credentials['api_key_value'] = f'Bearer {credentials["api_key_value"]}' elif api_key_header_prefix == 'custom': pass @@ -184,21 +184,7 @@ class ApiTool(Tool): for name, property in properties.items(): if name in parameters: # convert type - try: - value = parameters[name] - if property['type'] == 'integer': - value = int(value) - elif property['type'] == 'number': - # check if it is a float - if '.' in value: - value = float(value) - else: - value = int(value) - elif property['type'] == 'boolean': - value = bool(value) - body[name] = value - except ValueError as e: - body[name] = parameters[name] + body[name] = self._convert_body_property_type(property, parameters[name]) elif name in required: raise ToolProviderCredentialValidationError( f"Missing required parameter {name} in operation {self.api_bundle.operation_id}" @@ -228,10 +214,6 @@ class ApiTool(Tool): elif method == 'put': response = ssrf_proxy.put(url, params=params, headers=headers, cookies=cookies, data=body, timeout=10, follow_redirects=True) elif method == 'delete': - """ - request body data is unsupported for DELETE method in standard http protocol - however, OpenAPI 3.0 supports request body data for DELETE method, so we support it here by using requests - """ response = ssrf_proxy.delete(url, params=params, headers=headers, cookies=cookies, data=body, timeout=10, allow_redirects=True) elif method == 'patch': response = ssrf_proxy.patch(url, params=params, headers=headers, cookies=cookies, data=body, timeout=10, follow_redirects=True) @@ -243,6 +225,66 @@ class ApiTool(Tool): raise ValueError(f'Invalid http method {method}') return response + + def _convert_body_property_any_of(self, property: dict[str, Any], value: Any, any_of: list[dict[str, Any]], max_recursive=10) -> Any: + if max_recursive <= 0: + raise Exception("Max recursion depth reached") + for option in any_of or []: + try: + if 'type' in option: + # Attempt to convert the value based on the type. + if option['type'] == 'integer' or option['type'] == 'int': + return int(value) + elif option['type'] == 'number': + if '.' in str(value): + return float(value) + else: + return int(value) + elif option['type'] == 'string': + return str(value) + elif option['type'] == 'boolean': + if str(value).lower() in ['true', '1']: + return True + elif str(value).lower() in ['false', '0']: + return False + else: + continue # Not a boolean, try next option + elif option['type'] == 'null' and not value: + return None + else: + continue # Unsupported type, try next option + elif 'anyOf' in option and isinstance(option['anyOf'], list): + # Recursive call to handle nested anyOf + return self._convert_body_property_any_of(property, value, option['anyOf'], max_recursive - 1) + except ValueError: + continue # Conversion failed, try next option + # If no option succeeded, you might want to return the value as is or raise an error + return value # or raise ValueError(f"Cannot convert value '{value}' to any specified type in anyOf") + + def _convert_body_property_type(self, property: dict[str, Any], value: Any) -> Any: + try: + if 'type' in property: + if property['type'] == 'integer' or property['type'] == 'int': + return int(value) + elif property['type'] == 'number': + # check if it is a float + if '.' in value: + return float(value) + else: + return int(value) + elif property['type'] == 'string': + return str(value) + elif property['type'] == 'boolean': + return bool(value) + elif property['type'] == 'null': + if value is None: + return None + else: + raise ValueError(f"Invalid type {property['type']} for property {property}") + elif 'anyOf' in property and isinstance(property['anyOf'], list): + return self._convert_body_property_any_of(property, value, property['anyOf']) + except ValueError as e: + return value def _invoke(self, user_id: str, tool_parameters: dict[str, Any]) -> ToolInvokeMessage | list[ToolInvokeMessage]: """ diff --git a/web/app/components/tools/edit-custom-collection-modal/test-api.tsx b/web/app/components/tools/edit-custom-collection-modal/test-api.tsx index 2f79044da0..620ceb8f48 100644 --- a/web/app/components/tools/edit-custom-collection-modal/test-api.tsx +++ b/web/app/components/tools/edit-custom-collection-modal/test-api.tsx @@ -43,7 +43,7 @@ const TestApi: FC = ({ } const data = { tool_name: toolName, - credentials: tempCredential, + credentials, schema_type: customCollection.schema_type, schema: customCollection.schema, parameters: parametersValue,