agentbill-sdk 2.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.
data/lib/agentbill.rb CHANGED
@@ -1,16 +1,24 @@
1
1
  require 'net/http'
2
2
  require 'json'
3
3
  require 'securerandom'
4
+ require 'digest'
4
5
  require_relative 'agentbill/version'
5
6
  require_relative 'agentbill/tracer'
7
+ require_relative 'agentbill/pricing'
8
+ require_relative 'agentbill/customers'
9
+ require_relative 'agentbill/agents'
10
+ require_relative 'agentbill/orders'
6
11
 
7
12
  module AgentBill
8
13
  class Client
9
- attr_reader :config, :tracer
14
+ attr_reader :config, :tracer, :customers, :agents, :orders
10
15
 
11
16
  def initialize(config)
12
17
  @config = config
13
18
  @tracer = Tracer.new(config)
19
+ @customers = CustomersResource.new(config)
20
+ @agents = AgentsResource.new(config)
21
+ @orders = OrdersResource.new(config)
14
22
  end
15
23
 
16
24
  def self.init(config)
@@ -24,33 +32,23 @@ module AgentBill
24
32
  [1, text.to_s.length / 4].max
25
33
  end
26
34
 
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])
35
+ def estimate_cost(model, input_tokens, output_tokens, provider = 'openai')
36
+ Pricing.calculate_cost(model, input_tokens, output_tokens, provider)
42
37
  end
43
38
 
44
39
  def validate_request(model, messages, estimated_output_tokens = 1000)
45
- return { 'allowed' => true } unless @config[:daily_budget] || @config[:monthly_budget]
40
+ # Always validate when customer_id is present - backend will check DB policies
41
+ return { 'allowed' => true } unless @config[:customer_id]
46
42
 
47
- uri = URI("#{@config[:base_url] || 'https://bgwyprqxtdreuutzpbgw.supabase.co'}/functions/v1/ai-cost-guard-router")
43
+ uri = URI("#{@config[:base_url] || 'https://api.agentbill.io'}/functions/v1/ai-cost-guard-router")
48
44
 
49
45
  payload = {
50
46
  api_key: @config[:api_key],
51
47
  customer_id: @config[:customer_id],
52
48
  model: model,
53
- messages: messages
49
+ messages: messages,
50
+ daily_budget_override: @config[:daily_budget],
51
+ monthly_budget_override: @config[:monthly_budget]
54
52
  }
55
53
 
56
54
  begin
@@ -74,18 +72,42 @@ module AgentBill
74
72
  end
75
73
  end
76
74
 
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")
75
+ # NOTE: track_usage is deprecated in v7.0.2 - signals now go through OTEL collector
76
+ def track_usage(model, provider, input_tokens, output_tokens, latency_ms, cost, event_name = 'ai_request', trace_id = nil, span_id = nil)
77
+ # v7.0.2: Route signals through otel-collector with proper span format
78
+ uri = URI("#{@config[:base_url] || 'https://api.agentbill.io'}/functions/v1/otel-collector")
79
+
80
+ trace_id ||= SecureRandom.hex(16)
81
+ span_id ||= SecureRandom.hex(8)
82
+ now_ns = (Time.now.to_f * 1_000_000_000).to_i.to_s
79
83
 
80
84
  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
85
+ resourceSpans: [{
86
+ resource: {
87
+ attributes: [
88
+ { key: 'service.name', value: { stringValue: 'agentbill-ruby-sdk' } },
89
+ { key: 'agentbill.customer_id', value: { stringValue: @config[:customer_id] || '' } }
90
+ ]
91
+ },
92
+ scopeSpans: [{
93
+ scope: { name: 'agentbill.signals', version: '7.16.1' },
94
+ spans: [{
95
+ traceId: trace_id,
96
+ spanId: span_id,
97
+ name: 'agentbill.trace.signal',
98
+ kind: 1,
99
+ startTimeUnixNano: now_ns,
100
+ endTimeUnixNano: now_ns,
101
+ attributes: [
102
+ { key: 'agentbill.event_name', value: { stringValue: event_name } },
103
+ { key: 'agentbill.is_business_event', value: { boolValue: true } },
104
+ { key: 'gen_ai.request.model', value: { stringValue: model } },
105
+ { key: 'gen_ai.system', value: { stringValue: provider } }
106
+ ],
107
+ status: { code: 1 }
108
+ }]
109
+ }]
110
+ }]
89
111
  }
