diff --git a/autogpt_platform/backend/backend/api/features/chat/tools/helpers.py b/autogpt_platform/backend/backend/api/features/chat/tools/helpers.py index c2d2d16769..8c4f7df28a 100644 --- a/autogpt_platform/backend/backend/api/features/chat/tools/helpers.py +++ b/autogpt_platform/backend/backend/api/features/chat/tools/helpers.py @@ -34,6 +34,10 @@ def get_inputs_from_schema( Returns: List of dicts with field info (name, title, type, description, required, default) """ + # Safety check: original code returned [] if input_schema wasn't a dict + if not isinstance(input_schema, dict): + return [] + exclude = exclude_fields or set() properties = input_schema.get("properties", {}) required = set(input_schema.get("required", [])) diff --git a/autogpt_platform/backend/backend/api/features/chat/tools/run_block.py b/autogpt_platform/backend/backend/api/features/chat/tools/run_block.py index c4e6e5ffc4..0522f6fc98 100644 --- a/autogpt_platform/backend/backend/api/features/chat/tools/run_block.py +++ b/autogpt_platform/backend/backend/api/features/chat/tools/run_block.py @@ -138,7 +138,11 @@ class RunBlockTool(BaseTool): if not requirements: return {}, [] - return await match_credentials_to_requirements(user_id, requirements) + # Note: check_scopes=False preserves original run_block behavior which + # didn't verify OAuth2 scopes. Graph matching (run_agent) does check scopes. + return await match_credentials_to_requirements( + user_id, requirements, check_scopes=False + ) async def _execute( self, diff --git a/autogpt_platform/backend/backend/api/features/chat/tools/utils.py b/autogpt_platform/backend/backend/api/features/chat/tools/utils.py index 463f7ad6fe..802b1de3cd 100644 --- a/autogpt_platform/backend/backend/api/features/chat/tools/utils.py +++ b/autogpt_platform/backend/backend/api/features/chat/tools/utils.py @@ -234,14 +234,15 @@ async def get_user_credentials(user_id: str) -> list: def find_matching_credential( available_creds: list, field_info: CredentialsFieldInfo, + check_scopes: bool = True, ): - """Find a credential that matches the required provider, type, and scopes.""" + """Find a credential that matches the required provider, type, and optionally scopes.""" for cred in available_creds: if cred.provider not in field_info.provider: continue if cred.type not in field_info.supported_types: continue - if not _credential_has_required_scopes(cred, field_info): + if check_scopes and not _credential_has_required_scopes(cred, field_info): continue return cred return None @@ -260,11 +261,18 @@ def create_credential_meta_from_match(matching_cred) -> CredentialsMetaInput: async def match_credentials_to_requirements( user_id: str, requirements: dict[str, CredentialsFieldInfo], + check_scopes: bool = True, ) -> tuple[dict[str, CredentialsMetaInput], list[CredentialsMetaInput]]: """ Match user's credentials against a dictionary of credential requirements. This is the core matching logic shared by both graph and block credential matching. + + Args: + user_id: User ID to fetch credentials for + requirements: Dict mapping field names to CredentialsFieldInfo + check_scopes: Whether to verify OAuth2 scopes match requirements (default True). + Set to False to preserve original run_block behavior which didn't check scopes. """ matched: dict[str, CredentialsMetaInput] = {} missing: list[CredentialsMetaInput] = [] @@ -275,7 +283,9 @@ async def match_credentials_to_requirements( available_creds = await get_user_credentials(user_id) for field_name, field_info in requirements.items(): - matching_cred = find_matching_credential(available_creds, field_info) + matching_cred = find_matching_credential( + available_creds, field_info, check_scopes=check_scopes + ) if matching_cred: try: