@@ -391,8 +391,43 @@ def _normalize_needs_approval(
391391 if require_approval is None :
392392 return False
393393
394- def _to_bool (value : str ) -> bool :
395- return value == "always"
394+ def _to_bool (value : object , * , location : str ) -> bool :
395+ if value == "always" :
396+ return True
397+ if value == "never" :
398+ return False
399+ raise UserError (
400+ f"Invalid require_approval value at { location } : "
401+ f"expected 'always' or 'never', got { value !r} ."
402+ )
403+
404+ def _validate_tool_names (value : object , * , location : str ) -> list [str ]:
405+ if not isinstance (value , list ):
406+ raise UserError (
407+ f"Invalid require_approval tool_names at { location } : "
408+ f"expected a list of strings, got { type (value ).__name__ } ."
409+ )
410+
411+ tool_names : list [str ] = []
412+ for index , tool_name in enumerate (value ):
413+ if not isinstance (tool_name , str ):
414+ raise UserError (
415+ f"Invalid require_approval tool name at { location } [{ index } ]: "
416+ f"expected a string, got { type (tool_name ).__name__ } ."
417+ )
418+ tool_names .append (tool_name )
419+ return tool_names
420+
421+ def _get_tool_names_entry (value : object , * , policy : str ) -> list [str ]:
422+ if not isinstance (value , dict ):
423+ raise UserError (
424+ f"Invalid require_approval.{ policy } : "
425+ f"expected an object with tool_names, got { type (value ).__name__ } ."
426+ )
427+ return _validate_tool_names (
428+ value .get ("tool_names" , []),
429+ location = f"require_approval.{ policy } .tool_names" ,
430+ )
396431
397432 def _is_tool_list_schema (value : object ) -> bool :
398433 if not isinstance (value , dict ):
@@ -408,24 +443,36 @@ def _is_tool_list_schema(value: object) -> bool:
408443 if isinstance (require_approval , dict ) and _is_tool_list_schema (require_approval ):
409444 always_entry : RequireApprovalToolList | Any = require_approval .get ("always" , {})
410445 never_entry : RequireApprovalToolList | Any = require_approval .get ("never" , {})
411- always_names = (
412- always_entry .get ("tool_names" , []) if isinstance (always_entry , dict ) else []
413- )
414- never_names = never_entry .get ("tool_names" , []) if isinstance (never_entry , dict ) else []
446+ invalid_keys = sorted (set (require_approval ) - {"always" , "never" })
447+ if invalid_keys :
448+ raise UserError (
449+ "Invalid require_approval tool list policy: "
450+ f"unexpected keys { invalid_keys !r} ; expected only 'always' and 'never'."
451+ )
452+ always_names = _get_tool_names_entry (always_entry , policy = "always" )
453+ never_names = _get_tool_names_entry (never_entry , policy = "never" )
454+ overlapping_names = sorted (set (always_names ) & set (never_names ))
455+ if overlapping_names :
456+ raise UserError (
457+ "Invalid require_approval tool list policy: "
458+ f"tool names cannot appear in both always and never: { overlapping_names !r} ."
459+ )
415460 tool_list_mapping : dict [str , bool ] = {}
416461 for name in always_names :
417- tool_list_mapping [str ( name ) ] = True
462+ tool_list_mapping [name ] = True
418463 for name in never_names :
419- tool_list_mapping [str ( name ) ] = False
464+ tool_list_mapping [name ] = False
420465 return tool_list_mapping
421466
422467 if isinstance (require_approval , dict ):
423468 tool_mapping : dict [str , bool ] = {}
424469 for name , value in require_approval .items ():
425470 if isinstance (value , bool ):
426471 tool_mapping [str (name )] = value
427- elif isinstance (value , str ) and value in ("always" , "never" ):
428- tool_mapping [str (name )] = _to_bool (value )
472+ else :
473+ tool_mapping [str (name )] = _to_bool (
474+ value , location = f"require_approval[{ name !r} ]"
475+ )
429476 return tool_mapping
430477
431478 if callable (require_approval ):
@@ -434,7 +481,7 @@ def _is_tool_list_schema(value: object) -> bool:
434481 if isinstance (require_approval , bool ):
435482 return require_approval
436483
437- return _to_bool (require_approval )
484+ return _to_bool (require_approval , location = "require_approval" )
438485
439486 def _get_needs_approval_for_tool (
440487 self ,
0 commit comments