90
112
 
91
113
  begin
@@ -95,12 +117,13 @@ module AgentBill
95
117
 
96
118
  request = Net::HTTP::Post.new(uri.path)
97
119
  request['Content-Type'] = 'application/json'
120
+ request['x-api-key'] = @config[:api_key]
98
121
  request.body = payload.to_json
99
122
 
100
123
  http.request(request)
101
- puts "[AgentBill Cost Guard] Usage tracked: $#{format('%.4f', cost)}" if @config[:debug]
124
+ puts "[AgentBill] Usage tracked via OTEL: $#{format('%.4f', cost)}" if @config[:debug]
102
125
  rescue => e
103
- puts "[AgentBill Cost Guard] Tracking failed: #{e.message}" if @config[:debug]
126
+ puts "[AgentBill] Tracking failed: #{e.message}" if @config[:debug]
104
127
  end
105
128
  end
106
129
 
@@ -116,6 +139,10 @@ module AgentBill
116
139
  messages = params[:messages] || []
117
140
  max_tokens = params[:max_tokens] || params[:max_completion_tokens] || 1000
118
141
 
142
+ # Extract event_name from agentbill_options if provided (don't pass to OpenAI)
143
+ agentbill_options = params.delete(:agentbill_options) || {}
144
+ event_name = agentbill_options[:event_name] || 'ai_request'
145
+
119
146
  # Phase 1: Validate budget BEFORE API call
120
147
  validation = config[:_client].send(:validate_request, model, messages, max_tokens)
121
148
  unless validation['allowed']
@@ -126,9 +153,59 @@ module AgentBill
126
153
  raise error
127
154
  end
128
155
 
156
+ # v7.6.10: Check for cached response from semantic cache
157
+ # CRITICAL FIX: Router sends cost_saved/tokens_saved at top level
158
+ if validation['cached'] && validation['response_data']
159
+ puts "[AgentBill] ✓ Cache hit - returning cached response" if config[:debug]
160
+
161
+ # v7.6.10 FIX: Check both nested 'cache'/'cache_info' AND top-level fields
162
+ cache_info = validation['cache'] || validation['cache_info'] || {}
163
+ cached_response = validation['response_data']
164
+ usage = cached_response['usage'] || {}
165
+
166
+ # v7.6.10 FIX: Extract from cache_info OR top-level validation
167
+ tokens_saved = cache_info['tokens_saved'] || validation['tokens_saved'] || usage['total_tokens'] || 0
168
+ cost_saved = cache_info['cost_saved'] || validation['cost_saved'] || 0
169
+ cached_input_tokens = usage['prompt_tokens'] || (tokens_saved * 0.2).to_i
170
+ cached_output_tokens = usage['completion_tokens'] || (tokens_saved - cached_input_tokens)
171
+ cached_total_tokens = tokens_saved > 0 ? tokens_saved : (cached_input_tokens + cached_output_tokens)
172
+
173
+ # v7.6.10 CRITICAL FIX: Set gen_ai.usage.* to 0 for cache hits
174
+ # This prevents OTEL ingestion from calculating cost for cached responses
175
+ span = tracer.start_span('openai.chat.completion', {
176
+ 'gen_ai.system' => 'openai',
177
+ 'gen_ai.request.model' => model,
178
+ 'gen_ai.operation.name' => 'chat',
179
+ 'model' => model,
180
+ 'provider' => 'openai',
181
+ 'agentbill.from_cache' => true,
182
+ 'agentbill.cache_hit' => true,
183
+ # OTEL cost calculation attributes - set to 0 for cache hits
184
+ 'gen_ai.usage.input_tokens' => 0,
185
+ 'gen_ai.usage.output_tokens' => 0,
186
+ 'gen_ai.usage.total_tokens' => 0,
187
+ # Informational attributes - actual cached token counts
188
+ 'agentbill.cached_input_tokens' => cached_input_tokens,
189
+ 'agentbill.cached_output_tokens' => cached_output_tokens,
190
+ 'agentbill.cached_total_tokens' => cached_total_tokens,
191
+ 'agentbill.tokens_saved' => tokens_saved,
192
+ 'agentbill.cost_saved' => cost_saved
193
+ })
194
+ span.set_status(0)
195
+ span.finish
196
+ tracer.flush
197
+
198
+ return cached_response
199
+ end
200
+
129
201
  # Phase 2: Execute AI call
