agentbill-sdk 1.0.2 → 2.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 17b96a78039bd5ad557ee6abc8606a2240e9e4d9e69a496fd1fa99bd3d1814a1
4
- data.tar.gz: e126680a47d95e27ec984f94cba1631ed86c056111a1b40f2e7226c9cb274432
3
+ metadata.gz: d9f3faf536ea2cdd78d8d97f3200ab80bab8889654b924a2f035a35685aa210f
4
+ data.tar.gz: 4397d5f95b5e13bee80a28f7a27165a4f8b5e60c5f478bbfd19b11030651413f
5
5
  SHA512:
6
- metadata.gz: 2d465e3290f46ce834684c4960d78b61041d562f6378b2c234df7713d41d9927809559f2c907ab49fa10110c24c7ab16f50ec4a3203e2d0b8e64cb605c980637
7
- data.tar.gz: c29f3c706608e6e43659aff341a89d6f809ebda394c18edf1e7f6bd99c3256569bb2dbb2714bd855ec8c248cea3d7a0c7ade528e902f87da8f9064d90a5076c6
6
+ metadata.gz: 625bad2690c2dd452d299a5664cca0db3d2e806ab0318ce0a0ce7330b6ce6b873db4213b63e2123286ade6593b13ece94de3b1550e96f5f26b4dd2122c6921ba
7
+ data.tar.gz: 3257333096fda49a819738485c457e69741c556b35281648890533e58a3819d631289e9fc170575a55bf598d41a6d2aafe3c7e42bee8c7ea4c25b95bf9937672
data/CHANGELOG.md CHANGED
@@ -1,4 +1,5 @@
1
1
  # Changelog
2
+ # Force sync - Updated to 1.0.2
2
3
 
3
4
  All notable changes to this project will be documented in this file.
4
5
 
data/agentbill.gemspec CHANGED
@@ -4,7 +4,7 @@ Gem::Specification.new do |spec|
4
4
  spec.name = "agentbill-sdk"
5
5
  spec.version = AgentBill::VERSION
6
6
  spec.authors = ["AgentBill"]
7
- spec.email = ["support@agentbill.com"]
7
+ spec.email = ["dominic@agentbill.io"]
8
8
 
9
9
  spec.summary = "OpenTelemetry-based SDK for tracking AI agent usage and billing"
10
10
  spec.description = "Automatically track and bill AI agent usage with zero-config instrumentation for OpenAI, Anthropic, and more"
@@ -0,0 +1,69 @@
1
+ =begin
2
+ Zero-Config AI Cost Guard Example
3
+
4
+ Just wrap your AI client - that's it!
5
+
6
+ What happens automatically:
7
+ - Provider detection (OpenAI, Anthropic, etc.)
8
+ - Budget validation BEFORE spending (when daily_budget or monthly_budget is set)
9
+ - Automatic blocking if budget would be exceeded
10
+ - Usage tracking AFTER completion
11
+ =end
12
+
13
+ require 'agentbill'
14
+ require 'openai'
15
+
16
+ # 1. Initialize AgentBill with your API key and budget
17
+ agentbill = AgentBill::Client.init({
18
+ api_key: ENV['AGENTBILL_API_KEY'],
19
+ customer_id: 'customer-123',
20
+ daily_budget: 10.00, # Optional: $10/day limit (enables Cost Guard)
21
+ monthly_budget: 200.00, # Optional: $200/month limit (enables Cost Guard)
22
+ debug: true
23
+ })
24
+
25
+ # 2. Wrap your AI client - provider auto-detected!
26
+ openai = agentbill.wrap_openai(OpenAI::Client.new(
27
+ access_token: ENV['OPENAI_API_KEY']
28
+ ))
29
+
30
+ # 3. Use normally - Cost Guard protection happens automatically
31
+ begin
32
+ response = openai.chat({
33
+ model: 'gpt-4o-mini',
34
+ messages: [
35
+ { role: 'user', content: 'What is the capital of France?' }
36
+ ]
37
+ })
38
+
39
+ puts '✅ Response: ' + response['choices'][0]['message']['content']
40
+ puts '💰 Cost tracked automatically'
41
+ puts '🛡️ Budget validated BEFORE spending'
42
+
43
+ rescue => e
44
+ if e.respond_to?(:code)
45
+ case e.code
46
+ when 'BUDGET_EXCEEDED'
47
+ puts '❌ Budget limit reached - request blocked by Cost Guard'
48
+ when 'RATE_LIMIT_EXCEEDED'
49
+ puts '❌ Rate limit reached - request blocked by Cost Guard'
50
+ else
51
+ raise e
52
+ end
53
+ else
54
+ raise e
55
+ end
56
+ end
57
+
58
+ =begin
59
+ That's it! Zero configuration needed.
60
+
61
+ Cost Guard Features:
62
+ - ✅ Hard budget limits (daily/monthly) - request blocked BEFORE spending
63
+ - ✅ Real-time cost estimation and validation
64
+ - ✅ Automatic usage tracking
65
+ - ✅ Works across all providers (OpenAI, Anthropic, Bedrock, Azure, Mistral, Google AI)
66
+
67
+ To disable Cost Guard: Simply remove daily_budget and monthly_budget from init.
68
+ Advanced features (caching, fallback, etc.) coming in Pro tier.
69
+ =end
@@ -1,3 +1,3 @@
1
1
  module AgentBill
