ai-agents 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +29 -106
  3. data/docs/Gemfile +14 -0
  4. data/docs/Gemfile.lock +183 -0
  5. data/docs/_config.yml +53 -0
  6. data/docs/_sass/color_schemes/ruby.scss +72 -0
  7. data/docs/_sass/custom/custom.scss +93 -0
  8. data/docs/architecture.md +353 -0
  9. data/docs/assets/fonts/InterVariable.woff2 +0 -0
  10. data/docs/concepts/agent-tool.md +166 -0
  11. data/docs/concepts/agents.md +43 -0
  12. data/docs/concepts/context.md +110 -0
  13. data/docs/concepts/handoffs.md +81 -0
  14. data/docs/concepts/runner.md +87 -0
  15. data/docs/concepts/tools.md +62 -0
  16. data/docs/concepts.md +21 -0
  17. data/docs/guides/agent-as-tool-pattern.md +242 -0
  18. data/docs/guides/multi-agent-systems.md +261 -0
  19. data/docs/guides/rails-integration.md +440 -0
  20. data/docs/guides/state-persistence.md +451 -0
  21. data/docs/guides.md +18 -0
  22. data/docs/index.md +95 -0
  23. data/examples/collaborative-copilot/README.md +169 -0
  24. data/examples/collaborative-copilot/agents/analysis_agent.rb +48 -0
  25. data/examples/collaborative-copilot/agents/answer_suggestion_agent.rb +50 -0
  26. data/examples/collaborative-copilot/agents/copilot_orchestrator.rb +85 -0
  27. data/examples/collaborative-copilot/agents/integrations_agent.rb +58 -0
  28. data/examples/collaborative-copilot/agents/research_agent.rb +52 -0
  29. data/examples/collaborative-copilot/data/contacts.json +47 -0
  30. data/examples/collaborative-copilot/data/conversations.json +170 -0
  31. data/examples/collaborative-copilot/data/knowledge_base.json +58 -0
  32. data/examples/collaborative-copilot/data/linear_issues.json +83 -0
  33. data/examples/collaborative-copilot/data/stripe_billing.json +71 -0
  34. data/examples/collaborative-copilot/interactive.rb +90 -0
  35. data/examples/collaborative-copilot/tools/create_linear_ticket_tool.rb +58 -0
  36. data/examples/collaborative-copilot/tools/get_article_tool.rb +41 -0
  37. data/examples/collaborative-copilot/tools/get_contact_tool.rb +51 -0
  38. data/examples/collaborative-copilot/tools/get_conversation_tool.rb +53 -0
  39. data/examples/collaborative-copilot/tools/get_stripe_billing_tool.rb +44 -0
  40. data/examples/collaborative-copilot/tools/search_contacts_tool.rb +57 -0
  41. data/examples/collaborative-copilot/tools/search_conversations_tool.rb +54 -0
  42. data/examples/collaborative-copilot/tools/search_knowledge_base_tool.rb +55 -0
  43. data/examples/collaborative-copilot/tools/search_linear_issues_tool.rb +60 -0
  44. data/lib/agents/agent.rb +34 -0
  45. data/lib/agents/agent_tool.rb +113 -0
  46. data/lib/agents/handoff.rb +8 -34
  47. data/lib/agents/version.rb +1 -1
  48. data/lib/agents.rb +1 -0
  49. metadata +43 -1
