agentbill-sdk 5.0.1 → 7.17.0
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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +5 -4
- data/CHANGELOG.md +29 -0
- data/examples/ollama_basic.rb +81 -0
- data/examples/perplexity_basic.rb +66 -0
- data/lib/agentbill/agents.rb +226 -0
- data/lib/agentbill/customers.rb +164 -0
- data/lib/agentbill/distributed.rb +109 -0
- data/lib/agentbill/exceptions.rb +84 -0
- data/lib/agentbill/ollama_wrapper.rb +153 -0
- data/lib/agentbill/orders.rb +283 -0
- data/lib/agentbill/perplexity_wrapper.rb +101 -0
- data/lib/agentbill/pricing.rb +52 -0
- data/lib/agentbill/signal_types.rb +179 -0
- data/lib/agentbill/signals.rb +199 -0
- data/lib/agentbill/tracer.rb +68 -11
- data/lib/agentbill/tracing.rb +343 -0
- data/lib/agentbill/version.rb +1 -1
- data/lib/agentbill/wrappers.rb +384 -0
- data/lib/agentbill.rb +252 -45
- metadata +16 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 55eed0a2b3bf82cdd116d56d4fdd3f41c8ab60166be0d456d1a5791a2bf12dbd
|
|
4
|
+
data.tar.gz: aae409ee8bd40d1ba774364ce570c6d5e4da4ad81847dc466433e755a2dc0e7d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5ca68a5403e625c73793d0d26b68795b56660f348e70d49b977981dc78f46429804cc25a736e1146763566472c78e8749263c1e42ba36d36670b18bbc709ead7
|
|
7
|
+
data.tar.gz: 831a7b5f9170db9a40b52411771a992a07d683c73fc31a350c69e1070d37f6007d3e44902b5c503dd254aa08e21f87f68ea7ac69965025900f9de73cabeb490b
|
data/.github/workflows/ci.yml
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
name: CI
|
|
2
2
|
|
|
3
3
|
on:
|
|
4
|
-
push:
|
|
5
|
-
branches: [ main, develop ]
|
|
6
4
|
pull_request:
|
|
7
5
|
branches: [ main, develop ]
|
|
6
|
+
paths:
|
|
7
|
+
- 'sdks/ruby/**'
|
|
8
|
+
- '.github/workflows/ci.yml'
|
|
8
9
|
|
|
9
10
|
jobs:
|
|
10
11
|
test:
|
|
@@ -13,8 +14,8 @@ jobs:
|
|
|
13
14
|
|
|
14
15
|
strategy:
|
|
15
16
|
matrix:
|
|
16
|
-
ruby-version: ['3.
|
|
17
|
-
os: [ubuntu-latest
|
|
17
|
+
ruby-version: ['3.3']
|
|
18
|
+
os: [ubuntu-latest]
|
|
18
19
|
|
|
19
20
|
steps:
|
|
20
21
|
- uses: actions/checkout@v4
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,35 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [7.6.3] - 2025-12-17
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- **Version Alignment**: Aligned with Python SDK v7.6.3 wrap_openai() semantic cache fix
|
|
12
|
+
- Ruby SDK already had cache hit check and cache population in `wrap_openai()`
|
|
13
|
+
|
|
14
|
+
## [6.8.8] - 2025-12-09
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- **Version Bump**: Aligned with Python SDK v6.8.8 wrapper trace context auto-generation fix
|
|
18
|
+
|
|
19
|
+
## [6.8.5] - 2025-12-09
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
- **Tracer Version Alignment**: Updated `service.version` and `scope.version` in OTLP payload to 6.8.5
|
|
23
|
+
|
|
24
|
+
## [6.8.4] - 2025-12-08
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
- **CRITICAL**: Fixed `agent_id` and `customer_id` not being sent as OTEL resource attributes
|
|
28
|
+
- The otel-collector extracts these from resource attributes, not top-level payload
|
|
29
|
+
- Added `agent_id` config option to SDK initialization
|
|
30
|
+
|
|
31
|
+
## [6.8.3] - 2025-12-08
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
- **Version Bump**: Aligned with Python SDK v6.8.3 trace context fix for decorator pattern
|
|
35
|
+
- No functional changes in Ruby SDK (fix was Python-specific)
|
|
36
|
+
|
|
8
37
|
## [5.0.1] - 2025-11-06
|
|
9
38
|
|
|
10
39
|
### Added
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# AgentBill v6.0.0 - Ollama Integration Example
|
|
2
|
+
# Tracks local Ollama chat and generation (metrics only, $0 cost)
|
|
3
|
+
|
|
4
|
+
require 'agentbill'
|
|
5
|
+
require 'net/http'
|
|
6
|
+
require 'json'
|
|
7
|
+
|
|
8
|
+
# Mock Ollama client (replace with actual Ollama SDK)
|
|
9
|
+
class OllamaClient
|
|
10
|
+
def chat(params)
|
|
11
|
+
uri = URI('http://localhost:11434/api/chat')
|
|
12
|
+
|
|
13
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
14
|
+
|
|
15
|
+
request = Net::HTTP::Post.new(uri.path)
|
|
16
|
+
request['Content-Type'] = 'application/json'
|
|
17
|
+
request.body = params.to_json
|
|
18
|
+
|
|
19
|
+
response = http.request(request)
|
|
20
|
+
JSON.parse(response.body, symbolize_names: true)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def generate(params)
|
|
24
|
+
uri = URI('http://localhost:11434/api/generate')
|
|
25
|
+
|
|
26
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
27
|
+
|
|
28
|
+
request = Net::HTTP::Post.new(uri.path)
|
|
29
|
+
request['Content-Type'] = 'application/json'
|
|
30
|
+
request.body = params.to_json
|
|
31
|
+
|
|
32
|
+
response = http.request(request)
|
|
33
|
+
JSON.parse(response.body, symbolize_names: true)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Initialize AgentBill
|
|
38
|
+
agentbill = AgentBill::Client.init({
|
|
39
|
+
api_key: ENV['AGENTBILL_API_KEY'],
|
|
40
|
+
customer_id: 'customer-123',
|
|
41
|
+
debug: true
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
# Create Ollama client
|
|
45
|
+
ollama = OllamaClient.new
|
|
46
|
+
|
|
47
|
+
# Wrap with AgentBill
|
|
48
|
+
ollama_wrapper = AgentBill::OllamaWrapper.new(ollama, agentbill.config, agentbill.tracer)
|
|
49
|
+
tracked_ollama = ollama_wrapper.wrap
|
|
50
|
+
|
|
51
|
+
puts "🦙 Using Ollama (local, free)...\n\n"
|
|
52
|
+
|
|
53
|
+
# Example 1: Chat
|
|
54
|
+
puts "💬 Chat example:"
|
|
55
|
+
chat_response = tracked_ollama.chat({
|
|
56
|
+
model: 'llama2',
|
|
57
|
+
messages: [
|
|
58
|
+
{
|
|
59
|
+
role: 'user',
|
|
60
|
+
content: 'Why is the sky blue? Give a brief answer.'
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
puts "✅ Response: #{chat_response[:message][:content]}"
|
|
66
|
+
puts "📊 Tokens: prompt=#{chat_response[:prompt_eval_count]}, completion=#{chat_response[:eval_count]}"
|
|
67
|
+
puts "💰 Cost: $0.00 (local model)\n\n"
|
|
68
|
+
|
|
69
|
+
# Example 2: Generate
|
|
70
|
+
puts "📝 Generate example:"
|
|
71
|
+
generate_response = tracked_ollama.generate({
|
|
72
|
+
model: 'llama2',
|
|
73
|
+
prompt: 'Write a haiku about coding:'
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
puts "✅ Response: #{generate_response[:response]}"
|
|
77
|
+
puts "📊 Tokens: prompt=#{generate_response[:prompt_eval_count]}, completion=#{generate_response[:eval_count]}"
|
|
78
|
+
puts "💰 Cost: $0.00 (local model)\n\n"
|
|
79
|
+
|
|
80
|
+
puts "ℹ️ Ollama runs locally, so there's no API cost."
|
|
81
|
+
puts " AgentBill tracks metrics for monitoring and analysis.\n"
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# AgentBill v6.0.0 - Perplexity AI Integration Example
|
|
2
|
+
# Tracks Perplexity AI chat completions with online search
|
|
3
|
+
|
|
4
|
+
require 'agentbill'
|
|
5
|
+
require 'net/http'
|
|
6
|
+
require 'json'
|
|
7
|
+
|
|
8
|
+
# Mock Perplexity client (replace with actual Perplexity SDK)
|
|
9
|
+
class PerplexityClient
|
|
10
|
+
def initialize(api_key)
|
|
11
|
+
@api_key = api_key
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def chat_create(params)
|
|
15
|
+
uri = URI('https://api.perplexity.ai/chat/completions')
|
|
16
|
+
|
|
17
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
18
|
+
http.use_ssl = true
|
|
19
|
+
|
|
20
|
+
request = Net::HTTP::Post.new(uri.path)
|
|
21
|
+
request['Authorization'] = "Bearer #{@api_key}"
|
|
22
|
+
request['Content-Type'] = 'application/json'
|
|
23
|
+
request.body = params.to_json
|
|
24
|
+
|
|
25
|
+
response = http.request(request)
|
|
26
|
+
JSON.parse(response.body, symbolize_names: true)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Initialize AgentBill
|
|
31
|
+
agentbill = AgentBill::Client.init({
|
|
32
|
+
api_key: ENV['AGENTBILL_API_KEY'],
|
|
33
|
+
customer_id: 'customer-123',
|
|
34
|
+
debug: true
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
# Create Perplexity client
|
|
38
|
+
perplexity = PerplexityClient.new(ENV['PERPLEXITY_API_KEY'])
|
|
39
|
+
|
|
40
|
+
# Wrap with AgentBill
|
|
41
|
+
perplexity_wrapper = AgentBill::PerplexityWrapper.new(perplexity, agentbill.config, agentbill.tracer)
|
|
42
|
+
tracked_perplexity = perplexity_wrapper.wrap
|
|
43
|
+
|
|
44
|
+
puts "🔍 Querying Perplexity AI with online search...\n\n"
|
|
45
|
+
|
|
46
|
+
# Make a request - automatically tracked
|
|
47
|
+
response = tracked_perplexity.chat_create({
|
|
48
|
+
model: 'llama-3.1-sonar-small-128k-online',
|
|
49
|
+
messages: [
|
|
50
|
+
{
|
|
51
|
+
role: 'system',
|
|
52
|
+
content: 'Be precise and concise.'
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
role: 'user',
|
|
56
|
+
content: 'What are the latest developments in AI for 2025?'
|
|
57
|
+
}
|
|
58
|
+
],
|
|
59
|
+
temperature: 0.2,
|
|
60
|
+
top_p: 0.9,
|
|
61
|
+
max_tokens: 1000
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
puts "✅ Response: #{response[:choices][0][:message][:content]}"
|
|
65
|
+
puts "\n📊 Usage: #{response[:usage]}"
|
|
66
|
+
puts "💰 Cost automatically tracked in AgentBill dashboard\n"
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# AgentBill Agents Resource - CRUD operations with signal type management
|
|
2
|
+
#
|
|
3
|
+
# Example:
|
|
4
|
+
# ab = AgentBill::Client.init(api_key: 'agb_...')
|
|
5
|
+
#
|
|
6
|
+
# # Create agent
|
|
7
|
+
# agent = ab.agents.create(name: 'Support Bot', description: 'Customer support')
|
|
8
|
+
# puts "API Key (save this!): #{agent['api_key']}"
|
|
9
|
+
#
|
|
10
|
+
# # Assign signal types
|
|
11
|
+
# ab.agents.assign_signal_types(
|
|
12
|
+
# agent_id: agent['id'],
|
|
13
|
+
# signal_type_names: ['ai_call', 'completion']
|
|
14
|
+
# )
|
|
15
|
+
#
|
|
16
|
+
# # List agents
|
|
17
|
+
# result = ab.agents.list(status: 'active')
|
|
18
|
+
#
|
|
19
|
+
# # Get signal types for agent
|
|
20
|
+
# signal_types = ab.agents.get_signal_types(agent['id'])
|
|
21
|
+
|
|
22
|
+
require 'net/http'
|
|
23
|
+
require 'json'
|
|
24
|
+
|
|
25
|
+
module AgentBill
|
|
26
|
+
class AgentsResource
|
|
27
|
+
def initialize(config)
|
|
28
|
+
@config = config
|
|
29
|
+
@base_url = config[:base_url] || 'https://api.agentbill.io'
|
|
30
|
+
@api_key = config[:api_key]
|
|
31
|
+
@debug = config[:debug] || false
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# List agents with optional status filter and pagination
|
|
35
|
+
#
|
|
36
|
+
# @param status [String, nil] Filter by status ('active', 'inactive', 'archived')
|
|
37
|
+
# @param limit [Integer] Number of agents to return (default: 50)
|
|
38
|
+
# @param offset [Integer] Offset for pagination (default: 0)
|
|
39
|
+
# @param search [String, nil] Search term to filter by name or description
|
|
40
|
+
# @return [Hash] Hash with 'data' (list of agents) and 'pagination' info
|
|
41
|
+
def list(status: nil, limit: 50, offset: 0, search: nil)
|
|
42
|
+
uri = URI("#{@base_url}/functions/v1/api-agents")
|
|
43
|
+
params = { limit: limit.to_s, offset: offset.to_s }
|
|
44
|
+
params[:status] = status if status
|
|
45
|
+
params[:search] = search if search
|
|
46
|
+
uri.query = URI.encode_www_form(params)
|
|
47
|
+
|
|
48
|
+
response = make_request(:get, uri)
|
|
49
|
+
handle_response(response, 'list agents')
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Create a new agent
|
|
53
|
+
# IMPORTANT: The response includes a one-time visible API key.
|
|
54
|
+
# Save this key securely - it cannot be retrieved again.
|
|
55
|
+
#
|
|
56
|
+
# @param name [String] Agent name (required)
|
|
57
|
+
# @param description [String, nil] Agent description
|
|
58
|
+
# @param agent_type [String, nil] Type of agent (e.g., 'support', 'sales', 'assistant')
|
|
59
|
+
# @param model [String, nil] Default model for this agent
|
|
60
|
+
# @param external_id [String, nil] Your external ID for this agent
|
|
61
|
+
# @param metadata [Hash, nil] Additional metadata
|
|
62
|
+
# @param signal_type_names [Array<String>, nil] Signal types to auto-assign (e.g., ['ai_call', 'completion'])
|
|
63
|
+
# @return [Hash] Created agent object with api_key (one-time visible)
|
|
64
|
+
def create(name:, description: nil, agent_type: nil, model: nil, external_id: nil, metadata: nil, signal_type_names: nil)
|
|
65
|
+
uri = URI("#{@base_url}/functions/v1/api-agents")
|
|
66
|
+
|
|
67
|
+
payload = { name: name }
|
|
68
|
+
payload[:description] = description if description
|
|
69
|
+
payload[:agent_type] = agent_type if agent_type
|
|
70
|
+
payload[:model] = model if model
|
|
71
|
+
payload[:external_id] = external_id if external_id
|
|
72
|
+
payload[:metadata] = metadata if metadata
|
|
73
|
+
payload[:signal_type_names] = signal_type_names if signal_type_names
|
|
74
|
+
|
|
75
|
+
response = make_request(:post, uri, payload)
|
|
76
|
+
handle_response(response, 'create agent')
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Get an agent by ID or external_id
|
|
80
|
+
#
|
|
81
|
+
# @param agent_id [String] Agent UUID or external_id
|
|
82
|
+
# @param by_external_id [Boolean] If true, treat agent_id as external_id
|
|
83
|
+
# @return [Hash] Agent object
|
|
84
|
+
def get(agent_id, by_external_id: false)
|
|
85
|
+
uri = URI("#{@base_url}/functions/v1/api-agents")
|
|
86
|
+
params = by_external_id ? { external_id: agent_id } : { id: agent_id }
|
|
87
|
+
uri.query = URI.encode_www_form(params)
|
|
88
|
+
|
|
89
|
+
response = make_request(:get, uri)
|
|
90
|
+
result = handle_response(response, 'get agent')
|
|
91
|
+
|
|
92
|
+
if result['data'].is_a?(Array)
|
|
93
|
+
raise "Agent not found" if result['data'].empty?
|
|
94
|
+
return result['data'][0]
|
|
95
|
+
end
|
|
96
|
+
result
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Update an agent
|
|
100
|
+
#
|
|
101
|
+
# @param agent_id [String] Agent UUID
|
|
102
|
+
# @param name [String, nil] New name
|
|
103
|
+
# @param description [String, nil] New description
|
|
104
|
+
# @param agent_type [String, nil] New agent type
|
|
105
|
+
# @param model [String, nil] New default model
|
|
106
|
+
# @param status [String, nil] New status ('active', 'inactive', 'archived')
|
|
107
|
+
# @param metadata [Hash, nil] New metadata (replaces existing)
|
|
108
|
+
# @return [Hash] Updated agent object
|
|
109
|
+
def update(agent_id, name: nil, description: nil, agent_type: nil, model: nil, status: nil, metadata: nil)
|
|
110
|
+
uri = URI("#{@base_url}/functions/v1/api-agents")
|
|
111
|
+
|
|
112
|
+
payload = { id: agent_id }
|
|
113
|
+
payload[:name] = name if name
|
|
114
|
+
payload[:description] = description if description
|
|
115
|
+
payload[:agent_type] = agent_type if agent_type
|
|
116
|
+
payload[:model] = model if model
|
|
117
|
+
payload[:status] = status if status
|
|
118
|
+
payload[:metadata] = metadata if metadata
|
|
119
|
+
|
|
120
|
+
response = make_request(:patch, uri, payload)
|
|
121
|
+
handle_response(response, 'update agent')
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Delete an agent
|
|
125
|
+
#
|
|
126
|
+
# @param agent_id [String] Agent UUID to delete
|
|
127
|
+
def delete(agent_id)
|
|
128
|
+
uri = URI("#{@base_url}/functions/v1/api-agents")
|
|
129
|
+
payload = { id: agent_id }
|
|
130
|
+
|
|
131
|
+
response = make_request(:delete, uri, payload)
|
|
132
|
+
handle_response(response, 'delete agent')
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Assign signal types to an agent (explicit assignment)
|
|
136
|
+
# This method explicitly configures which signal types the agent can emit.
|
|
137
|
+
#
|
|
138
|
+
# @param agent_id [String, nil] Agent UUID (provide this OR external_agent_id)
|
|
139
|
+
# @param external_agent_id [String, nil] External agent ID (provide this OR agent_id)
|
|
140
|
+
# @param signal_type_ids [Array<String>, nil] List of signal type UUIDs to assign
|
|
141
|
+
# @param signal_type_names [Array<String>, nil] List of signal type names (e.g., ['ai_call', 'completion'])
|
|
142
|
+
# @return [Hash] Hash with agent_id, signal_type_ids, and assigned_count
|
|
143
|
+
def assign_signal_types(agent_id: nil, external_agent_id: nil, signal_type_ids: nil, signal_type_names: nil)
|
|
144
|
+
raise ArgumentError, "Either agent_id or external_agent_id is required" unless agent_id || external_agent_id
|
|
145
|
+
raise ArgumentError, "Either signal_type_ids or signal_type_names is required" unless signal_type_ids || signal_type_names
|
|
146
|
+
|
|
147
|
+
uri = URI("#{@base_url}/functions/v1/api-agent-signal-types")
|
|
148
|
+
|
|
149
|
+
payload = {}
|
|
150
|
+
payload[:agent_id] = agent_id if agent_id
|
|
151
|
+
payload[:external_agent_id] = external_agent_id if external_agent_id
|
|
152
|
+
payload[:signal_type_ids] = signal_type_ids if signal_type_ids
|
|
153
|
+
payload[:signal_type_names] = signal_type_names if signal_type_names
|
|
154
|
+
|
|
155
|
+
response = make_request(:post, uri, payload)
|
|
156
|
+
handle_response(response, 'assign signal types')
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Get signal types assigned to an agent
|
|
160
|
+
#
|
|
161
|
+
# @param agent_id [String] Agent UUID or external_id
|
|
162
|
+
# @param by_external_id [Boolean] If true, treat agent_id as external_id
|
|
163
|
+
# @return [Hash] Hash with agent_id and list of signal_types
|
|
164
|
+
def get_signal_types(agent_id, by_external_id: false)
|
|
165
|
+
uri = URI("#{@base_url}/functions/v1/api-agent-signal-types")
|
|
166
|
+
params = by_external_id ? { external_agent_id: agent_id } : { agent_id: agent_id }
|
|
167
|
+
uri.query = URI.encode_www_form(params)
|
|
168
|
+
|
|
169
|
+
response = make_request(:get, uri)
|
|
170
|
+
handle_response(response, 'get signal types')
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Remove signal types from an agent
|
|
174
|
+
#
|
|
175
|
+
# @param agent_id [String] Agent UUID
|
|
176
|
+
# @param signal_type_ids [Array<String>, nil] Specific signal type IDs to remove. If nil, removes all.
|
|
177
|
+
def remove_signal_types(agent_id, signal_type_ids: nil)
|
|
178
|
+
uri = URI("#{@base_url}/functions/v1/api-agent-signal-types")
|
|
179
|
+
|
|
180
|
+
payload = { agent_id: agent_id }
|
|
181
|
+
payload[:signal_type_ids] = signal_type_ids if signal_type_ids
|
|
182
|
+
|
|
183
|
+
response = make_request(:delete, uri, payload)
|
|
184
|
+
handle_response(response, 'remove signal types')
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
private
|
|
188
|
+
|
|
189
|
+
def make_request(method, uri, payload = nil)
|
|
190
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
191
|
+
http.use_ssl = true
|
|
192
|
+
http.read_timeout = 30
|
|
193
|
+
|
|
194
|
+
case method
|
|
195
|
+
when :get
|
|
196
|
+
request = Net::HTTP::Get.new(uri)
|
|
197
|
+
when :post
|
|
198
|
+
request = Net::HTTP::Post.new(uri.path)
|
|
199
|
+
request.body = payload.to_json if payload
|
|
200
|
+
when :patch
|
|
201
|
+
request = Net::HTTP::Patch.new(uri.path)
|
|
202
|
+
request.body = payload.to_json if payload
|
|
203
|
+
when :delete
|
|
204
|
+
request = Net::HTTP::Delete.new(uri.path)
|
|
205
|
+
request.body = payload.to_json if payload
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
request['Content-Type'] = 'application/json'
|
|
209
|
+
request['X-API-Key'] = @api_key
|
|
210
|
+
|
|
211
|
+
http.request(request)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def handle_response(response, operation)
|
|
215
|
+
body = response.body ? JSON.parse(response.body) : {}
|
|
216
|
+
|
|
217
|
+
unless %w[200 201 204].include?(response.code)
|
|
218
|
+
error_msg = body['error'] || "Failed to #{operation}"
|
|
219
|
+
raise error_msg
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
puts "[AgentBill] #{operation} successful" if @debug
|
|
223
|
+
body
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# AgentBill Customers Resource - CRUD operations
|
|
2
|
+
#
|
|
3
|
+
# Example:
|
|
4
|
+
# ab = AgentBill::Client.init(api_key: 'agb_...')
|
|
5
|
+
#
|
|
6
|
+
# # List customers
|
|
7
|
+
# result = ab.customers.list(limit: 10)
|
|
8
|
+
#
|
|
9
|
+
# # Create customer
|
|
10
|
+
# customer = ab.customers.create(name: 'Acme', email: 'a@b.com')
|
|
11
|
+
#
|
|
12
|
+
# # Get customer by ID
|
|
13
|
+
# customer = ab.customers.get('cust-123')
|
|
14
|
+
#
|
|
15
|
+
# # Update customer
|
|
16
|
+
# customer = ab.customers.update('cust-123', name: 'Acme Inc')
|
|
17
|
+
#
|
|
18
|
+
# # Delete customer
|
|
19
|
+
# ab.customers.delete('cust-123')
|
|
20
|
+
|
|
21
|
+
require 'net/http'
|
|
22
|
+
require 'json'
|
|
23
|
+
|
|
24
|
+
module AgentBill
|
|
25
|
+
class CustomersResource
|
|
26
|
+
def initialize(config)
|
|
27
|
+
@config = config
|
|
28
|
+
@base_url = config[:base_url] || 'https://api.agentbill.io'
|
|
29
|
+
@api_key = config[:api_key]
|
|
30
|
+
@debug = config[:debug] || false
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# List customers with pagination and search
|
|
34
|
+
#
|
|
35
|
+
# @param limit [Integer] Number of customers to return (default: 50)
|
|
36
|
+
# @param offset [Integer] Offset for pagination (default: 0)
|
|
37
|
+
# @param search [String, nil] Search term to filter by name or email
|
|
38
|
+
# @return [Hash] Hash with 'data' (list of customers) and 'pagination' info
|
|
39
|
+
def list(limit: 50, offset: 0, search: nil)
|
|
40
|
+
uri = URI("#{@base_url}/functions/v1/api-customers")
|
|
41
|
+
params = { limit: limit.to_s, offset: offset.to_s }
|
|
42
|
+
params[:search] = search if search
|
|
43
|
+
uri.query = URI.encode_www_form(params)
|
|
44
|
+
|
|
45
|
+
response = make_request(:get, uri)
|
|
46
|
+
handle_response(response, 'list customers')
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Create a new customer
|
|
50
|
+
#
|
|
51
|
+
# @param name [String] Customer name (required)
|
|
52
|
+
# @param email [String] Customer email (required)
|
|
53
|
+
# @param phone [String, nil] Phone number
|
|
54
|
+
# @param website [String, nil] Website URL
|
|
55
|
+
# @param external_id [String, nil] Your external ID for this customer
|
|
56
|
+
# @param metadata [Hash, nil] Additional metadata
|
|
57
|
+
# @return [Hash] Created customer object
|
|
58
|
+
def create(name:, email:, phone: nil, website: nil, external_id: nil, metadata: nil)
|
|
59
|
+
uri = URI("#{@base_url}/functions/v1/api-customers")
|
|
60
|
+
|
|
61
|
+
payload = { name: name, email: email }
|
|
62
|
+
payload[:phone] = phone if phone
|
|
63
|
+
payload[:website] = website if website
|
|
64
|
+
payload[:external_id] = external_id if external_id
|
|
65
|
+
payload[:metadata] = metadata if metadata
|
|
66
|
+
|
|
67
|
+
response = make_request(:post, uri, payload)
|
|
68
|
+
handle_response(response, 'create customer')
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Get a customer by ID or external_id
|
|
72
|
+
#
|
|
73
|
+
# @param customer_id [String] Customer UUID or external_id
|
|
74
|
+
# @param by_external_id [Boolean] If true, treat customer_id as external_id
|
|
75
|
+
# @return [Hash] Customer object
|
|
76
|
+
def get(customer_id, by_external_id: false)
|
|
77
|
+
uri = URI("#{@base_url}/functions/v1/api-customers")
|
|
78
|
+
params = by_external_id ? { external_id: customer_id } : { id: customer_id }
|
|
79
|
+
uri.query = URI.encode_www_form(params)
|
|
80
|
+
|
|
81
|
+
response = make_request(:get, uri)
|
|
82
|
+
result = handle_response(response, 'get customer')
|
|
83
|
+
|
|
84
|
+
if result['data'].is_a?(Array)
|
|
85
|
+
raise "Customer not found" if result['data'].empty?
|
|
86
|
+
return result['data'][0]
|
|
87
|
+
end
|
|
88
|
+
result
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Update a customer
|
|
92
|
+
#
|
|
93
|
+
# @param customer_id [String] Customer UUID
|
|
94
|
+
# @param name [String, nil] New name
|
|
95
|
+
# @param email [String, nil] New email
|
|
96
|
+
# @param phone [String, nil] New phone
|
|
97
|
+
# @param website [String, nil] New website
|
|
98
|
+
# @param metadata [Hash, nil] New metadata (replaces existing)
|
|
99
|
+
# @return [Hash] Updated customer object
|
|
100
|
+
def update(customer_id, name: nil, email: nil, phone: nil, website: nil, metadata: nil)
|
|
101
|
+
uri = URI("#{@base_url}/functions/v1/api-customers")
|
|
102
|
+
|
|
103
|
+
payload = { id: customer_id }
|
|
104
|
+
payload[:name] = name if name
|
|
105
|
+
payload[:email] = email if email
|
|
106
|
+
payload[:phone] = phone if phone
|
|
107
|
+
payload[:website] = website if website
|
|
108
|
+
payload[:metadata] = metadata if metadata
|
|
109
|
+
|
|
110
|
+
response = make_request(:patch, uri, payload)
|
|
111
|
+
handle_response(response, 'update customer')
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Delete a customer
|
|
115
|
+
#
|
|
116
|
+
# @param customer_id [String] Customer UUID to delete
|
|
117
|
+
def delete(customer_id)
|
|
118
|
+
uri = URI("#{@base_url}/functions/v1/api-customers")
|
|
119
|
+
payload = { id: customer_id }
|
|
120
|
+
|
|
121
|
+
response = make_request(:delete, uri, payload)
|
|
122
|
+
handle_response(response, 'delete customer')
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
private
|
|
126
|
+
|
|
127
|
+
def make_request(method, uri, payload = nil)
|
|
128
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
129
|
+
http.use_ssl = true
|
|
130
|
+
http.read_timeout = 30
|
|
131
|
+
|
|
132
|
+
case method
|
|
133
|
+
when :get
|
|
134
|
+
request = Net::HTTP::Get.new(uri)
|
|
135
|
+
when :post
|
|
136
|
+
request = Net::HTTP::Post.new(uri.path)
|
|
137
|
+
request.body = payload.to_json if payload
|
|
138
|
+
when :patch
|
|
139
|
+
request = Net::HTTP::Patch.new(uri.path)
|
|
140
|
+
request.body = payload.to_json if payload
|
|
141
|
+
when :delete
|
|
142
|
+
request = Net::HTTP::Delete.new(uri.path)
|
|
143
|
+
request.body = payload.to_json if payload
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
request['Content-Type'] = 'application/json'
|
|
147
|
+
request['X-API-Key'] = @api_key
|
|
148
|
+
|
|
149
|
+
http.request(request)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def handle_response(response, operation)
|
|
153
|
+
body = response.body ? JSON.parse(response.body) : {}
|
|
154
|
+
|
|
155
|
+
unless %w[200 201 204].include?(response.code)
|
|
156
|
+
error_msg = body['error'] || "Failed to #{operation}"
|
|
157
|
+
raise error_msg
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
puts "[AgentBill] #{operation} successful" if @debug
|
|
161
|
+
body
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|