2
- VERSION = "1.0.2"
2
+ VERSION = "2.0.0"
3
3
  end
data/lib/agentbill.rb CHANGED
@@ -17,29 +17,142 @@ module AgentBill
17
17
  new(config)
18
18
  end
19
19
 
20
+ private
21
+
22
+ def estimate_tokens(text)
23
+ # Simple token estimation: ~4 chars per token
24
+ [1, text.to_s.length / 4].max
25
+ end
26
+
27
+ def estimate_cost(model, input_tokens, output_tokens)
28
+ # Model pricing (per 1K tokens)
29
+ pricing = {
30
+ 'gpt-4' => { input: 0.03, output: 0.06 },
31
+ 'gpt-4o' => { input: 0.005, output: 0.015 },
32
+ 'gpt-4o-mini' => { input: 0.00015, output: 0.0006 },
33
+ 'claude-sonnet-4-5' => { input: 0.003, output: 0.015 },
34
+ 'claude-opus-4-1-20250805' => { input: 0.015, output: 0.075 },
35
+ 'claude-3-5-sonnet-20241022' => { input: 0.003, output: 0.015 },
36
+ 'mistral-large-latest' => { input: 0.004, output: 0.012 },
37
+ 'gemini-pro' => { input: 0.00025, output: 0.0005 }
38
+ }
39
+
40
+ model_price = pricing[model] || { input: 0.001, output: 0.002 }
41
+ (input_tokens / 1000.0 * model_price[:input]) + (output_tokens / 1000.0 * model_price[:output])
42
+ end
43
+
44
+ def validate_request(model, messages, estimated_output_tokens = 1000)
45
+ return { 'allowed' => true } unless @config[:daily_budget] || @config[:monthly_budget]
46
+
47
+ uri = URI("#{@config[:base_url] || 'https://bgwyprqxtdreuutzpbgw.supabase.co'}/functions/v1/ai-cost-guard-router")
48
+
49
+ payload = {
50
+ api_key: @config[:api_key],
51
+ customer_id: @config[:customer_id],
52
+ model: model,
53
+ messages: messages
54
+ }
55
+
56
+ begin
57
+ http = Net::HTTP.new(uri.host, uri.port)
58
+ http.use_ssl = true
59
+ http.read_timeout = 10
60
+
61
+ request = Net::HTTP::Post.new(uri.path)
62
+ request['Content-Type'] = 'application/json'
63
+ request.body = payload.to_json
64
+
65
+ response = http.request(request)
66
+ result = JSON.parse(response.body)
67
+
68
+ puts "[AgentBill Cost Guard] Router response: #{result}" if @config[:debug]
69
+ puts "[AgentBill] Tier: #{result['tier']}, Mode: #{result['mode']}" if @config[:debug] && result['tier']
70
+ result
71
+ rescue => e
72
+ puts "[AgentBill Cost Guard] Router failed: #{e.message}" if @config[:debug]
73
+ { 'allowed' => true } # Fail open
74
+ end
75
+ end
76
+
77
+ def track_usage(model, provider, input_tokens, output_tokens, latency_ms, cost)
78
+ uri = URI("#{@config[:base_url] || 'https://bgwyprqxtdreuutzpbgw.supabase.co'}/functions/v1/track-ai-usage")
79
+
80
+ payload = {
81
+ api_key: @config[:api_key],
82
+ customer_id: @config[:customer_id],
83
+ model: model,
84
+ provider: provider,
85
+ prompt_tokens: input_tokens,
86
+ completion_tokens: output_tokens,
87
+ latency_ms: latency_ms,
88
+ cost: cost
89
+ }
90
+
91
+ begin
92
+ http = Net::HTTP.new(uri.host, uri.port)
93
+ http.use_ssl = true
94
+ http.read_timeout = 10
95
+
96
+ request = Net::HTTP::Post.new(uri.path)
97
+ request['Content-Type'] = 'application/json'
98
+ request.body = payload.to_json
99
+
100
+ http.request(request)
101
+ puts "[AgentBill Cost Guard] Usage tracked: $#{format('%.4f', cost)}" if @config[:debug]
102
+ rescue => e
103
+ puts "[AgentBill Cost Guard] Tracking failed: #{e.message}" if @config[:debug]
104
+ end
105
+ end
106
+
107
+ public
108
+
20
109
  def wrap_openai(client)
