@@ -156,6 +156,7 @@ def _create_tool(
156156 execute : CustomToolExecuteFn ,
157157 extends_toolkit : t .Optional [str ] = None ,
158158 output_params : t .Optional [t .Type [BaseModel ]] = None ,
159+ preload : t .Optional [bool ] = None ,
159160) -> CustomTool :
160161 """Internal: create and validate a CustomTool."""
161162 context = "experimental.tool"
@@ -218,6 +219,7 @@ def _create_tool(
218219 output_schema = output_schema ,
219220 input_params = input_params ,
220221 execute = execute ,
222+ preload = preload ,
221223 )
222224
223225
@@ -258,6 +260,7 @@ def _infer_tool_from_function(
258260 description : t .Optional [str ] = None ,
259261 extends_toolkit : t .Optional [str ] = None ,
260262 output_params : t .Optional [t .Type [BaseModel ]] = None ,
263+ preload : t .Optional [bool ] = None ,
261264 annotation_locals : t .Optional [t .Mapping [str , t .Any ]] = None ,
262265) -> CustomTool :
263266 """Create a CustomTool by inferring metadata from a decorated function.
@@ -341,6 +344,7 @@ def execute(input: t.Any, ctx: t.Any) -> t.Dict[str, t.Any]:
341344 execute = execute ,
342345 extends_toolkit = extends_toolkit ,
343346 output_params = output_params ,
347+ preload = preload ,
344348 )
345349
346350
@@ -372,7 +376,14 @@ def search_code(input: SearchInput, ctx):
372376 return {"results": []}
373377 """
374378
375- def __init__ (self , * , slug : str , name : str , description : str ) -> None :
379+ def __init__ (
380+ self ,
381+ * ,
382+ slug : str ,
383+ name : str ,
384+ description : str ,
385+ preload : t .Optional [bool ] = None ,
386+ ) -> None :
376387 context = "experimental.Toolkit"
377388 _validate_slug (slug , context )
378389 if not name :
@@ -383,6 +394,7 @@ def __init__(self, *, slug: str, name: str, description: str) -> None:
383394 self .slug = slug
384395 self .name = name
385396 self .description = description
397+ self .preload = preload
386398 self ._tools : t .List [CustomTool ] = []
387399
388400 @property
@@ -400,6 +412,7 @@ def tool(
400412 name : t .Optional [str ] = None ,
401413 description : t .Optional [str ] = None ,
402414 output_params : t .Optional [t .Type [BaseModel ]] = None ,
415+ preload : t .Optional [bool ] = None ,
403416 ) -> t .Callable [[t .Callable [..., t .Any ]], CustomTool ]: ...
404417
405418 def tool (
@@ -410,6 +423,7 @@ def tool(
410423 name : t .Optional [str ] = None ,
411424 description : t .Optional [str ] = None ,
412425 output_params : t .Optional [t .Type [BaseModel ]] = None ,
426+ preload : t .Optional [bool ] = None ,
413427 ) -> t .Union [CustomTool , t .Callable [[t .Callable [..., t .Any ]], CustomTool ]]:
414428 """Decorator to add a tool to this toolkit.
415429
@@ -425,6 +439,7 @@ def decorator(f: t.Callable[..., t.Any]) -> CustomTool:
425439 name = name ,
426440 description = description ,
427441 output_params = output_params ,
442+ preload = preload ,
428443 annotation_locals = annotation_locals ,
429444 # No extends_toolkit for toolkit tools
430445 )
@@ -441,6 +456,7 @@ def decorator(f: t.Callable[..., t.Any]) -> CustomTool:
441456 name = name ,
442457 description = description ,
443458 output_params = output_params ,
459+ preload = preload ,
444460 annotation_locals = _get_caller_locals (),
445461 )
446462 _validate_slug_length (
@@ -479,6 +495,7 @@ def tool(
479495 description : t .Optional [str ] = None ,
480496 extends_toolkit : t .Optional [str ] = None ,
481497 output_params : t .Optional [t .Type [BaseModel ]] = None ,
498+ preload : t .Optional [bool ] = None ,
482499 ) -> t .Callable [[t .Callable [..., t .Any ]], CustomTool ]: ...
483500
484501 def tool (
@@ -490,6 +507,7 @@ def tool(
490507 description : t .Optional [str ] = None ,
491508 extends_toolkit : t .Optional [str ] = None ,
492509 output_params : t .Optional [t .Type [BaseModel ]] = None ,
510+ preload : t .Optional [bool ] = None ,
493511 ) -> t .Union [CustomTool , t .Callable [[t .Callable [..., t .Any ]], CustomTool ]]:
494512 """Decorator to create a custom tool from a function.
495513
@@ -526,6 +544,7 @@ def decorator(f: t.Callable[..., t.Any]) -> CustomTool:
526544 description = description ,
527545 extends_toolkit = extends_toolkit ,
528546 output_params = output_params ,
547+ preload = preload ,
529548 annotation_locals = annotation_locals ,
530549 )
531550
@@ -537,6 +556,7 @@ def decorator(f: t.Callable[..., t.Any]) -> CustomTool:
537556 description = description ,
538557 extends_toolkit = extends_toolkit ,
539558 output_params = output_params ,
559+ preload = preload ,
540560 annotation_locals = _get_caller_locals (),
541561 )
542562 return decorator
@@ -547,7 +567,28 @@ def decorator(f: t.Callable[..., t.Any]) -> CustomTool:
547567# ────────────────────────────────────────────────────────────────
548568
549569
550- def serialize_custom_tools (tools : t .List [CustomTool ]) -> t .List [t .Dict [str , t .Any ]]:
570+ def _serialized_preload_value (
571+ preload : t .Optional [bool ],
572+ inherited_preload : t .Optional [bool ],
573+ default_preload : bool ,
574+ ) -> t .Optional [bool ]:
575+ resolved = (
576+ preload
577+ if preload is not None
578+ else inherited_preload
579+ if inherited_preload is not None
580+ else default_preload
581+ )
582+ if preload is not None or inherited_preload is not None or default_preload :
583+ return resolved
584+ return None
585+
586+
587+ def serialize_custom_tools (
588+ tools : t .List [CustomTool ],
589+ * ,
590+ default_preload : bool = False ,
591+ ) -> t .List [t .Dict [str , t .Any ]]:
551592 """Serialize custom tools into the format expected by the backend."""
552593 result = []
553594 for tool in tools :
@@ -561,12 +602,19 @@ def serialize_custom_tools(tools: t.List[CustomTool]) -> t.List[t.Dict[str, t.An
561602 entry ["output_schema" ] = tool .output_schema
562603 if tool .extends_toolkit :
563604 entry ["extends_toolkit" ] = tool .extends_toolkit
605+ preload = _serialized_preload_value (
606+ tool .preload , inherited_preload = None , default_preload = default_preload
607+ )
608+ if preload is not None :
609+ entry ["preload" ] = preload
564610 result .append (entry )
565611 return result
566612
567613
568614def serialize_custom_toolkits (
569615 toolkits : t .Sequence [ExperimentalToolkit ],
616+ * ,
617+ default_preload : bool = False ,
570618) -> t .List [t .Dict [str , t .Any ]]:
571619 """Serialize custom toolkits into the format expected by the backend."""
572620 result = []
@@ -581,15 +629,26 @@ def serialize_custom_toolkits(
581629 }
582630 if tool .output_schema :
583631 entry ["output_schema" ] = tool .output_schema
632+ preload = _serialized_preload_value (
633+ tool .preload ,
634+ inherited_preload = tk .preload ,
635+ default_preload = default_preload ,
636+ )
637+ if preload is not None :
638+ entry ["preload" ] = preload
584639 toolkit_tools .append (entry )
585- result .append (
586- {
587- "slug" : tk .slug ,
588- "name" : tk .name ,
589- "description" : tk .description ,
590- "tools" : toolkit_tools ,
591- }
640+ toolkit_entry : t .Dict [str , t .Any ] = {
641+ "slug" : tk .slug ,
642+ "name" : tk .name ,
643+ "description" : tk .description ,
644+ "tools" : toolkit_tools ,
645+ }
646+ preload = _serialized_preload_value (
647+ tk .preload , inherited_preload = None , default_preload = default_preload
592648 )
649+ if preload is not None :
650+ toolkit_entry ["preload" ] = preload
651+ result .append (toolkit_entry )
593652 return result
594653
595654
@@ -729,23 +788,63 @@ def find_custom_tool_map_entry_by_final_slug(
729788 return custom_tools_map .by_final_slug .get (slug .upper ())
730789
731790
791+ def assert_no_custom_tool_slugs_in_preload (
792+ preload_tools : t .Union [t .Sequence [str ], t .Literal ["all" ], None ],
793+ custom_tools_map : t .Optional [CustomToolsMap ],
794+ ) -> None :
795+ """Reject legacy top-level preload of custom tool slugs."""
796+ if preload_tools is None or preload_tools == "all" :
797+ return
798+
799+ custom_preload_slugs = []
800+ for slug in preload_tools :
801+ normalized = slug .upper ()
802+ if normalized .startswith (LOCAL_TOOL_PREFIX ) or (
803+ custom_tools_map is not None
804+ and (
805+ normalized in custom_tools_map .by_original_slug
806+ or normalized in custom_tools_map .by_final_slug
807+ )
808+ ):
809+ custom_preload_slugs .append (slug )
810+
811+ if custom_preload_slugs :
812+ raise ValidationError (
813+ "Custom tool slugs are not supported in preload.tools: "
814+ f"{ ', ' .join (custom_preload_slugs )} . Set preload=True on the SDK "
815+ "custom tool or custom toolkit definition instead."
816+ )
817+
818+
732819def get_preloaded_custom_tool_slugs (
733- tool_router_tools : t .Optional [t .Iterable [str ]],
734820 custom_tools_map : t .Optional [CustomToolsMap ],
821+ * ,
822+ default_preload : bool = False ,
735823) -> t .List [str ]:
736- """Return final custom tool slugs selected by the backend for preload."""
737- if tool_router_tools is None or custom_tools_map is None :
824+ """Return final custom tool slugs selected locally for preload."""
825+ if custom_tools_map is None :
738826 return []
739827
740828 seen : t .Set [str ] = set ()
741829 custom_tool_slugs : t .List [str ] = []
742830
743- for slug in tool_router_tools :
744- entry = find_custom_tool_map_entry_by_final_slug (
745- custom_tools_map ,
746- str (slug ),
831+ for entry in custom_tools_map .by_final_slug .values ():
832+ toolkit = next (
833+ (
834+ tk
835+ for tk in custom_tools_map .toolkits or []
836+ if entry .toolkit and tk .slug .lower () == entry .toolkit .lower ()
837+ ),
838+ None ,
839+ )
840+ should_preload = (
841+ entry .handle .preload
842+ if entry .handle .preload is not None
843+ else toolkit .preload
844+ if toolkit is not None and toolkit .preload is not None
845+ else default_preload
747846 )
748- if entry is None :
847+ if not should_preload :
749848 continue
750849
751850 final_slug_key = entry .final_slug .upper ()
0 commit comments