From 89a748eef633ff831b5414ccc5db9deb9ec320f2 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 15 Jul 2025 21:55:09 +0530 Subject: [PATCH] feat: update tools --- .../captain/tools/add_contact_note_tool.rb | 30 ++++-------- .../tools/add_label_to_conversation_tool.rb | 34 ++++++++++++++ .../captain/tools/add_private_note_tool.rb | 29 ++++-------- .../lib/captain/tools/base_agent_tool.rb | 46 ------------------- .../lib/captain/tools/base_public_tool.rb | 45 ++++++++++++++++++ .../lib/captain/tools/update_priority_tool.rb | 39 +++++----------- 6 files changed, 110 insertions(+), 113 deletions(-) create mode 100644 enterprise/lib/captain/tools/add_label_to_conversation_tool.rb delete mode 100644 enterprise/lib/captain/tools/base_agent_tool.rb create mode 100644 enterprise/lib/captain/tools/base_public_tool.rb diff --git a/enterprise/lib/captain/tools/add_contact_note_tool.rb b/enterprise/lib/captain/tools/add_contact_note_tool.rb index ea3841ef7..e1475abf0 100644 --- a/enterprise/lib/captain/tools/add_contact_note_tool.rb +++ b/enterprise/lib/captain/tools/add_contact_note_tool.rb @@ -1,36 +1,26 @@ -class Captain::Tools::AddContactNoteTool < Captain::Tools::BaseAgentTool +class Captain::Tools::AddContactNoteTool < Captain::Tools::BasePublicTool description 'Add a note to a contact profile' - param :contact_id, type: 'string', desc: 'The ID of the contact' param :note, type: 'string', desc: 'The note content to add to the contact' - def perform(_tool_context, note:, contact_id:) - log_tool_usage('add_contact_note', { contact_id: contact_id, note_length: note.length }) - - return 'Missing required parameters: contact_id, note' if note.blank? || contact_id.blank? - - contact = find_contact(contact_id) + def perform(tool_context, note:) + contact = find_contact(tool_context.state) return 'Contact not found' unless contact + return 'Note content is required' if note.blank? + + log_tool_usage('add_contact_note', { contact_id: contact.id, note_length: note.length }) + create_contact_note(contact, note) "Note added successfully to contact #{contact.name} (ID: #{contact.id})" end private - def find_contact(contact_id) - account_scoped(::Contact).find_by(id: contact_id) - end - def create_contact_note(contact, note) - contact.notes.create!( - account: @assistant.account, - contact: contact, - content: note, - user: @user - ) + contact.notes.create!(content: note) end - def active? - user_has_permission('contact_manage') + def permissions + %w[contact_manage] end end diff --git a/enterprise/lib/captain/tools/add_label_to_conversation_tool.rb b/enterprise/lib/captain/tools/add_label_to_conversation_tool.rb new file mode 100644 index 000000000..429f33b7b --- /dev/null +++ b/enterprise/lib/captain/tools/add_label_to_conversation_tool.rb @@ -0,0 +1,34 @@ +class Captain::Tools::AddLabelToConversationTool < Captain::Tools::BasePublicTool + description 'Add a label to a conversation' + param :label_name, type: 'string', desc: 'The name of the label to add' + + def perform(tool_context, label_name:) + conversation = find_conversation(tool_context.state) + return 'Conversation not found' unless conversation + + label_name = label_name&.strip&.downcase + return 'Label name is required' if label_name.blank? + + label = find_label(label_name) + return 'Label not found' unless label + + add_label_to_conversation(conversation, label_name) + + log_tool_usage('added_label', conversation_id: conversation.id, label: label_name) + + "Label '#{label_name}' added to conversation ##{conversation.display_id}" + end + + private + + def find_label(label_name) + account_scoped(Label).find_by(title: label_name) + end + + def add_label_to_conversation(conversation, label_name) + conversation.add_labels(label_name) + rescue StandardError => e + Rails.logger.error "Failed to add label to conversation: #{e.message}" + raise + end +end diff --git a/enterprise/lib/captain/tools/add_private_note_tool.rb b/enterprise/lib/captain/tools/add_private_note_tool.rb index 28606c231..4a2328d43 100644 --- a/enterprise/lib/captain/tools/add_private_note_tool.rb +++ b/enterprise/lib/captain/tools/add_private_note_tool.rb @@ -1,40 +1,31 @@ -class Captain::Tools::AddPrivateNoteTool < Captain::Tools::BaseAgentTool +class Captain::Tools::AddPrivateNoteTool < Captain::Tools::BasePublicTool description 'Add a private note to a conversation' - param :conversation_id, type: 'string', desc: 'The display ID of the conversation' param :note, type: 'string', desc: 'The private note content' - def perform(_tool_context, conversation_id:, note:) - log_tool_usage('add_private_note', { conversation_id: conversation_id, note_length: note.length }) - - return 'Missing required parameters: conversation_id, note' if conversation_id.blank? || note.blank? - - conversation = find_conversation(conversation_id) + def perform(tool_context, note:) + conversation = find_conversation(tool_context.state) return 'Conversation not found' unless conversation + log_tool_usage('add_private_note', { conversation_id: conversation.id, note_length: note.length }) create_private_note(conversation, note) - "Private note added successfully to conversation #{conversation_id}" + + 'Private note added successfully' end private - def find_conversation(conversation_id) - account_scoped(::Conversation).find_by(display_id: conversation_id) - end - def create_private_note(conversation, note) conversation.messages.create!( account: @assistant.account, inbox: conversation.inbox, - sender: @user, + sender: @assistant, + message_type: :outgoing, content: note, - message_type: 'activity', private: true ) end - def active? - user_has_permission('conversation_manage') || - user_has_permission('conversation_unassigned_manage') || - user_has_permission('conversation_participating_manage') + def permissions + %w[conversation_manage conversation_unassigned_manage conversation_participating_manage] end end diff --git a/enterprise/lib/captain/tools/base_agent_tool.rb b/enterprise/lib/captain/tools/base_agent_tool.rb deleted file mode 100644 index 8020cd5f0..000000000 --- a/enterprise/lib/captain/tools/base_agent_tool.rb +++ /dev/null @@ -1,46 +0,0 @@ -require 'agents' - -class Captain::Tools::BaseAgentTool < Agents::Tool - def initialize(assistant, user: nil) - @assistant = assistant - @user = user - @account_user = find_account_user if @user.present? - super() - end - - def active? - user_has_permission(required_permission) - end - - protected - - def required_permission - # Override in subclasses to specify the required permission - 'agent' - end - - private - - def user_has_permission(permission) - return false if @account_user.blank? - - return @account_user.custom_role.permissions.include?(permission) if @account_user.custom_role.present? - - # Default permission for agents without custom roles - @account_user.administrator? || @account_user.agent? - end - - def find_account_user - AccountUser.find_by(account_id: @assistant.account_id, user_id: @user.id) - end - - def account_scoped(model_class) - model_class.where(account_id: @assistant.account_id) - end - - def log_tool_usage(action, details = {}) - Rails.logger.info do - "#{self.class.name}: #{action} by user #{@user&.id} for assistant #{@assistant&.id} - #{details.inspect}" - end - end -end diff --git a/enterprise/lib/captain/tools/base_public_tool.rb b/enterprise/lib/captain/tools/base_public_tool.rb new file mode 100644 index 000000000..e53f7a0f1 --- /dev/null +++ b/enterprise/lib/captain/tools/base_public_tool.rb @@ -0,0 +1,45 @@ +require 'agents' + +class Captain::Tools::BasePublicTool < Agents::Tool + def initialize(assistant) + @assistant = assistant + super() + end + + def active? + # Public tools are always active + true + end + + def permissions + # Override in subclasses to specify required permissions + # Returns empty array for public tools (no permissions required) + [] + end + + private + + def account_scoped(model_class) + model_class.where(account_id: @assistant.account_id) + end + + def find_conversation(state) + conversation_id = state[:conversation][:id] + return nil unless conversation_id + + account_scoped(::Conversation).find_by(id: conversation_id) + end + + def find_contact(state) + contact_id = state[:contact][:id] + return nil unless contact_id + + account_scoped(::Contact).find_by(id: contact_id) + end + + def log_tool_usage(action, details = {}) + Rails.logger.info do + "#{self.class.name}: #{action} for assistant #{@assistant&.id} - #{details.inspect}" + end + end +end diff --git a/enterprise/lib/captain/tools/update_priority_tool.rb b/enterprise/lib/captain/tools/update_priority_tool.rb index 738bcab22..8fc75f601 100644 --- a/enterprise/lib/captain/tools/update_priority_tool.rb +++ b/enterprise/lib/captain/tools/update_priority_tool.rb @@ -1,40 +1,25 @@ -class Captain::Tools::UpdatePriorityTool < Captain::Tools::BaseAgentTool +class Captain::Tools::UpdatePriorityTool < Captain::Tools::BasePublicTool description 'Update the priority of a conversation' - param :conversation_id, type: 'string', desc: 'The display ID of the conversation' param :priority, type: 'string', desc: 'The priority level: low, medium, high, urgent, or nil to remove priority' - def perform(_tool_context, conversation_id:, priority:) - log_tool_usage('update_priority', { conversation_id: conversation_id, priority: priority }) + def perform(tool_context, priority:) + @conversation = find_conversation(tool_context.state) + return 'Conversation not found' unless @conversation - error = validate_and_prepare(conversation_id, priority) - return error if error + @normalized_priority = normalize_priority(priority) + return "Invalid priority. Valid options: #{valid_priority_options}" unless valid_priority?(@normalized_priority) + + log_tool_usage('update_priority', { conversation_id: @conversation.id, priority: priority }) execute_priority_update end private - def validate_and_prepare(conversation_id, priority) - return 'Missing required parameter: conversation_id' if conversation_id.blank? - - @conversation = find_conversation(conversation_id) - return 'Conversation not found' unless @conversation - - @normalized_priority = normalize_priority(priority) - return "Invalid priority. Valid options: #{valid_priority_options}" unless valid_priority?(@normalized_priority) - - @conversation_id = conversation_id - nil - end - def execute_priority_update update_conversation_priority(@conversation, @normalized_priority) priority_text = @normalized_priority || 'none' - "Priority updated to '#{priority_text}' for conversation #{@conversation_id}" - end - - def find_conversation(conversation_id) - account_scoped(::Conversation).find_by(display_id: conversation_id) + "Priority updated to '#{priority_text}' for conversation ##{@conversation.display_id}" end def normalize_priority(priority) @@ -57,9 +42,7 @@ class Captain::Tools::UpdatePriorityTool < Captain::Tools::BaseAgentTool conversation.update!(priority: priority) end - def active? - user_has_permission('conversation_manage') || - user_has_permission('conversation_unassigned_manage') || - user_has_permission('conversation_participating_manage') + def permissions + %w[conversation_manage conversation_unassigned_manage conversation_participating_manage] end end