@@ -0,0 +1,58 @@
1
+ {
2
+ "articles": {
3
+ "ART-001": {
4
+ "id": "ART-001",
5
+ "title": "How to Clear Browser Cache",
6
+ "category": "troubleshooting",
7
+ "content": "To clear your browser cache:\n\n**Chrome:**\n1. Press Ctrl+Shift+Delete (Cmd+Shift+Delete on Mac)\n2. Select 'All time' from the time range\n3. Check 'Cached images and files'\n4. Click 'Clear data'\n\n**Firefox:**\n1. Press Ctrl+Shift+Delete (Cmd+Shift+Delete on Mac)\n2. Select 'Everything' from time range\n3. Check 'Cache'\n4. Click 'Clear Now'\n\n**Safari:**\n1. Go to Safari menu > Clear History\n2. Select 'All History'\n3. Click 'Clear History'",
8
+ "tags": ["browser", "cache", "troubleshooting", "login-issues"],
9
+ "created_at": "2023-08-15T10:00:00Z",
10
+ "updated_at": "2024-01-10T14:30:00Z"
11
+ },
12
+ "ART-002": {
13
+ "id": "ART-002",
14
+ "title": "Password Reset Instructions",
15
+ "category": "account",
16
+ "content": "If you're unable to log in, try resetting your password:\n\n1. Go to the login page\n2. Click 'Forgot Password?'\n3. Enter your email address\n4. Check your email for reset instructions\n5. Click the reset link (valid for 24 hours)\n6. Create a new strong password\n\n**Password Requirements:**\n- At least 8 characters\n- One uppercase letter\n- One lowercase letter \n- One number\n- One special character\n\nIf you don't receive the reset email, check your spam folder or contact support.",
17
+ "tags": ["password", "reset", "login", "account"],
18
+ "created_at": "2023-08-15T10:15:00Z",
19
+ "updated_at": "2024-01-05T09:20:00Z"
20
+ },
21
+ "ART-003": {
22
+ "id": "ART-003",
23
+ "title": "API Rate Limits and Error Handling",
24
+ "category": "development",
25
+ "content": "Our API has the following rate limits:\n\n**Standard Plan:** 1,000 requests/hour\n**Pro Plan:** 5,000 requests/hour \n**Enterprise Plan:** 50,000 requests/hour\n\n**Common Error Codes:**\n- 429: Rate limit exceeded\n- 500: Internal server error\n- 503: Service temporarily unavailable\n\n**Best Practices:**\n- Implement exponential backoff for 5xx errors\n- Cache responses when possible\n- Use pagination for large datasets\n- Monitor your usage in the dashboard\n\nIf you're experiencing consistent 500 errors, please contact our engineering team.",
26
+ "tags": ["api", "rate-limits", "errors", "development"],
27
+ "created_at": "2023-09-01T11:00:00Z",
28
+ "updated_at": "2024-01-12T16:45:00Z"
29
+ },
30
+ "ART-004": {
31
+ "id": "ART-004",
32
+ "title": "Billing and Refund Policy",
33
+ "category": "billing",
34
+ "content": "**Billing Cycle:**\nAll plans are billed monthly on the date you signed up.\n\n**Refund Policy:**\n- Full refund within 30 days of signup\n- Partial refunds for downgrades (prorated)\n- No refunds for accounts terminated for policy violations\n\n**Common Billing Issues:**\n- Duplicate charges: Usually resolved within 24 hours\n- Failed payments: Update payment method in settings\n- Upgrade/downgrade: Changes take effect immediately\n\n**Contacting Billing:**\nFor billing questions, include your account email and approximate charge date.",
35
+ "tags": ["billing", "refunds", "charges", "payment"],
36
+ "created_at": "2023-08-20T14:00:00Z",
37
+ "updated_at": "2024-01-08T10:30:00Z"
38
+ }
39
+ },
40
+ "categories": [
41
+ {
42
+ "name": "troubleshooting",
43
+ "description": "Technical issues and problem resolution"
44
+ },
45
+ {
46
+ "name": "account",
47
+ "description": "Account management and login issues"
48
+ },
49
+ {
50
+ "name": "development",
51
+ "description": "API documentation and developer resources"
52
+ },
53
+ {
54
+ "name": "billing",
55
+ "description": "Billing, payments, and subscription management"
56
+ }
57
+ ]
58
+ }
@@ -0,0 +1,83 @@
1
+ {
2
+ "issues": {
3
+ "ENG-445": {
4
+ "id": "ENG-445",
5
+ "title": "API /users endpoint returning 500 errors intermittently",
6
+ "status": "in_progress",
7
+ "priority": "high",
8
+ "assignee": "alex.dev@company.com",
9
+ "labels": ["bug", "api", "production"],
10
+ "created_at": "2024-01-12T11:15:00Z",
11
+ "updated_at": "2024-01-12T15:30:00Z",
12
+ "description": "Customer reports 20% failure rate on /users endpoint with 500 errors. Investigation shows database connection pool exhaustion during peak traffic.",
13
+ "comments": [
14
+ {
15
+ "author": "alex.dev@company.com",
16
+ "content": "Reproduced locally. Issue appears to be connection pool size too small for current load.",
17
+ "timestamp": "2024-01-12T13:20:00Z"
18
+ },
19
+ {
20
+ "author": "sarah.lead@company.com",
21
+ "content": "Increasing pool size from 10 to 25 connections. Testing in staging.",
22
+ "timestamp": "2024-01-12T15:30:00Z"
23
+ }
24
+ ]
25
+ },
26
+ "ENG-442": {
27
+ "id": "ENG-442",
28
+ "title": "Add dark mode support to web application",
29
+ "status": "backlog",
30
+ "priority": "medium",
31
+ "assignee": null,
32
+ "labels": ["feature", "ui", "accessibility"],
33
+ "created_at": "2024-01-05T10:00:00Z",
34
+ "updated_at": "2024-01-08T14:15:00Z",
35
+ "description": "Multiple customers have requested dark mode. This would improve accessibility and user experience, especially for users who work in low-light environments.",
36
+ "comments": [
37
+ {
38
+ "author": "product.manager@company.com",
39
+ "content": "Added to Q1 roadmap. Design team will create mockups first.",
40
+ "timestamp": "2024-01-08T14:15:00Z"
41
+ }
42
+ ]
43
+ },
44
+ "ENG-440": {
45
+ "id": "ENG-440",
46
+ "title": "Login page occasionally shows white screen",
47
+ "status": "resolved",
48
+ "priority": "medium",
49
+ "assignee": "mike.frontend@company.com",
50
+ "labels": ["bug", "frontend", "login"],
51
+ "created_at": "2024-01-03T09:30:00Z",
52
+ "updated_at": "2024-01-10T16:00:00Z",
53
+ "description": "Some users report seeing a white screen when navigating to login page. Appears to be related to JavaScript bundle loading issues.",
54
+ "resolution": "Fixed race condition in bundle loading. Deployed fix in v2.1.3",
55
+ "comments": [
56
+ {
57
+ "author": "mike.frontend@company.com",
58
+ "content": "Issue was caused by missing error boundary. Added proper loading states.",
59
+ "timestamp": "2024-01-10T16:00:00Z"
60
+ }
61
+ ]
62
+ },
63
+ "ENG-438": {
64
+ "id": "ENG-438",
65
+ "title": "Implement password strength indicator",
66
+ "status": "completed",
67
+ "priority": "low",
68
+ "assignee": "jane.security@company.com",
69
+ "labels": ["security", "enhancement", "password"],
70
+ "created_at": "2024-01-01T11:00:00Z",
71
+ "updated_at": "2024-01-07T13:45:00Z",
72
+ "description": "Add real-time password strength indicator to signup and password reset forms to help users create secure passwords.",
73
+ "resolution": "Implemented using zxcvbn library. Shows strength meter and helpful suggestions.",
74
+ "comments": [
75
+ {
76
+ "author": "jane.security@company.com",
77
+ "content": "Added strength meter with color coding and actionable feedback.",
78
+ "timestamp": "2024-01-07T13:45:00Z"
79
+ }
80
+ ]
81
+ }
82
+ }
83
+ }
@@ -0,0 +1,71 @@
1
+ {
2
+ "john.smith@example.com": {
3
+ "customer_id": "cus_123abc",
4
+ "subscription_status": "active",
5
+ "current_plan": "Pro Plan",
6
+ "monthly_amount": 2999,
7
+ "currency": "usd",
8
+ "next_billing_date": "2024-02-15",
9
+ "payment_method": "card_ending_4242",
10
+ "recent_charges": [
11
+ {
12
+ "date": "2024-01-15",
13
+ "amount": 2999,
14
+ "status": "succeeded",
15
+ "description": "Pro Plan subscription"
16
+ },
17
+ {
18
+ "date": "2024-01-10",
19
+ "amount": 2999,
20
+ "status": "failed",
21
+ "description": "Pro Plan subscription - duplicate charge refunded"
22
+ }
23
+ ],
24
+ "total_paid": 89970,
25
+ "customer_since": "2023-12-01"
26
+ },
27
+ "sarah.j@startup.io": {
28
+ "customer_id": "cus_456def",
29
+ "subscription_status": "active",
30
+ "current_plan": "Basic Plan",
31
+ "monthly_amount": 999,
32
+ "currency": "usd",
33
+ "next_billing_date": "2024-02-01",
34
+ "payment_method": "card_ending_1234",
35
+ "recent_charges": [
36
+ {
37
+ "date": "2024-01-01",
38
+ "amount": 999,
39
+ "status": "succeeded",
40
+ "description": "Basic Plan subscription"
41
+ }
42
+ ],
43
+ "total_paid": 999,
44
+ "customer_since": "2024-01-01"
45
+ },
46
+ "mike.chen@techfirm.com": {
47
+ "customer_id": "cus_789ghi",
48
+ "subscription_status": "active",
49
+ "current_plan": "Enterprise Plan",
50
+ "monthly_amount": 9999,
51
+ "currency": "usd",
52
+ "next_billing_date": "2024-02-15",
53
+ "payment_method": "card_ending_5678",
54
+ "recent_charges": [
55
+ {
56
+ "date": "2024-01-15",
57
+ "amount": 9999,
58
+ "status": "succeeded",
59
+ "description": "Enterprise Plan subscription"
60
+ },
61
+ {
62
+ "date": "2023-12-15",
63
+ "amount": 9999,
64
+ "status": "succeeded",
65
+ "description": "Enterprise Plan subscription"
66
+ }
67
+ ],
68
+ "total_paid": 79992,
69
+ "customer_since": "2023-06-15"
70
+ }
71
+ }
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../../lib/agents"
5
+ require_relative "agents/copilot_orchestrator"
6
+
7
+ # Configure the agents SDK
8
+ Agents.configure do |config|
9
+ config.openai_api_key = ENV["OPENAI_API_KEY"]
10
+ config.default_model = "gpt-4o-mini"
11
+ config.debug = false
12
+ end
13
+
14
+ puts "=== Support Copilot Demo ==="
15
+ puts "This demonstrates agent-as-tool collaboration where specialist agents work behind the scenes."
16
+ puts
17
+
18
+ # Create the main copilot
19
+ copilot = Copilot::CopilotOrchestrator.create
20
+ runner = Agents::Runner.with_agents(copilot)
21
+ context = {}
22
+
23
+ # Demo scenarios
24
+ # scenarios = [
25
+ # {
26
+ # title: "API Errors - Enterprise Customer",
27
+ # query: "mike.chen@techfirm.com reporting API 500 errors (CONV-004). Help?"
28
+ # },
29
+ # {
30
+ # title: "Angry Customer - Login + Billing",
31
+ # query: "john.smith@example.com can't login AND has billing issues (CONV-001). Threatening to cancel. What do I do?"
32
+ # },
33
+ # {
34
+ # title: "Dark Mode Feature Request",
35
+ # query: "Customer asking about dark mode. Is this being worked on? Should I create a ticket?"
36
+ # },
37
+ # {
38
+ # title: "Enterprise Integration Issues",
39
+ # query: "CONTACT-789 can't get API integration working. Need help with response."
40
+ # }
41
+ # ]
42
+
43
+ # scenarios.each_with_index do |scenario, i|
44
+ # puts("-" * 60)
45
+ # puts "Scenario #{i + 1}: #{scenario[:title]}"
46
+ # puts("-" * 60)
47
+ # puts "Support Agent Query: #{scenario[:query]}"
48
+ # puts
49
+ # puts "Copilot Response:"
50
+ # puts
51
+
52
+ # begin
53
+ # result = runner.run(scenario[:query])
54
+ # puts result.output
55
+ # rescue StandardError => e
56
+ # puts "Error: #{e.message}"
57
+ # end
58
+
59
+ # puts
60
+ # puts "Press Enter to continue to next scenario..."
61
+ # gets
62
+ # puts
63
+ # end
64
+
65
+ puts "=== Interactive Mode ==="
66
+ puts "Now you can ask the copilot questions directly."
67
+ puts "Type 'exit' to quit."
68
+ puts
69
+
70
+ loop do
71
+ print "Support Agent: "
72
+ input = gets.chomp
73
+ break if input.downcase == "exit"
74
+
75
+ puts
76
+ puts "Copilot:"
77
+ begin
78
+ result = runner.run(input, context: context)
79
+
80
+ # Update context with the returned context from Runner
81
+ context = result.context if result.respond_to?(:context) && result.context
82
+
83
+ puts result.output
84
+ rescue StandardError => e
85
+ puts "Error: #{e.message}"
86
+ end
87
+ puts
88
+ end
89
+
90
+ puts "Thanks for trying the Support Copilot demo!"
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Copilot
6
+ # Tool for creating Linear tickets for engineering issues
7
+ class CreateLinearTicketTool < Agents::Tool
8
+ description "Create a Linear ticket for engineering issues or feature requests"
9
+ param :title, type: "string", desc: "Title of the issue"
10
+ param :description, type: "string", desc: "Detailed description of the issue"
11
+ param :priority, type: "string", desc: "Priority level (low, medium, high)"
12
+ param :assignee, type: "string", desc: "Optional: email of person to assign to", required: false
13
+ param :labels, type: "string", desc: "Comma-separated labels (e.g., bug,api,production)"
14
+
15
+ def perform(tool_context, title:, description:, priority: "medium", assignee: nil, labels: "")
16
+ # Generate a ticket ID
17
+ ticket_id = "ENG-#{rand(100..999)}"
18
+
19
+ # Parse labels
20
+ label_array = labels.split(",").map(&:strip).reject(&:empty?)
21
+ label_array = ["support-request"] if label_array.empty?
22
+
23
+ # Create ticket data
24
+ {
25
+ id: ticket_id,
26
+ title: title,
27
+ description: description,
28
+ status: "backlog",
29
+ priority: priority.downcase,
30
+ assignee: assignee,
31
+ labels: label_array,
32
+ created_at: Time.now.strftime("%Y-%m-%dT%H:%M:%SZ"),
33
+ reporter: "support-agent"
34
+ }
35
+
36
+ # Store ticket info in shared state
37
+ tool_context.state[:created_ticket_id] = ticket_id
38
+ tool_context.state[:ticket_priority] = priority
39
+ tool_context.state[:ticket_assignee] = assignee
40
+
41
+ response = {
42
+ success: true,
43
+ ticket_id: ticket_id,
44
+ title: title,
45
+ status: "backlog",
46
+ priority: priority,
47
+ assignee: assignee,
48
+ labels: label_array,
49
+ url: "https://linear.app/company/issue/#{ticket_id}",
50
+ message: "Successfully created Linear ticket #{ticket_id}"
51
+ }
52
+
53
+ JSON.pretty_generate(response)
54
+ rescue StandardError => e
55
+ "Error creating Linear ticket: #{e.message}"
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Copilot
6
+ # Tool for retrieving specific knowledge base articles by ID
7
+ class GetArticleTool < Agents::Tool
8
+ description "Get the full content of a specific knowledge base article"
9
+ param :article_id, type: "string", desc: "ID of the article to retrieve (e.g., ART-001)"
10
+
11
+ def perform(tool_context, article_id:)
12
+ data_file = File.join(__dir__, "../data/knowledge_base.json")
13
+ return "Knowledge base unavailable" unless File.exist?(data_file)
14
+
15
+ begin
16
+ kb_data = JSON.parse(File.read(data_file))
17
+ article = kb_data["articles"][article_id.upcase]
18
+
19
+ return "Article not found" unless article
20
+
21
+ # Store article data in shared state for reference
22
+ tool_context.state[:last_article_id] = article_id.upcase
23
+ tool_context.state[:last_article_category] = article["category"]
24
+
25
+ response = {
26
+ id: article_id.upcase,
27
+ title: article["title"],
28
+ category: article["category"],
29
+ content: article["content"],
30
+ tags: article["tags"],
31
+ created_at: article["created_at"],
32
+ updated_at: article["updated_at"]
33
+ }
34
+
35
+ JSON.pretty_generate(response)
36
+ rescue StandardError => e
37
+ "Error retrieving article: #{e.message}"
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Copilot
6
+ # Tool for retrieving contact/customer information
7
+ class GetContactTool < Agents::Tool
8
+ description "Get customer profile and contact information"
9
+ param :contact_id, type: "string", desc: "ID of the contact to retrieve"
10
+
11
+ def perform(tool_context, contact_id:)
12
+ data_file = File.join(__dir__, "../data/contacts.json")
13
+ return "Contacts database unavailable" unless File.exist?(data_file)
14
+
15
+ begin
16
+ contacts = JSON.parse(File.read(data_file))
17
+ contact = contacts[contact_id.upcase]
18
+
19
+ return "Contact not found" unless contact
20
+
21
+ # Store contact data in shared state for other agents
22
+ tool_context.state[:contact_name] = contact["name"]
23
+ tool_context.state[:contact_email] = contact["email"]
24
+ tool_context.state[:contact_company] = contact["company"]
25
+ tool_context.state[:contact_plan] = contact["plan"]
26
+ tool_context.state[:contact_satisfaction] = contact["satisfaction_score"]
27
+
28
+ # Format contact information
29
+ response = {
30
+ id: contact["id"],
31
+ name: contact["name"],
32
+ email: contact["email"],
33
+ phone: contact["phone"],
34
+ company: contact["company"],
35
+ plan: contact["plan"],
36
+ account_status: contact["account_status"],
37
+ created_at: contact["created_at"],
38
+ last_login: contact["last_login"],
39
+ total_conversations: contact["total_conversations"],
40
+ satisfaction_score: contact["satisfaction_score"],
41
+ tags: contact["tags"],
42
+ notes: contact["notes"]
43
+ }
44
+
45
+ JSON.pretty_generate(response)
46
+ rescue StandardError => e
47
+ "Error retrieving contact: #{e.message}"
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Copilot
6
+ # Tool for retrieving conversation details and context
7
+ class GetConversationTool < Agents::Tool
8
+ description "Get conversation details, messages, and context for analysis"
9
+ param :conversation_id, type: "string", desc: "ID of the conversation to retrieve"
10
+
11
+ def perform(tool_context, conversation_id:)
12
+ data_file = File.join(__dir__, "../data/conversations.json")
13
+ return "Conversations database unavailable" unless File.exist?(data_file)
14
+
15
+ begin
16
+ conversations = JSON.parse(File.read(data_file))
17
+ conversation = conversations[conversation_id.upcase]
18
+
19
+ return "Conversation not found" unless conversation
20
+
21
+ # Store conversation data in shared state for other agents
22
+ tool_context.state[:current_conversation_id] = conversation_id.upcase
23
+ tool_context.state[:conversation_status] = conversation["status"]
24
+ tool_context.state[:conversation_subject] = conversation["subject"]
25
+ tool_context.state[:contact_id] = conversation["contact_id"]
26
+
27
+ # Format conversation for analysis
28
+ formatted_messages = conversation["messages"].map do |msg|
29
+ "#{msg["role"].upcase} (#{msg["timestamp"]}): #{msg["content"]}"
30
+ end
31
+
32
+ response = {
33
+ id: conversation["id"],
34
+ subject: conversation["subject"],
35
+ status: conversation["status"],
36
+ contact_id: conversation["contact_id"],
37
+ created_at: conversation["created_at"],
38
+ message_count: conversation["messages"].length,
39
+ tags: conversation["tags"],
40
+ messages: formatted_messages
41
+ }
42
+
43
+ # Add resolution if available
44
+ response[:resolution] = conversation["resolution"] if conversation["resolution"]
45
+ response[:escalation_ticket] = conversation["escalation_ticket"] if conversation["escalation_ticket"]
46
+
47
+ JSON.pretty_generate(response)
48
+ rescue StandardError => e
49
+ "Error retrieving conversation: #{e.message}"
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Copilot
6
+ # Tool for retrieving Stripe billing information
7
+ class GetStripeBillingTool < Agents::Tool
8
+ description "Get customer billing information and payment history from Stripe"
9
+ param :customer_email, type: "string", desc: "Customer email to look up billing info"
10
+
11
+ def perform(tool_context, customer_email:)
12
+ data_file = File.join(__dir__, "../data/stripe_billing.json")
13
+ return "Stripe billing data unavailable" unless File.exist?(data_file)
14
+
15
+ begin
16
+ billing_data = JSON.parse(File.read(data_file))
17
+ customer_data = billing_data[customer_email.downcase]
18
+
19
+ return "No billing information found for #{customer_email}" unless customer_data
20
+
21
+ # Store billing info in shared state
22
+ tool_context.state[:stripe_customer_id] = customer_data["customer_id"]
23
+ tool_context.state[:subscription_status] = customer_data["subscription_status"]
24
+ tool_context.state[:current_plan] = customer_data["current_plan"]
25
+
26
+ formatted_data = {
27
+ customer_email: customer_email,
28
+ stripe_customer_id: customer_data["customer_id"],
29
+ subscription_status: customer_data["subscription_status"],
30
+ current_plan: customer_data["current_plan"],
31
+ monthly_amount: "$#{customer_data["monthly_amount"] / 100}",
32
+ next_billing_date: customer_data["next_billing_date"],
33
+ payment_method: customer_data["payment_method"],
34
+ total_paid: "$#{customer_data["total_paid"] / 100}",
35
+ recent_charges: customer_data["recent_charges"]
36
+ }
37
+
38
+ JSON.pretty_generate(formatted_data)
39
+ rescue StandardError => e
40
+ "Error retrieving billing information: #{e.message}"
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Copilot
6
+ # Tool for searching contacts to find patterns and related customers
7
+ class SearchContactsTool < Agents::Tool
8
+ description "Search contacts to find patterns, related customers, or specific profiles"
9
+ param :query, type: "string", desc: "Search terms (name, email, company, or tags)"
10
+ param :plan, type: "string", desc: "Optional: filter by plan type (Basic, Pro, Enterprise)", required: false
11
+
12
+ def perform(_tool_context, query:, plan: nil)
13
+ data_file = File.join(__dir__, "../data/contacts.json")
14
+ return "Contacts database unavailable" unless File.exist?(data_file)
15
+
16
+ begin
17
+ contacts = JSON.parse(File.read(data_file))
18
+ query_text = query.downcase
19
+ matches = []
20
+
21
+ contacts.each do |contact_id, contact|
22
+ # Skip if plan filter is specified and doesn't match
23
+ next if plan && contact["plan"].downcase != plan.downcase
24
+
25
+ # Simple keyword matching
26
+ text_to_search = [
27
+ contact["name"],
28
+ contact["email"],
29
+ contact["company"],
30
+ contact["tags"].join(" "),
31
+ contact["notes"]
32
+ ].join(" ").downcase
33
+
34
+ next unless text_to_search.include?(query_text)
35
+
36
+ matches << {
37
+ contact_id: contact_id,
38
+ name: contact["name"],
39
+ email: contact["email"],
40
+ company: contact["company"],
41
+ plan: contact["plan"],
42
+ tags: contact["tags"],
43
+ satisfaction_score: contact["satisfaction_score"]
44
+ }
45
+ end
46
+
47
+ if matches.empty?
48
+ "No contacts found for: #{query}"
49
+ else
50
+ JSON.pretty_generate({ query: query, contacts: matches })
51
+ end
52
+ rescue StandardError => e
53
+ "Error searching contacts: #{e.message}"
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Copilot
6
+ # Tool for searching through conversation history to find similar cases
7
+ class SearchConversationsTool < Agents::Tool
8
+ description "Search past conversations for similar cases and resolutions"
9
+ param :query, type: "string", desc: "Search terms (keywords, tags, or issue description)"
10
+ param :contact_id, type: "string", desc: "Optional: limit search to specific contact", required: false
11
+
12
+ def perform(_tool_context, query:, contact_id: nil)
13
+ data_file = File.join(__dir__, "../data/conversations.json")
14
+ return "Conversations database unavailable" unless File.exist?(data_file)
15
+
16
+ begin
17
+ conversations = JSON.parse(File.read(data_file))
18
+ query_text = query.downcase
19
+ matches = []
20
+
21
+ conversations.each do |conv_id, conversation|
22
+ # Skip if contact_id filter is specified and doesn't match
23
+ next if contact_id && conversation["contact_id"] != contact_id.upcase
24
+
25
+ # Simple keyword matching
26
+ text_to_search = [
27
+ conversation["subject"],
28
+ conversation["tags"].join(" "),
29
+ conversation["messages"].map { |m| m["content"] }.join(" "),
30
+ conversation["resolution"]
31
+ ].compact.join(" ").downcase
32
+
33
+ next unless text_to_search.include?(query_text)
34
+
35
+ matches << {
36
+ conversation_id: conv_id,
37
+ subject: conversation["subject"],
38
+ status: conversation["status"],
39
+ tags: conversation["tags"],
40
+ resolution: conversation["resolution"]
41
+ }
42
+ end
43
+
44
+ if matches.empty?
45
+ "No similar conversations found for: #{query}"
46
+ else
47
+ JSON.pretty_generate({ query: query, conversations: matches })
48
+ end
49
+ rescue StandardError => e
50
+ "Error searching conversations: #{e.message}"
51
+ end
52
+ end
53
+ end
54
+ end