@@ -96,11 +96,6 @@ func ResumeReject(reason string) ResumeRequest {
9696// ToolHandlerFunc is a function type for handling tool calls
9797type ToolHandlerFunc func (ctx context.Context , sess * session.Session , toolCall tools.ToolCall , events chan Event ) (* tools.ToolCallResult , error )
9898
99- type ToolHandler struct {
100- handler ToolHandlerFunc
101- tool tools.Tool
102- }
103-
10499// ElicitationRequestHandler is a function type for handling elicitation requests
105100type ElicitationRequestHandler func (ctx context.Context , message string , schema map [string ]any ) (map [string ]any , error )
106101
@@ -196,7 +191,7 @@ type ToolsChangeSubscriber interface {
196191
197192// LocalRuntime manages the execution of agents
198193type LocalRuntime struct {
199- toolMap map [string ]ToolHandler
194+ toolMap map [string ]ToolHandlerFunc
200195 team * team.Team
201196 currentAgent string
202197 resumeChan chan ResumeRequest
@@ -297,7 +292,7 @@ func NewLocalRuntime(agents *team.Team, opts ...Opt) (*LocalRuntime, error) {
297292 }
298293
299294 r := & LocalRuntime {
300- toolMap : make (map [string ]ToolHandler ),
295+ toolMap : make (map [string ]ToolHandlerFunc ),
301296 team : agents ,
302297 currentAgent : defaultAgent .Name (),
303298 resumeChan : make (chan ResumeRequest ),
@@ -909,30 +904,14 @@ func (r *LocalRuntime) emitToolsProgressively(ctx context.Context, a *agent.Agen
909904 send (ToolsetInfo (totalTools , false , r .currentAgent ))
910905}
911906
912- // registerDefaultTools registers the default tool handlers
907+ // registerDefaultTools registers the runtime-managed tool handlers.
908+ // The tool definitions themselves come from the agent's toolsets; this only
909+ // maps tool names to the runtime handler functions that implement them.
913910func (r * LocalRuntime ) registerDefaultTools () {
914- slog .Debug ("Registering default tools" )
915-
916- tt := builtin .NewTransferTaskTool ()
917- ht := builtin .NewHandoffTool ()
918- ttTools , _ := tt .Tools (context .TODO ())
919- htTools , _ := ht .Tools (context .TODO ())
920- allTools := append (ttTools , htTools ... )
921-
922- handlers := map [string ]ToolHandlerFunc {
923- builtin .ToolNameTransferTask : r .handleTaskTransfer ,
924- builtin .ToolNameHandoff : r .handleHandoff ,
925- }
926-
927- for _ , t := range allTools {
928- if h , exists := handlers [t .Name ]; exists {
929- r .toolMap [t .Name ] = ToolHandler {handler : h , tool : t }
930- } else {
931- slog .Warn ("No handler found for default tool" , "tool" , t .Name )
932- }
933- }
934-
935- slog .Debug ("Registered default tools" , "count" , len (r .toolMap ))
911+ r .toolMap [builtin .ToolNameTransferTask ] = r .handleTaskTransfer
912+ r .toolMap [builtin .ToolNameHandoff ] = r .handleHandoff
913+ r .toolMap [builtin .ToolNameChangeModel ] = r .handleChangeModel
914+ r .toolMap [builtin .ToolNameRevertModel ] = r .handleRevertModel
936915}
937916
938917func (r * LocalRuntime ) finalizeEventChannel (ctx context.Context , sess * session.Session , events chan Event ) {
@@ -1579,8 +1558,8 @@ func (r *LocalRuntime) processToolCalls(ctx context.Context, sess *session.Sessi
15791558 // Pick the handler: runtime-managed tools (transfer_task, handoff)
15801559 // have dedicated handlers; everything else goes through the toolset.
15811560 var runTool func ()
1582- if def , exists := r .toolMap [toolCall .Function .Name ]; exists {
1583- runTool = func () { r .runAgentTool (callCtx , def . handler , sess , toolCall , tool , events , a ) }
1561+ if handler , exists := r .toolMap [toolCall .Function .Name ]; exists {
1562+ runTool = func () { r .runAgentTool (callCtx , handler , sess , toolCall , tool , events , a ) }
15841563 } else {
15851564 runTool = func () { r .runTool (callCtx , tool , toolCall , events , sess , a ) }
15861565 }
@@ -2089,6 +2068,75 @@ func (r *LocalRuntime) handleHandoff(_ context.Context, _ *session.Session, tool
20892068 return tools .ResultSuccess (handoffMessage ), nil
20902069}
20912070
2071+ // findModelPickerTool returns the ModelPickerTool from the current agent's
2072+ // toolsets, or nil if the agent has no model_picker configured.
2073+ func (r * LocalRuntime ) findModelPickerTool () * builtin.ModelPickerTool {
2074+ a , err := r .team .Agent (r .currentAgent )
2075+ if err != nil {
2076+ return nil
2077+ }
2078+ for _ , ts := range a .ToolSets () {
2079+ if mpt , ok := tools.As [* builtin.ModelPickerTool ](ts ); ok {
2080+ return mpt
2081+ }
2082+ }
2083+ return nil
2084+ }
2085+
2086+ // handleChangeModel handles the change_model tool call by switching the current agent's model.
2087+ func (r * LocalRuntime ) handleChangeModel (ctx context.Context , _ * session.Session , toolCall tools.ToolCall , events chan Event ) (* tools.ToolCallResult , error ) {
2088+ var params builtin.ChangeModelArgs
2089+ if err := json .Unmarshal ([]byte (toolCall .Function .Arguments ), & params ); err != nil {
2090+ return nil , fmt .Errorf ("invalid arguments: %w" , err )
2091+ }
2092+
2093+ if params .Model == "" {
2094+ return tools .ResultError ("model parameter is required" ), nil
2095+ }
2096+
2097+ // Validate the requested model against the allowed list
2098+ mpt := r .findModelPickerTool ()
2099+ if mpt == nil {
2100+ return tools .ResultError ("model_picker is not configured for this agent" ), nil
2101+ }
2102+ allowed := mpt .AllowedModels ()
2103+ if ! slices .Contains (allowed , params .Model ) {
2104+ return tools .ResultError (fmt .Sprintf (
2105+ "model %q is not in the allowed list. Available models: %s" ,
2106+ params .Model , strings .Join (allowed , ", " ),
2107+ )), nil
2108+ }
2109+
2110+ return r .setModelAndEmitInfo (ctx , params .Model , events )
2111+ }
2112+
2113+ // handleRevertModel handles the revert_model tool call by reverting the current agent to its default model.
2114+ func (r * LocalRuntime ) handleRevertModel (ctx context.Context , _ * session.Session , _ tools.ToolCall , events chan Event ) (* tools.ToolCallResult , error ) {
2115+ return r .setModelAndEmitInfo (ctx , "" , events )
2116+ }
2117+
2118+ // setModelAndEmitInfo sets the model for the current agent and emits an updated
2119+ // AgentInfo event so the UI reflects the change. An empty modelRef reverts to
2120+ // the agent's default model.
2121+ func (r * LocalRuntime ) setModelAndEmitInfo (ctx context.Context , modelRef string , events chan Event ) (* tools.ToolCallResult , error ) {
2122+ if err := r .SetAgentModel (ctx , r .currentAgent , modelRef ); err != nil {
2123+ return tools .ResultError (fmt .Sprintf ("failed to set model: %v" , err )), nil
2124+ }
2125+
2126+ if a , err := r .team .Agent (r .currentAgent ); err == nil {
2127+ events <- AgentInfo (a .Name (), r .getEffectiveModelID (a ), a .Description (), a .WelcomeMessage ())
2128+ } else {
2129+ slog .Warn ("Failed to retrieve agent after model change; UI may not reflect the update" , "agent" , r .currentAgent , "error" , err )
2130+ }
2131+
2132+ if modelRef == "" {
2133+ slog .Info ("Model reverted via model_picker tool" , "agent" , r .currentAgent )
2134+ return tools .ResultSuccess ("Model reverted to the agent's default model" ), nil
2135+ }
2136+ slog .Info ("Model changed via model_picker tool" , "agent" , r .currentAgent , "model" , modelRef )
2137+ return tools .ResultSuccess (fmt .Sprintf ("Model changed to %s" , modelRef )), nil
2138+ }
2139+
20922140// Summarize generates a summary for the session based on the conversation history.
20932141// The additionalPrompt parameter allows users to provide additional instructions
20942142// for the summarization (e.g., "focus on code changes" or "include action items").
0 commit comments