package actor import ( "context" "encoding/json" "fmt" "github.com/orca/orca/pkg/bus" "github.com/orca/orca/pkg/tool" ) // ToolWorker is an agent that processes tool call messages by executing // tools through the tool.Manager. // // It implements the Agent interface and handles MsgTypeToolCall messages. // When a tool call is received, it extracts the tool name and arguments // from the message content, executes the tool via the Manager, and // returns a MsgTypeToolResult with the execution result. type ToolWorker struct { *BaseAgent manager *tool.Manager } // NewToolWorker creates a new ToolWorker agent with the given id and tool manager. // The agent is started automatically upon creation. func NewToolWorker(id string, manager *tool.Manager) *ToolWorker { w := &ToolWorker{ BaseAgent: NewBaseAgent(id, "tool_worker"), manager: manager, } w.SetHandler(w.handleMessage) if err := w.Start(); err != nil { panic(fmt.Sprintf("tool_worker: failed to start: %v", err)) } return w } // handleMessage routes incoming messages to the appropriate handler. func (w *ToolWorker) handleMessage(ctx context.Context, msg bus.Message) (bus.Message, error) { switch msg.Type { case bus.MsgTypeToolCall: return w.handleToolCall(ctx, msg) case bus.MsgTypeTaskRequest: return w.handleTask(ctx, msg) case bus.MsgTypeSystem: return w.handleSystem(ctx, msg) default: return bus.Message{}, fmt.Errorf("tool_worker %s: unsupported message type %s", w.ID(), msg.Type) } } // handleToolCall processes a tool call by executing the named tool // with the provided arguments. // // The msg.Content is expected to contain a JSON object with: // - "name": the tool name (string) // - "arguments": the tool arguments (object) // // Or alternatively, msg.Content can be a string in the format: // tool_name(arg1=val1, arg2=val2) func (w *ToolWorker) handleToolCall(ctx context.Context, msg bus.Message) (bus.Message, error) { w.setStatus(StatusWaitingForTool) defer w.setStatus(StatusProcessing) toolName, args, err := parseToolCallContent(msg.Content) if err != nil { return bus.Message{ ID: msg.ID + "-result", Type: bus.MsgTypeToolResult, From: w.ID(), To: msg.From, Content: map[string]interface{}{"error": err.Error()}, }, nil } // Execute the tool result, err := w.manager.Execute(toolName, ctx, args) if err != nil { return bus.Message{ ID: msg.ID + "-result", Type: bus.MsgTypeToolResult, From: w.ID(), To: msg.From, Content: map[string]interface{}{"error": err.Error()}, }, nil } return bus.Message{ ID: msg.ID + "-result", Type: bus.MsgTypeToolResult, From: w.ID(), To: msg.From, Content: result, }, nil } // parseToolCallContent extracts the tool name and arguments from various // content formats. func parseToolCallContent(content interface{}) (string, map[string]interface{}, error) { switch v := content.(type) { case map[string]interface{}: // Format: {"name": "tool_name", "arguments": {...}} name, ok := v["name"].(string) if !ok || name == "" { return "", nil, fmt.Errorf("tool call content missing 'name' field") } args, _ := v["arguments"].(map[string]interface{}) if args == nil { args = make(map[string]interface{}) } return name, args, nil case string: // Try JSON format var parsed map[string]interface{} if err := json.Unmarshal([]byte(v), &parsed); err == nil { name, ok := parsed["name"].(string) if ok && name != "" { args, _ := parsed["arguments"].(map[string]interface{}) if args == nil { args = make(map[string]interface{}) } return name, args, nil } } return "", nil, fmt.Errorf("cannot parse tool call from string content: %s", v) default: return "", nil, fmt.Errorf("unsupported tool call content type: %T", content) } } // handleTask processes a task request by returning a task response. func (w *ToolWorker) handleTask(ctx context.Context, msg bus.Message) (bus.Message, error) { return bus.Message{ ID: msg.ID + "-response", Type: bus.MsgTypeTaskResponse, From: w.ID(), To: msg.From, Content: msg.Content, }, nil } // handleSystem processes internal system messages. func (w *ToolWorker) handleSystem(ctx context.Context, msg bus.Message) (bus.Message, error) { return bus.Message{ ID: msg.ID + "-ack", Type: bus.MsgTypeSystem, From: w.ID(), To: msg.From, Content: "tool_worker acknowledged", }, nil }