130
202
  start_time = Time.now
131
203
  span = tracer.start_span('openai.chat.completion', {
204
+ # ✅ OTEL GenAI compliant attributes
205
+ 'gen_ai.system' => 'openai',
206
+ 'gen_ai.request.model' => model,
207
+ 'gen_ai.operation.name' => 'chat',
208
+ # ⚠️ Backward compatibility
132
209
  'model' => model,
133
210
  'provider' => 'openai'
134
211
  })
@@ -137,14 +214,19 @@ module AgentBill
137
214
  response = original_method.call(params)
138
215
  latency = ((Time.now - start_time) * 1000).round
139
216
 
140
- # Phase 3: Track actual usage
217
+ # NOTE: wrap() only creates OTEL spans, NOT signals
218
+ # Signals should be created explicitly via track_signal()
141
219
  input_tokens = response.dig(:usage, :prompt_tokens) || 0
142
220
  output_tokens = response.dig(:usage, :completion_tokens) || 0
143
221
  cost = config[:_client].send(:estimate_cost, model, input_tokens, output_tokens)
144
222
 
145
- config[:_client].send(:track_usage, model, 'openai', input_tokens, output_tokens, latency, cost)
146
-
147
223
  span.set_attributes({
224
+ # ✅ OTEL GenAI compliant attributes
225
+ 'gen_ai.usage.input_tokens' => input_tokens,
226
+ 'gen_ai.usage.output_tokens' => output_tokens,
227
+ 'gen_ai.usage.total_tokens' => response.dig(:usage, :total_tokens),
228
+ 'gen_ai.response.id' => response.dig(:id),
229
+ # ⚠️ Backward compatibility
148
230
  'response.prompt_tokens' => input_tokens,
149
231
  'response.completion_tokens' => output_tokens,
150
232
  'response.total_tokens' => response.dig(:usage, :total_tokens),
@@ -152,16 +234,24 @@ module AgentBill
152
234
  })
153
235
  span.set_status(0)
154
236
 
155
- puts "[AgentBill Cost Guard] ✓ Protected call completed: $#{format('%.4f', cost)}" if config[:debug]
237
+ puts "[AgentBill] ✓ OTEL span created: $#{format('%.4f', cost)} (use track_signal() for signals)" if config[:debug]
238
+
239
+ # v7.5.0: Cache AI response for semantic caching
240
+ response_content = response.dig(:choices, 0, :message, :content) || ''
241
+ prompt_text = messages.map { |m| m[:content] }.join(' ')
242
+ prompt_hash = Digest::SHA256.hexdigest(prompt_text)[0..15]
243
+ config[:_client].send(:cache_response, model, prompt_hash, response_content, input_tokens + output_tokens, cost)
244
+
156
245
  response
157
246
  rescue => e
158
247
  span.set_status(1, e.message)
159
248
  raise
160
249
  ensure
161
250
  span.finish
251
+ # Auto-flush spans to prevent data loss
252
+ tracer.flush
162
253
  end
163
254
  end
164
-
165
255
  # Store reference to self for helper methods
166
256
  @config[:_client] = self
167
257
  client
@@ -190,6 +280,11 @@ module AgentBill
190
280
  # Phase 2: Execute AI call
191
281
  start_time = Time.now
192
282
  span = tracer.start_span('anthropic.message', {
283
+ # ✅ OTEL GenAI compliant attributes
284
+ 'gen_ai.system' => 'anthropic',
285
+ 'gen_ai.request.model' => model,
286
+ 'gen_ai.operation.name' => 'chat',
287
+ # ⚠️ Backward compatibility
193
288
  'model' => model,
194
289
  'provider' => 'anthropic'
195
290
  })
@@ -198,39 +293,45 @@ module AgentBill
198
293
  response = original_method.call(params)
199
294
  latency = ((Time.now - start_time) * 1000).round
200
295
 
201
- # Phase 3: Track actual usage
296
+ # NOTE: wrap() only creates OTEL spans, NOT signals
202
297
  input_tokens = response.dig(:usage, :input_tokens) || 0
203
298
  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)
299
+ cost = config[:_client].send(:estimate_cost, model, input_tokens, output_tokens, 'anthropic')
207
300
 
208
301
  span.set_attributes({
302
+ # ✅ OTEL GenAI compliant attributes
303
+ 'gen_ai.usage.input_tokens' => input_tokens,
304
+ 'gen_ai.usage.output_tokens' => output_tokens,
305
+ 'gen_ai.response.id' => response.dig(:id),
306
+ # ⚠️ Backward compatibility
209
307
  'response.input_tokens' => input_tokens,
210
308
  'response.output_tokens' => output_tokens,
211
309
  'latency_ms' => latency
212
310
  })
213
311
  span.set_status(0)
214
312
 
215
- puts "[AgentBill Cost Guard] ✓ Protected call completed: $#{format('%.4f', cost)}" if config[:debug]
313
+ puts "[AgentBill] ✓ OTEL span created: $#{format('%.4f', cost)} (use track_signal() for signals)" if config[:debug]
216
314
  response
217
315
  rescue => e
218
316
  span.set_status(1, e.message)
219
317
  raise
220
318
  ensure
221
319
  span.finish
320
+ # Auto-flush spans
321
+ tracer.flush
222
322
  end
223
323
  end
224
-
225
324
  # Store reference to self for helper methods
226
325
  @config[:_client] = self
227
326
  client
228
327
  end
229
328
 
230
- # Track a comprehensive signal with all 68 parameters
329
+ # Track a comprehensive signal with all 68 parameters including trace_id and span_id
231
330
  #
232
- # Supports all parameters:
331
+ # Supports optional trace_id and span_id for OTEL correlation:
233
332
  # - event_name (required)
333
+ # - trace_id (optional) - For correlating with OTEL spans for cost reconciliation
334
+ # - span_id (optional) - For correlating with OTEL spans for cost reconciliation
234
335
  # - data_source, timestamp
235
336
  # - agent_external_id, customer_external_id, account_external_id, user_external_id,
236
337
  # order_external_id, session_id, conversation_id, thread_id
@@ -243,26 +344,46 @@ module AgentBill
243
344
  # - feedback_score, user_satisfaction, error_type, error_message, retry_count, success_rate
244
345
  # - tags, category, priority, severity, compliance_flag, data_classification
245
346
  # - product_id, feature_flag, environment, deployment_version, region, tenant_id
246
- # - parent_span_id, trace_id
347
+ # - parent_span_id
247
348
  # - custom_dimensions, metadata, data
248
349
  #
249
350
  # Example:
351
+ # # Basic tracking
250
352
  # agentbill.track_signal(
251
353
  # event_name: "user_conversion",
252
354
  # revenue: 99.99,
253
- # customer_external_id: "cust_123",
254
- # experiment_id: "exp_abc",
255
- # conversion_type: "purchase",
256
- # tags: ["checkout", "success"]
355
+ # customer_external_id: "cust_123"
356
+ # )
357
+ #
358
+ # # With OTEL correlation
359
+ # span = agentbill.tracer.start_span('ai_completion')
360
+ # # ... make AI call ...
361
+ # agentbill.track_signal(
362
+ # event_name: "ai_request",
363
+ # revenue: 5.00,
364
+ # trace_id: span.trace_id, # Optional
365
+ # span_id: span.span_id # Optional
257
366
  # )
258
367
  def track_signal(**params)
259
368
  raise ArgumentError, "event_name is required" unless params[:event_name]
260
369
 
261
- uri = URI("#{@config[:base_url] || 'https://bgwyprqxtdreuutzpbgw.supabase.co'}/functions/v1/record-signals")
370
+ uri = URI("#{@config[:base_url] || 'https://api.agentbill.io'}/functions/v1/record-signals")
262
371
 
263
372
  # Add timestamp if not provided
264
373
  params[:timestamp] ||= Time.now.to_f
265
374
 
375
+ # Auto-fill customer_id or customer_external_id from config if not provided
376
+ if !params[:customer_id] && !params[:customer_external_id] && @config[:customer_id]
377
+ # Check if it's a UUID format - send as customer_id, else customer_external_id
378
+ uuid_regex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
379
+ is_uuid = @config[:customer_id].match?(uuid_regex)
380
+ if is_uuid
381
+ params[:customer_id] = @config[:customer_id]
382
+ else
383
+ params[:customer_external_id] = @config[:customer_id]
384
+ end
385
+ end
386
+
266
387
  # Remove nil values
267
388
  payload = params.reject { |_, v| v.nil? }
268
389
 
@@ -271,14 +392,15 @@ module AgentBill
271
392
  http.use_ssl = true
272
393
 
273
394
  request = Net::HTTP::Post.new(uri.path)
274
- request['Authorization'] = "Bearer #{@config[:api_key]}"
395
+ request['X-API-Key'] = @config[:api_key]
275
396
  request['Content-Type'] = 'application/json'
276
397
  request.body = payload.to_json
277
398
 
278
399
  response = http.request(request)
279
400
 
280
401
  if @config[:debug]
281
- puts "[AgentBill] Signal tracked: #{params[:event_name]}"
402
+ trace_info = params[:trace_id] ? " (trace: #{params[:trace_id]})" : ""
403
+ puts "[AgentBill] Signal tracked: #{params[:event_name]}#{trace_info}"
282
404
  end
283
405
 
284
406
  response.code == '200'
@@ -290,8 +412,122 @@ module AgentBill
290
412
  end
291
413
  end
292
414
 
415
+ # Track a conversion event for prompt profitability analysis
416
+ # Links conversions to AI prompts to calculate ROI per prompt
417
+ #
418
+ # @param event_type [String] Type of conversion (e.g., 'purchase', 'signup', 'trial_start')
419
+ # @param event_value [Float] Revenue amount from the conversion
420
+ # @param signal_id [String, nil] Optional UUID linking to specific AI signal/prompt
421
+ # @param session_id [String, nil] Optional session identifier
422
+ # @param attribution_window_hours [Integer] Time window for attribution (default: 24 hours)
423
+ # @param currency [String] Currency code (default: 'USD')
424
+ # @param metadata [Hash, nil] Optional additional data
425
+ # @return [Hash] Response with success status and conversion_id
426
+ #
427
+ # Example:
428
+ # # Track a purchase conversion
429
+ # result = agentbill.track_conversion(
430
+ # event_type: 'purchase',
431
+ # event_value: 99.99,
432
+ # currency: 'USD'
433
+ # )
434
+ #
435
+ # # Link conversion to specific AI prompt
436
+ # result = agentbill.track_conversion(
437
+ # event_type: 'trial_signup',
438
+ # event_value: 29.99,
439
+ # signal_id: 'signal-uuid-from-prompt',
440
+ # session_id: 'session-123'
441
+ # )
442
+ def track_conversion(event_type:, event_value:, signal_id: nil, session_id: nil, attribution_window_hours: 24, currency: 'USD', metadata: nil)
443
+ uri = URI("#{@config[:base_url] || 'https://api.agentbill.io'}/functions/v1/track-conversion")
444
+
445
+ payload = {
446
+ api_key: @config[:api_key],
447
+ customer_id: @config[:customer_id],
448
+ event_type: event_type,
449
+ event_value: event_value,
450
+ signal_id: signal_id,
451
+ session_id: session_id,
452
+ attribution_window_hours: attribution_window_hours,
453
+ currency: currency,
454
+ metadata: metadata
455
+ }
456
+
457
+ begin
458
+ http = Net::HTTP.new(uri.host, uri.port)
459
+ http.use_ssl = true
460
+
461
+ request = Net::HTTP::Post.new(uri.path)
462
+ request['Content-Type'] = 'application/json'
463
+ request.body = payload.to_json
464
+
465
+ response = http.request(request)
466
+ data = JSON.parse(response.body)
467
+
468
+ if response.code != '200'
469
+ return {
470
+ success: false,
471
+ error: data['error'] || "HTTP #{response.code}"
472
+ }
473
+ end
474
+
475
+ if @config[:debug]
476
+ puts "[AgentBill] Conversion tracked: #{event_type} = $#{event_value} (ID: #{data['conversion_id']})"
477
+ end
478
+
479
+ {
480
+ success: true,
481
+ conversion_id: data['conversion_id']
482
+ }
483
+ rescue => e
484
+ if @config[:debug]
485
+ puts "[AgentBill] Failed to track conversion: #{e.message}"
486
+ end
487
+ {
488
+ success: false,
489
+ error: e.message
490
+ }
491
+ end
492
+ end
493
+
293
494
  def flush
294
495
  @tracer.flush
295
496
  end
497
+
498
+ private
499
+
500
+ # Cache AI response for semantic caching (v7.5.0)
501
+ def cache_response(model, prompt_hash, response_content, tokens_used = 0, cost = 0.0)
502
+ return if response_content.nil? || response_content.empty?
503
+
504
+ uri = URI("#{@config[:base_url] || 'https://api.agentbill.io'}/functions/v1/cache-ai-response")
505
+
506
+ payload = {
507
+ api_key: @config[:api_key],
508
+ prompt_hash: prompt_hash,
509
+ response_content: response_content,
510
+ model: model,
511
+ tokens_used: tokens_used,
512
+ cost: cost,
513
+ customer_id: @config[:customer_id],
514
+ agent_id: @config[:agent_id]
515
+ }
516
+
517
+ begin
518
+ http = Net::HTTP.new(uri.host, uri.port)
519
+ http.use_ssl = true
520
+ http.read_timeout = 5
521
+
522
+ request = Net::HTTP::Post.new(uri.path)
523
+ request['Content-Type'] = 'application/json'
524
+ request.body = payload.to_json
525
+
526
+ http.request(request)
527
+ puts "[AgentBill] Response cached for semantic caching" if @config[:debug]
528
+ rescue => e
529
+ puts "[AgentBill] Cache response failed: #{e.message}" if @config[:debug]
530
+ end
531
+ end
296
532
  end
297
533
  end
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: 2.0.1
4
+ version: 7.17.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-31 00:00:00.000000000 Z
11
+ date: 2026-01-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -65,11 +65,26 @@ files:
65
65
  - agentbill.gemspec
66
66
  - examples/anthropic_basic.rb
67
67
  - examples/manual_otel_tracking.rb
68
+ - examples/ollama_basic.rb
68
69
  - examples/openai_basic.rb
70
+ - examples/openai_custom_event.rb
71
+ - examples/perplexity_basic.rb
69
72
  - examples/zero_config.rb
70
73
  - lib/agentbill.rb
74
+ - lib/agentbill/agents.rb
75
+ - lib/agentbill/customers.rb
76
+ - lib/agentbill/distributed.rb
77
+ - lib/agentbill/exceptions.rb
78
+ - lib/agentbill/ollama_wrapper.rb
79
+ - lib/agentbill/orders.rb
80
+ - lib/agentbill/perplexity_wrapper.rb
81
+ - lib/agentbill/pricing.rb
82
+ - lib/agentbill/signal_types.rb
83
+ - lib/agentbill/signals.rb
71
84
  - lib/agentbill/tracer.rb
85
+ - lib/agentbill/tracing.rb
72
86
  - lib/agentbill/version.rb
87
+ - lib/agentbill/wrappers.rb
73
88
  homepage: https://github.com/Agent-Bill/Ruby
74
89
  licenses:
75
90
  - MIT