From 165cb850eebb05c5ef62285d6f01139ccb2e62c9 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 2 Jul 2025 20:34:17 +0530 Subject: [PATCH] feat: setup agents --- .../captain/copilot/agents_response_job.rb | 43 ++++++++++ enterprise/app/models/copilot_message.rb | 25 ++++-- .../lib/captain/agents/analysis_agent.rb | 65 ++++++++++++++ .../agents/copilot_orchestrator_agent.rb | 85 +++++++++++++++++++ .../lib/captain/agents/integrations_agent.rb | 60 +++++++++++++ .../lib/captain/agents/knowledge_agent.rb | 57 +++++++++++++ .../lib/captain/agents/research_agent.rb | 61 +++++++++++++ .../lib/captain/tools/base_agent_tool.rb | 44 ++++++++++ .../lib/captain/tools/get_article_tool.rb | 21 +++++ .../lib/captain/tools/get_contact_tool.rb | 21 +++++ .../captain/tools/get_conversation_tool.rb | 21 +++++ .../lib/captain/tools/search_articles_tool.rb | 40 +++++++++ .../lib/captain/tools/search_contacts_tool.rb | 48 +++++++++++ .../tools/search_conversations_tool.rb | 51 +++++++++++ .../tools/search_documentation_tool.rb | 19 +++++ .../tools/search_linear_issues_tool.rb | 21 +++++ 16 files changed, 675 insertions(+), 7 deletions(-) create mode 100644 enterprise/app/jobs/captain/copilot/agents_response_job.rb create mode 100644 enterprise/lib/captain/agents/analysis_agent.rb create mode 100644 enterprise/lib/captain/agents/copilot_orchestrator_agent.rb create mode 100644 enterprise/lib/captain/agents/integrations_agent.rb create mode 100644 enterprise/lib/captain/agents/knowledge_agent.rb create mode 100644 enterprise/lib/captain/agents/research_agent.rb create mode 100644 enterprise/lib/captain/tools/base_agent_tool.rb create mode 100644 enterprise/lib/captain/tools/get_article_tool.rb create mode 100644 enterprise/lib/captain/tools/get_contact_tool.rb create mode 100644 enterprise/lib/captain/tools/get_conversation_tool.rb create mode 100644 enterprise/lib/captain/tools/search_articles_tool.rb create mode 100644 enterprise/lib/captain/tools/search_contacts_tool.rb create mode 100644 enterprise/lib/captain/tools/search_conversations_tool.rb create mode 100644 enterprise/lib/captain/tools/search_documentation_tool.rb create mode 100644 enterprise/lib/captain/tools/search_linear_issues_tool.rb diff --git a/enterprise/app/jobs/captain/copilot/agents_response_job.rb b/enterprise/app/jobs/captain/copilot/agents_response_job.rb new file mode 100644 index 000000000..82b67e7e8 --- /dev/null +++ b/enterprise/app/jobs/captain/copilot/agents_response_job.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +class Captain::Copilot::AgentsResponseJob < ApplicationJob + queue_as :default + + def perform(copilot_thread_id:, message_content:, conversation_id: nil, user_id: nil) + Rails.logger.debug do + "[DEBUG] AgentsResponseJob: Starting with thread_id: #{copilot_thread_id}, message: #{message_content}, conversation_id: #{conversation_id}, user_id: #{user_id}" + end + + copilot_thread = CopilotThread.find(copilot_thread_id) + Rails.logger.debug { "[DEBUG] AgentsResponseJob: Found copilot_thread: #{copilot_thread.inspect}" } + + # Use the new AgentsChatService with direct parameters + Rails.logger.debug '[DEBUG] AgentsResponseJob: Creating AgentsChatService' + service = AgentsChatService.new( + copilot_thread: copilot_thread, + message_content: message_content, + conversation_id: conversation_id, + user_id: user_id + ) + + Rails.logger.debug '[DEBUG] AgentsResponseJob: Calling service.perform' + service.perform + Rails.logger.debug '[DEBUG] AgentsResponseJob: Service completed successfully' + rescue StandardError => e + Rails.logger.debug { "[DEBUG] AgentsResponseJob: ERROR - #{e.class}: #{e.message}" } + Rails.logger.debug '[DEBUG] AgentsResponseJob: FULL BACKTRACE:' + e.backtrace.each { |line| Rails.logger.debug "[DEBUG] #{line}" } + Rails.logger.error "AgentsResponseJob Error: #{e.class}: #{e.message}" + Rails.logger.error 'Full backtrace:' + e.backtrace.each { |line| Rails.logger.error line } + + # Create an error response if the job fails + copilot_thread&.copilot_messages&.create!( + message_type: 'assistant', + message: { + content: 'I apologize, but I encountered an error while processing your request. Please try again.' + } + ) + Rails.logger.debug '[DEBUG] AgentsResponseJob: Created error response message' + end +end \ No newline at end of file diff --git a/enterprise/app/models/copilot_message.rb b/enterprise/app/models/copilot_message.rb index d19cb69c6..69e0b3a24 100644 --- a/enterprise/app/models/copilot_message.rb +++ b/enterprise/app/models/copilot_message.rb @@ -38,13 +38,24 @@ class CopilotMessage < ApplicationRecord end def enqueue_response_job(conversation_id, user_id) - Captain::Copilot::ResponseJob.perform_later( - assistant: copilot_thread.assistant, - conversation_id: conversation_id, - user_id: user_id, - copilot_thread_id: copilot_thread.id, - message: message['content'] - ) + if ENV['USE_AGENTS'] == 'true' + # Use the new AI Agents SDK implementation + Captain::Copilot::AgentsResponseJob.perform_later( + copilot_thread_id: copilot_thread.id, + message_content: message['content'], + conversation_id: conversation_id, + user_id: user_id + ) + else + # Use the original implementation + Captain::Copilot::ResponseJob.perform_later( + assistant: copilot_thread.assistant, + conversation_id: conversation_id, + user_id: user_id, + copilot_thread_id: copilot_thread.id, + message: message['content'] + ) + end end private diff --git a/enterprise/lib/captain/agents/analysis_agent.rb b/enterprise/lib/captain/agents/analysis_agent.rb new file mode 100644 index 000000000..8f94f330b --- /dev/null +++ b/enterprise/lib/captain/agents/analysis_agent.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +class Captain::Agents::AnalysisAgent + def self.create(assistant, user: nil) + ::Agents::Agent.new( + name: 'Analysis Agent', + instructions: analysis_instructions, + model: 'gpt-4o-mini', + tools: [ + Captain::Tools::GetConversationTool.new(assistant, user: user), + Captain::Tools::SearchConversationsTool.new(assistant, user: user), + Captain::Tools::GetContactTool.new(assistant, user: user), + Captain::Tools::SearchContactsTool.new(assistant, user: user) + ] + ) + end + + def self.analysis_instructions + <<~INSTRUCTIONS + You are an Analysis Agent for Chatwoot. You analyze customer interactions and provide actionable insights. + + **Your analysis capabilities:** + + 📊 **Sentiment Analysis**:#{' '} + - Identify customer emotions (frustrated, satisfied, confused, angry) + - Assess tone throughout the conversation + - Flag escalation risks or satisfaction indicators + + 📊 **Conversation Quality Assessment**: + - Evaluate response time and effectiveness + - Identify missed opportunities or pain points + - Assess resolution completeness + + 📊 **Pattern Recognition**: + - Spot recurring issues or common questions + - Identify successful resolution strategies + - Flag customers with multiple interactions + + 📊 **Performance Insights**: + - Evaluate agent response quality + - Suggest improvements for handling similar cases + - Recommend knowledge gaps to address + + **Your tools:** + - **get_conversation**: Get detailed conversation for analysis + - **search_conversations**: Find patterns across multiple conversations + - **get_contact**: Understand customer history and context + - **search_contacts**: Identify recurring customer patterns + + **Analysis output format:** + 1. **Sentiment Summary**: Customer emotional state and satisfaction level + 2. **Key Issues**: Main problems or concerns identified + 3. **Resolution Assessment**: How well issues were addressed + 4. **Recommendations**: Specific actionable suggestions + 5. **Risk Flags**: Escalation risks, dissatisfaction indicators + 6. **Success Indicators**: What worked well + + **Always provide:** + - Specific examples from the conversation + - Actionable recommendations + - Risk assessment (low/medium/high) + - Clear reasoning for your conclusions + INSTRUCTIONS + end +end \ No newline at end of file diff --git a/enterprise/lib/captain/agents/copilot_orchestrator_agent.rb b/enterprise/lib/captain/agents/copilot_orchestrator_agent.rb new file mode 100644 index 000000000..6c7e186dc --- /dev/null +++ b/enterprise/lib/captain/agents/copilot_orchestrator_agent.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +class Captain::Agents::CopilotOrchestratorAgent + def self.create(assistant, user: nil) + Rails.logger.debug '[DEBUG] CopilotOrchestratorAgent: Starting creation' + + # Create the specialized agents using factory pattern + research_agent = ResearchAgent.create(assistant, user: user) + Rails.logger.debug '[DEBUG] CopilotOrchestratorAgent: Created ResearchAgent' + analysis_agent = AnalysisAgent.create(assistant, user: user) + Rails.logger.debug '[DEBUG] CopilotOrchestratorAgent: Created AnalysisAgent' + integrations_agent = IntegrationsAgent.create(assistant, user: user) + Rails.logger.debug '[DEBUG] CopilotOrchestratorAgent: Created IntegrationsAgent' + knowledge_agent = KnowledgeAgent.create(assistant, user: user) + Rails.logger.debug '[DEBUG] CopilotOrchestratorAgent: Created KnowledgeAgent' + + # Create the main orchestrator agent + ::Agents::Agent.new( + name: 'Chatwoot Copilot', + instructions: orchestrator_instructions, + model: 'gpt-4.1-mini', + tools: [ + research_agent.as_tool( + name: 'research_agent', + description: "Get conversation details by ID, search conversations by status/contact, find contact info, search knowledge articles. Use for: 'Get conversation 12345', 'Find recent conversations', 'Search for customer john@example.com'" + ), + analysis_agent.as_tool( + name: 'analysis_agent', + description: 'Analyze conversations for sentiment, quality, and patterns. Provide insights on customer emotions and support effectiveness. Use after getting conversation data from research_agent.' + ), + integrations_agent.as_tool( + name: 'integrations_agent', + description: "Update conversation status, change priority, add/remove labels, assign agents. Use for: 'Mark as resolved', 'Set priority to high', 'Add billing label'" + ), + knowledge_agent.as_tool( + name: 'knowledge_agent', + description: "Search help articles, find policies, get documentation. Use for: 'Find refund policy', 'Search billing articles', 'What's our shipping policy'" + ) + ] + ) + end + + def self.orchestrator_instructions + lambda { |context| + state = context.context[:state] || {} + + base_instructions = <<~INSTRUCTIONS + You are the Chatwoot Copilot. Help agents with customer support tasks. + + Tools available: + - research_agent: Get conversation details, find contacts, search articles + - analysis_agent: Analyze conversations for sentiment and insights + - integrations_agent: Update conversation status, priority, labels + - knowledge_agent: Find help articles and policies + + How to use tools: + - For conversation summary: research_agent("Get conversation [ID]") + - For sentiment analysis: research_agent first, then analysis_agent + - For status updates: integrations_agent("Mark conversation as resolved") + - For help content: knowledge_agent("Find billing policy") + + Always get conversation data first before analysis or recommendations. + INSTRUCTIONS + + # Add current conversation context if available + if state[:current_conversation] + conversation = state[:current_conversation] + base_instructions += <<~CONTEXT + + **Current Conversation:** + ID: #{conversation[:display_id]} + Status: #{conversation[:status]} + Contact: #{conversation[:contact_name]} + Assignee: #{conversation[:assignee] || 'Unassigned'} + Last Activity: #{conversation[:last_activity]} + + This conversation is currently #{conversation[:status]} and assigned to #{conversation[:assignee] || 'no one'}. + The customer #{conversation[:contact_name]} was last active #{conversation[:last_activity]}. + CONTEXT + end + + base_instructions + } + end +end diff --git a/enterprise/lib/captain/agents/integrations_agent.rb b/enterprise/lib/captain/agents/integrations_agent.rb new file mode 100644 index 000000000..991420d89 --- /dev/null +++ b/enterprise/lib/captain/agents/integrations_agent.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +class Captain::Agents::IntegrationsAgent + def self.create(assistant, user: nil) + ::Agents::Agent.new( + name: 'Integrations Agent', + instructions: integrations_instructions, + model: 'gpt-4o-mini', + tools: [ + Captain::Tools::GetConversationTool.new(assistant, user: user) + ] + ) + end + + def self.integrations_instructions + <<~INSTRUCTIONS + You are an Integrations Agent for Chatwoot. You handle conversation management and system actions. + + **Your action capabilities:** + + ⚙️ **Conversation Management**:#{' '} + - Update status (open, resolved, pending) + - Change priority (low, medium, high, urgent) + - Assign conversations to agents + - Add or remove labels for organization + + ⚙️ **System Integration**: + - Execute workflow automations + - Trigger macro sequences + - Create internal notes and documentation + - Handle routing and assignment logic + + **Your tools:** + - **get_conversation**: Get current conversation details before making changes + + **Current limitations:** + Your tool set is currently limited to conversation retrieval. For actual system changes, provide detailed instructions that agents can execute manually. + + **Action recommendations format:** + 1. **Current State**: What the conversation status/priority currently is + 2. **Recommended Actions**: Specific steps to take + 3. **Reasoning**: Why these actions are appropriate + 4. **Next Steps**: What should happen after the changes + + **When providing action guidance:** + - Be specific about status changes needed + - Suggest appropriate labels based on conversation content + - Recommend priority levels based on urgency/impact + - Provide clear reasoning for each recommendation + - Include any follow-up actions needed + + **Example response:** + "Based on the conversation, I recommend: + 1. Update status to 'pending' (waiting for customer response) + 2. Add label 'billing-inquiry' for categorization + 3. Set priority to 'medium' (standard billing question) + 4. Assign to billing team for specialized handling" + INSTRUCTIONS + end +end \ No newline at end of file diff --git a/enterprise/lib/captain/agents/knowledge_agent.rb b/enterprise/lib/captain/agents/knowledge_agent.rb new file mode 100644 index 000000000..05b80d831 --- /dev/null +++ b/enterprise/lib/captain/agents/knowledge_agent.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +class Captain::Agents::KnowledgeAgent + def self.create(assistant, user: nil) + ::Agents::Agent.new( + name: 'Knowledge Agent', + instructions: knowledge_instructions, + model: 'gpt-4o-mini', + tools: [ + Captain::Tools::SearchArticlesTool.new(assistant, user: user), + Captain::Tools::GetArticleTool.new(assistant, user: user) + ] + ) + end + + def self.knowledge_instructions + <<~INSTRUCTIONS + You are a Knowledge Agent for Chatwoot. You help find and deliver knowledge base content and response suggestions. + + **Your capabilities:** + + 📚 **Knowledge Base Search**: + - Find relevant articles by topic, category, or keyword + - Retrieve full article content for detailed information + - Search across all published knowledge base content + - Provide article summaries and key points + + 📚 **Response Assistance**: + - Suggest appropriate articles for customer questions + - Provide template responses based on knowledge base content + - Help maintain consistent messaging across the team + - Find policy and procedure documentation + + **Your tools:** + - **search_articles**: Find articles by query, category, or status + - **get_article**: Retrieve full content of specific articles + + **Search approach:** + 1. **Be thorough**: Search with relevant keywords from the user's query + 2. **Be specific**: Use category filters when the topic is clear + 3. **Be helpful**: Provide article summaries and key takeaways + 4. **Be organized**: Structure results by relevance and category + + **Output format:** + - **Article Suggestions**: List relevant articles with brief descriptions + - **Key Points**: Highlight the most important information + - **Quick Answer**: Provide immediate helpful information when possible + - **Full Content**: Include detailed article content when requested + + **Best practices:** + - Always search for the most current and relevant articles + - Provide context about why an article is relevant + - Include article IDs and titles for easy reference + - Suggest multiple articles when appropriate for comprehensive coverage + INSTRUCTIONS + end +end \ No newline at end of file diff --git a/enterprise/lib/captain/agents/research_agent.rb b/enterprise/lib/captain/agents/research_agent.rb new file mode 100644 index 000000000..660afb3f3 --- /dev/null +++ b/enterprise/lib/captain/agents/research_agent.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +class Captain::Agents::ResearchAgent + def self.create(assistant, user: nil) + ::Agents::Agent.new( + name: 'Research Agent', + instructions: research_instructions, + model: 'gpt-4o-mini', + tools: [ + Captain::Tools::SearchConversationsTool.new(assistant, user: user), + Captain::Tools::GetConversationTool.new(assistant, user: user), + Captain::Tools::SearchContactsTool.new(assistant, user: user), + Captain::Tools::GetContactTool.new(assistant, user: user), + Captain::Tools::SearchArticlesTool.new(assistant, user: user), + Captain::Tools::GetArticleTool.new(assistant, user: user) + ] + ) + end + + def self.research_instructions + <<~INSTRUCTIONS + You are a Research Agent for Chatwoot. Your job is to find and retrieve specific information from the system. + + **Your tools and when to use them:** + + 🔍 **get_conversation**: Use when you have a specific conversation ID or display ID + - Input: conversation_id (use the display_id, not the main id) + - Example: For conversation display_id "12345", use conversation_id: "12345" + - Returns: Full conversation with messages, contact info, status, labels + + 🔍 **search_conversations**: Use when you need to find conversations by criteria + - Filter by: contact_id, status, priority, labels + - Use for: "recent conversations", "open tickets", "conversations with specific contact" + + 🔍 **get_contact**: Use when you have a specific contact ID + - Returns: Contact profile, interaction history, details + + 🔍 **search_contacts**: Use to find contacts by name, email, or phone + - Use for: "find customer john@example.com", "contacts named Sarah" + + 🔍 **search_articles**: Use for knowledge base searches + - Search by: query, category, status + - Use for: finding documentation, help articles, policies + + 🔍 **get_article**: Use when you have a specific article ID + - Returns: Full article content and metadata + + **Research approach:** + 1. **Be specific**: Use exact IDs when available + 2. **Be comprehensive**: Include all relevant details in your response + 3. **Be organized**: Structure information clearly (conversation summary, contact details, etc.) + 4. **Use context**: Look for conversation IDs or contact information in the request context + + **Output format:** + - Provide detailed, structured summaries + - Include key timestamps, statuses, and participant information + - Format conversation messages chronologically + - Highlight important details like customer concerns and agent responses + INSTRUCTIONS + end +end \ No newline at end of file diff --git a/enterprise/lib/captain/tools/base_agent_tool.rb b/enterprise/lib/captain/tools/base_agent_tool.rb new file mode 100644 index 000000000..3a1a4c618 --- /dev/null +++ b/enterprise/lib/captain/tools/base_agent_tool.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +class Captain::Tools::BaseAgentTool < Agents::Tool + def initialize(assistant, user: nil) + @assistant = assistant + @user = user + 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 @user.blank? + + account_user = AccountUser.find_by(account_id: @assistant.account_id, user_id: @user.id) + 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 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/get_article_tool.rb b/enterprise/lib/captain/tools/get_article_tool.rb new file mode 100644 index 000000000..63b669228 --- /dev/null +++ b/enterprise/lib/captain/tools/get_article_tool.rb @@ -0,0 +1,21 @@ +class Captain::Tools::GetArticleTool < BaseAgentTool + description 'Get details of an article including its content and metadata' + param :article_id, type: 'number', desc: 'The ID of the article to retrieve' + + def perform(_tool_context, article_id:) + log_tool_usage('get_article', { article_id: article_id }) + + return 'Missing required parameters' if article_id.blank? + + article = account_scoped(Article).find_by(id: article_id) + return 'Article not found' if article.nil? + + article.to_llm_text + end + + protected + + def required_permission + 'knowledge_base_manage' + end +end \ No newline at end of file diff --git a/enterprise/lib/captain/tools/get_contact_tool.rb b/enterprise/lib/captain/tools/get_contact_tool.rb new file mode 100644 index 000000000..e3cd263ae --- /dev/null +++ b/enterprise/lib/captain/tools/get_contact_tool.rb @@ -0,0 +1,21 @@ +class Captain::Tools::GetContactTool < BaseAgentTool + description 'Get details of a contact including their profile information' + param :contact_id, type: 'number', desc: 'The ID of the contact to retrieve' + + def perform(_tool_context, contact_id:) + log_tool_usage('get_contact', { contact_id: contact_id }) + + return 'Missing required parameters' if contact_id.blank? + + contact = account_scoped(Contact).find_by(id: contact_id) + return 'Contact not found' if contact.nil? + + contact.to_llm_text + end + + protected + + def required_permission + 'contact_manage' + end +end \ No newline at end of file diff --git a/enterprise/lib/captain/tools/get_conversation_tool.rb b/enterprise/lib/captain/tools/get_conversation_tool.rb new file mode 100644 index 000000000..3528016d2 --- /dev/null +++ b/enterprise/lib/captain/tools/get_conversation_tool.rb @@ -0,0 +1,21 @@ +class Captain::Tools::GetConversationTool < BaseAgentTool + description 'Get details of a conversation including messages and context' + param :conversation_id, type: 'string', desc: 'The display ID of the conversation to retrieve' + + def perform(_tool_context, conversation_id:) + log_tool_usage('get_conversation', { conversation_id: conversation_id }) + + return 'Missing required parameters' if conversation_id.blank? + + conversation = account_scoped(::Conversation).find_by(display_id: conversation_id) + return 'Conversation not found' if conversation.nil? + + conversation.to_llm_text + end + + def active? + user_has_permission('conversation_manage') || + user_has_permission('conversation_unassigned_manage') || + user_has_permission('conversation_participating_manage') + end +end \ No newline at end of file diff --git a/enterprise/lib/captain/tools/search_articles_tool.rb b/enterprise/lib/captain/tools/search_articles_tool.rb new file mode 100644 index 000000000..8ee05cc0b --- /dev/null +++ b/enterprise/lib/captain/tools/search_articles_tool.rb @@ -0,0 +1,40 @@ +class Captain::Tools::SearchArticlesTool < BaseAgentTool + description 'Search articles based on parameters' + param :query, type: 'string', desc: 'Search articles by title or content (partial match)' + param :category_id, type: 'number', desc: 'Filter articles by category ID', required: false + param :status, type: 'string', desc: 'Filter articles by status (draft, published, archived)', required: false + + def perform(_tool_context, query:, category_id: nil, status: nil) + log_tool_usage('search_articles', { query: query, category_id: category_id, status: status }) + + return 'Missing required parameters' if query.blank? + + articles = fetch_articles(query, category_id, status) + + return 'No articles found' unless articles.exists? + + total_count = articles.count + articles = articles.limit(100) + + <<~RESPONSE + #{total_count > 100 ? "Found #{total_count} articles (showing first 100)" : "Total number of articles: #{total_count}"} + #{articles.map(&:to_llm_text).join("\n---\n")} + RESPONSE + end + + protected + + def required_permission + 'knowledge_base_manage' + end + + private + + def fetch_articles(query, category_id, status) + articles = account_scoped(Article) + articles = articles.where('title ILIKE :query OR content ILIKE :query', query: "%#{query}%") if query.present? + articles = articles.where(category_id: category_id) if category_id.present? + articles = articles.where(status: status) if status.present? + articles + end +end \ No newline at end of file diff --git a/enterprise/lib/captain/tools/search_contacts_tool.rb b/enterprise/lib/captain/tools/search_contacts_tool.rb new file mode 100644 index 000000000..4017bcc6b --- /dev/null +++ b/enterprise/lib/captain/tools/search_contacts_tool.rb @@ -0,0 +1,48 @@ +class Captain::Tools::SearchContactsTool < BaseAgentTool + description 'Search contacts based on parameters' + param :query, type: 'string', desc: 'Search contacts by name, email, phone', required: false + param :inbox_id, type: 'number', desc: 'Filter contacts by inbox ID', required: false + param :labels, type: 'string', desc: 'Filter contacts by labels (comma-separated)', required: false + + def perform(_tool_context, query: nil, inbox_id: nil, labels: nil) + log_tool_usage('search_contacts', { query: query, inbox_id: inbox_id, labels: labels }) + + contacts = get_contacts(query, inbox_id, labels) + + return 'No contacts found' unless contacts.exists? + + total_count = contacts.count + contacts = contacts.limit(100) + + <<~RESPONSE + #{total_count > 100 ? "Found #{total_count} contacts (showing first 100)" : "Total number of contacts: #{total_count}"} + #{contacts.map(&:to_llm_text).join("\n---\n")} + RESPONSE + end + + protected + + def required_permission + 'contact_manage' + end + + private + + def get_contacts(query, inbox_id, labels) + contacts = account_scoped(Contact) + + if query.present? + contacts = contacts.where( + 'name ILIKE :query OR email ILIKE :query OR phone_number ILIKE :query', + query: "%#{query}%" + ) + end + + contacts = contacts.where(inbox_id: inbox_id) if inbox_id.present? + if labels.present? + label_array = labels.split(',').map(&:strip) + contacts = contacts.tagged_with(label_array, any: true) + end + contacts + end +end \ No newline at end of file diff --git a/enterprise/lib/captain/tools/search_conversations_tool.rb b/enterprise/lib/captain/tools/search_conversations_tool.rb new file mode 100644 index 000000000..538fdeb82 --- /dev/null +++ b/enterprise/lib/captain/tools/search_conversations_tool.rb @@ -0,0 +1,51 @@ +class Captain::Tools::SearchConversationsTool < BaseAgentTool + description 'Search conversations based on parameters' + param :contact_id, type: 'number', desc: 'Filter conversations by contact ID', required: false + param :status, type: 'string', desc: 'Filter conversations by status (open, resolved, pending, snoozed)', required: false + param :priority, type: 'string', desc: 'Filter conversations by priority (low, medium, high, urgent)', required: false + param :labels, type: 'string', desc: 'Filter conversations by labels (comma-separated)', required: false + + def perform(_tool_context, contact_id: nil, status: nil, priority: nil, labels: nil) + log_tool_usage('search_conversations', { contact_id: contact_id, status: status, priority: priority, labels: labels }) + + conversations = get_conversations(status, contact_id, priority, labels) + + return 'No conversations found' unless conversations.exists? + + total_count = conversations.count + conversations = conversations.limit(100) + + <<~RESPONSE + #{total_count > 100 ? "Found #{total_count} conversations (showing first 100)" : "Total number of conversations: #{total_count}"} + #{conversations.map { |conversation| conversation.to_llm_text(include_contact_details: true) }.join("\n---\n")} + RESPONSE + end + + def active? + user_has_permission('conversation_manage') || + user_has_permission('conversation_unassigned_manage') || + user_has_permission('conversation_participating_manage') + end + + private + + def get_conversations(status, contact_id, priority, labels) + conversations = permissible_conversations + conversations = conversations.where(contact_id: contact_id) if contact_id.present? + conversations = conversations.where(status: status) if status.present? + conversations = conversations.where(priority: priority) if priority.present? + if labels.present? + label_array = labels.split(',').map(&:strip) + conversations = conversations.tagged_with(label_array, any: true) + end + conversations + end + + def permissible_conversations + Conversations::PermissionFilterService.new( + account_scoped(::Conversation), + @user, + @assistant.account + ).perform + end +end \ No newline at end of file diff --git a/enterprise/lib/captain/tools/search_documentation_tool.rb b/enterprise/lib/captain/tools/search_documentation_tool.rb new file mode 100644 index 000000000..91ee6112d --- /dev/null +++ b/enterprise/lib/captain/tools/search_documentation_tool.rb @@ -0,0 +1,19 @@ +class Captain::Tools::SearchDocumentationTool < BaseAgentTool + description 'Search documentation and help docs' + param :query, type: 'string', desc: 'Search query for documentation' + + def perform(_tool_context, query:) + log_tool_usage('search_documentation', { query: query }) + + return 'Missing required parameters' if query.blank? + + # Call the existing search documentation service + Captain::Tools::SearchDocumentationService.new(@assistant, user: @user).execute({ + 'query' => query + }) + end + + def active? + true # Documentation search is available to all users + end +end \ No newline at end of file diff --git a/enterprise/lib/captain/tools/search_linear_issues_tool.rb b/enterprise/lib/captain/tools/search_linear_issues_tool.rb new file mode 100644 index 000000000..d89b48fed --- /dev/null +++ b/enterprise/lib/captain/tools/search_linear_issues_tool.rb @@ -0,0 +1,21 @@ +class Captain::Tools::SearchLinearIssuesTool < BaseAgentTool + description 'Search Linear issues and development context' + param :query, type: 'string', desc: 'Search query for Linear issues' + + def perform(_tool_context, query:) + log_tool_usage('search_linear_issues', { query: query }) + + return 'Missing required parameters' if query.blank? + + # Call the existing search linear issues service + Captain::Tools::Copilot::SearchLinearIssuesService.new(@assistant, user: @user).execute({ + 'query' => query + }) + end + + protected + + def required_permission + 'conversation_manage' # Basic permission check for technical context + end +end