21
110
  original_method = client.method(:chat)
111
+ config = @config
112
+ tracer = @tracer
22
113
 
23
114
  client.define_singleton_method(:chat) do |params|
24
- start_time = Time.now
115
+ model = params[:model] || 'unknown'
116
+ messages = params[:messages] || []
117
+ max_tokens = params[:max_tokens] || params[:max_completion_tokens] || 1000
118
+
119
+ # Phase 1: Validate budget BEFORE API call
120
+ validation = config[:_client].send(:validate_request, model, messages, max_tokens)
121
+ unless validation['allowed']
122
+ error_msg = validation['reason'] || 'Budget limit reached'
123
+ puts "[AgentBill Cost Guard] ❌ Request blocked: #{error_msg}" if config[:debug]
124
+ error = StandardError.new(error_msg)
125
+ error.define_singleton_method(:code) { 'BUDGET_EXCEEDED' }
126
+ raise error
127
+ end
25
128
 
26
- span = @tracer.start_span('openai.chat.completion', {
27
- 'model' => params[:model] || 'unknown',
129
+ # Phase 2: Execute AI call
130
+ start_time = Time.now
131
+ span = tracer.start_span('openai.chat.completion', {
132
+ 'model' => model,
28
133
  'provider' => 'openai'
29
134
  })
30
135
 
31
136
  begin
32
137
  response = original_method.call(params)
33
-
34
138
  latency = ((Time.now - start_time) * 1000).round
139
+
140
+ # Phase 3: Track actual usage
141
+ input_tokens = response.dig(:usage, :prompt_tokens) || 0
142
+ output_tokens = response.dig(:usage, :completion_tokens) || 0
143
+ cost = config[:_client].send(:estimate_cost, model, input_tokens, output_tokens)
144
+
145
+ config[:_client].send(:track_usage, model, 'openai', input_tokens, output_tokens, latency, cost)
146
+
35
147
  span.set_attributes({
36
- 'response.prompt_tokens' => response.dig(:usage, :prompt_tokens),
37
- 'response.completion_tokens' => response.dig(:usage, :completion_tokens),
148
+ 'response.prompt_tokens' => input_tokens,
149
+ 'response.completion_tokens' => output_tokens,
38
150
  'response.total_tokens' => response.dig(:usage, :total_tokens),
39
151
  'latency_ms' => latency
40
152
  })
41
153
  span.set_status(0)
42
154
 
155
+ puts "[AgentBill Cost Guard] ✓ Protected call completed: $#{format('%.4f', cost)}" if config[:debug]
43
156
  response
44
157
  rescue => e
45
158
  span.set_status(1, e.message)
@@ -49,31 +162,57 @@ module AgentBill
49
162
  end
50
163
  end
51
164
 
165
+ # Store reference to self for helper methods
166
+ @config[:_client] = self
52
167
  client
53
168
  end
54
169
 
55
170
  def wrap_anthropic(client)
56
171
  original_method = client.method(:messages)
172
+ config = @config
173
+ tracer = @tracer
57
174
 
58
175
  client.define_singleton_method(:messages) do |params|
59
- start_time = Time.now
176
+ model = params[:model] || 'unknown'
177
+ messages = params[:messages] || []
178
+ max_tokens = params[:max_tokens] || 1000
60
179
 
61
- span = @tracer.start_span('anthropic.message', {
62
- 'model' => params[:model] || 'unknown',
180
+ # Phase 1: Validate budget BEFORE API call
181
+ validation = config[:_client].send(:validate_request, model, messages, max_tokens)
182
+ unless validation['allowed']
183
+ error_msg = validation['reason'] || 'Budget limit reached'
184
+ puts "[AgentBill Cost Guard] ❌ Request blocked: #{error_msg}" if config[:debug]
185
+ error = StandardError.new(error_msg)
186
+ error.define_singleton_method(:code) { 'BUDGET_EXCEEDED' }
187
+ raise error
188
+ end
189
+
190
+ # Phase 2: Execute AI call
191
+ start_time = Time.now
192
+ span = tracer.start_span('anthropic.message', {
193
+ 'model' => model,
63
194
  'provider' => 'anthropic'
64
195
  })
65
196
 
66
197
  begin
67
198
  response = original_method.call(params)
68
-
69
199
  latency = ((Time.now - start_time) * 1000).round
200
+
201
+ # Phase 3: Track actual usage
202
+ input_tokens = response.dig(:usage, :input_tokens) || 0
203
+ output_tokens = response.dig(:usage, :output_tokens) || 0
204
+ cost = config[:_client].send(:estimate_cost, model, input_tokens, output_tokens)
205
+
206
+ config[:_client].send(:track_usage, model, 'anthropic', input_tokens, output_tokens, latency, cost)
207
+
70
208
  span.set_attributes({
71
- 'response.input_tokens' => response.dig(:usage, :input_tokens),
72
- 'response.output_tokens' => response.dig(:usage, :output_tokens),
209
+ 'response.input_tokens' => input_tokens,
210
+ 'response.output_tokens' => output_tokens,
73
211
  'latency_ms' => latency
74
212
  })
75
213
  span.set_status(0)
76
214
 
215
+ puts "[AgentBill Cost Guard] ✓ Protected call completed: $#{format('%.4f', cost)}" if config[:debug]
77
216
  response
78
217
  rescue => e
79
218
  span.set_status(1, e.message)
@@ -83,6 +222,8 @@ module AgentBill
83
222
  end
84
223
  end
85
224
 
225
+ # Store reference to self for helper methods
226
+ @config[:_client] = self
86
227
  client
87
228
  end
88
229
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: agentbill-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - AgentBill
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-10-27 00:00:00.000000000 Z
11
+ date: 2025-10-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -41,7 +41,7 @@ dependencies:
41
41
  description: Automatically track and bill AI agent usage with zero-config instrumentation
42
42
  for OpenAI, Anthropic, and more
43
43
  email:
44
- - support@agentbill.com
44
+ - dominic@agentbill.io
45
45
  executables: []
46
46
  extensions: []
47
47
  extra_rdoc_files: []
@@ -66,6 +66,7 @@ files:
66
66
  - examples/anthropic_basic.rb
67
67
  - examples/manual_otel_tracking.rb
68
68
  - examples/openai_basic.rb
69
+ - examples/zero_config.rb
69
70
  - lib/agentbill.rb
70
71
  - lib/agentbill/tracer.rb
71
72
  - lib/agentbill/version.rb