fix: preserve original credential matching behavior

- Add check_scopes parameter to find_matching_credential and
  match_credentials_to_requirements (default True)
- run_block uses check_scopes=False to preserve original behavior
  (original run_block did not verify OAuth2 scopes)
- Add isinstance check to get_inputs_from_schema for safety
  (original returned [] if input_schema wasn't a dict)
This commit is contained in:
Otto
2026-02-03 13:15:54 +00:00
parent 4d6471a7eb
commit ff8ca11845
3 changed files with 22 additions and 4 deletions

View File

@@ -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", []))

View File

@@ -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,

View File

@@ -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: