paid_ruby 0.1.1 → 0.1.2

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: e08b12d8087ea612ca1b405c5d6cfa6fab4374f4f89ad81df1a7042b31206c72
4
- data.tar.gz: 7dd1f1ef4c71fe4dd19d3c34e6eacd051a95087b74ec5be2f610efe9608e2a55
3
+ metadata.gz: 1b2054795e06be38ede1daa5465842367cfef452cd91ecc6b1122a66a8942d99
4
+ data.tar.gz: ba513d072353748c62740300308ce64102e215441123d037891dae820f000231
5
5
  SHA512:
6
- metadata.gz: 6eed37d4f15ac6939c644a4c7db14fbcaeb3d5e9e67d16b983d3060790f0daeb6054b1e5beb0241470bdaeca393bdc119503dc2ffd893272e9d522c62031059c
7
- data.tar.gz: 931419aa3d4767c1df1f7c0ca332db5e629b73f7d38b3f8cdc61463626c726476ac89f327434ecd08bebdc631c8dbcc726484b925a6d4f3a821695b5220a4684
6
+ metadata.gz: '0792683c410f927576c39efef7549ce4dc88b2730f681142f4556620af55bd283e391ce501adc85ba453555cafb7a1bbabf11c1e2c45613b74adbff4700d0828'
7
+ data.tar.gz: 6f6f145c2f60fa277a67bf8ff983de8f0ae5877fd2dcf3519afb8756805e5323d8ee373ddcbdcfaafc511121622b7f978ca5a281c3e13797de5196f71dd89a48
data/lib/gemconfig.rb CHANGED
@@ -9,5 +9,6 @@ module Paid
9
9
  HOMEPAGE = "https://github.com/paid-ai/paid-ruby"
10
10
  SOURCE_CODE_URI = "https://github.com/paid-ai/paid-ruby"
11
11
  CHANGELOG_URI = "https://github.com/paid-ai/paid-ruby/blob/master/CHANGELOG.md"
12
+ RUBY_VERSION = ">= 3.4.4"
12
13
  end
13
14
  end
@@ -60,6 +60,7 @@ end
60
60
  # @param description [String]
61
61
  # @param agent_code [String]
62
62
  # @param external_id [String]
63
+ # @param active [Boolean]
63
64
  # @param request_options [Paid::RequestOptions]
64
65
  # @return [Paid::Agent]
65
66
  # @example
@@ -68,8 +69,12 @@ end
68
69
  # environment: Paid::Environment::PRODUCTION,
69
70
  # token: "YOUR_AUTH_TOKEN"
70
71
  # )
71
- # api.agents.create(name: "name", description: "description")
72
- def create(name:, description:, agent_code: nil, external_id: nil, request_options: nil)
72
+ # api.agents.create(
73
+ # name: "Acme Agent",
74
+ # description: "Acme Agent is an AI agent that does things.",
75
+ # external_id: "acme-agent"
76
+ # )
77
+ def create(name:, description:, agent_code: nil, external_id: nil, active: nil, request_options: nil)
73
78
  response = @request_client.conn.post do | req |
74
79
  unless request_options&.timeout_in_seconds.nil?
75
80
  req.options.timeout = request_options.timeout_in_seconds
@@ -81,7 +86,7 @@ end
81
86
  unless request_options.nil? || request_options&.additional_query_parameters.nil?
82
87
  req.params = { **(request_options&.additional_query_parameters || {}) }.compact
83
88
  end
84
- req.body = { **(request_options&.additional_body_parameters || {}), name: name, description: description, agentCode: agent_code, externalId: external_id }.compact
89
+ req.body = { **(request_options&.additional_body_parameters || {}), name: name, description: description, agentCode: agent_code, externalId: external_id, active: active }.compact
85
90
  req.url "#{@request_client.get_url(request_options: request_options)}/agents"
86
91
  end
87
92
  Paid::Agent.from_json(json_object: response.body)
@@ -131,7 +136,7 @@ end
131
136
  # environment: Paid::Environment::PRODUCTION,
132
137
  # token: "YOUR_AUTH_TOKEN"
133
138
  # )
134
- # api.agents.update(agent_id: "agentId", request: { })
139
+ # api.agents.update(agent_id: "agentId", request: { name: "Acme Agent (Updated)", agent_attributes: [{ name: "Emails sent signal", active: true, pricing: { event_name: "emails_sent", taxable: true, charge_type: USAGE, pricing_model: PER_UNIT, billing_frequency: MONTHLY, price_points: { "USD": { tiers: [{ min_quantity: 0, max_quantity: 10, unit_price: 100 }, { min_quantity: 11, max_quantity: 100, unit_price: 90 }, { min_quantity: 101, unit_price: 80 }] } } } }] })
135
140
  def update(agent_id:, request:, request_options: nil)
136
141
  response = @request_client.conn.put do | req |
137
142
  unless request_options&.timeout_in_seconds.nil?
@@ -222,7 +227,7 @@ end
222
227
  # environment: Paid::Environment::PRODUCTION,
223
228
  # token: "YOUR_AUTH_TOKEN"
224
229
  # )
225
- # api.agents.update_by_external_id(external_id: "externalId", request: { })
230
+ # api.agents.update_by_external_id(external_id: "externalId", request: { name: "Acme Agent (Updated)", agent_attributes: [{ name: "Emails sent signal", active: true, pricing: { event_name: "emails_sent", taxable: true, charge_type: USAGE, pricing_model: PER_UNIT, billing_frequency: MONTHLY, price_points: { "USD": { unit_price: 150 } } } }] })
226
231
  def update_by_external_id(external_id:, request:, request_options: nil)
227
232
  response = @request_client.conn.put do | req |
228
233
  unless request_options&.timeout_in_seconds.nil?
@@ -317,6 +322,7 @@ end
317
322
  # @param description [String]
318
323
  # @param agent_code [String]
319
324
  # @param external_id [String]
325
+ # @param active [Boolean]
320
326
  # @param request_options [Paid::RequestOptions]
321
327
  # @return [Paid::Agent]
322
328
  # @example
@@ -325,8 +331,12 @@ end
325
331
  # environment: Paid::Environment::PRODUCTION,
326
332
  # token: "YOUR_AUTH_TOKEN"
327
333
  # )
328
- # api.agents.create(name: "name", description: "description")
329
- def create(name:, description:, agent_code: nil, external_id: nil, request_options: nil)
334
+ # api.agents.create(
335
+ # name: "Acme Agent",
336
+ # description: "Acme Agent is an AI agent that does things.",
337
+ # external_id: "acme-agent"
338
+ # )
339
+ def create(name:, description:, agent_code: nil, external_id: nil, active: nil, request_options: nil)
330
340
  Async do
331
341
  response = @request_client.conn.post do | req |
332
342
  unless request_options&.timeout_in_seconds.nil?
@@ -339,7 +349,7 @@ end
339
349
  unless request_options.nil? || request_options&.additional_query_parameters.nil?
340
350
  req.params = { **(request_options&.additional_query_parameters || {}) }.compact
341
351
  end
342
- req.body = { **(request_options&.additional_body_parameters || {}), name: name, description: description, agentCode: agent_code, externalId: external_id }.compact
352
+ req.body = { **(request_options&.additional_body_parameters || {}), name: name, description: description, agentCode: agent_code, externalId: external_id, active: active }.compact
343
353
  req.url "#{@request_client.get_url(request_options: request_options)}/agents"
344
354
  end
345
355
  Paid::Agent.from_json(json_object: response.body)
@@ -392,7 +402,7 @@ end
392
402
  # environment: Paid::Environment::PRODUCTION,
393
403
  # token: "YOUR_AUTH_TOKEN"
394
404
  # )
395
- # api.agents.update(agent_id: "agentId", request: { })
405
+ # api.agents.update(agent_id: "agentId", request: { name: "Acme Agent (Updated)", agent_attributes: [{ name: "Emails sent signal", active: true, pricing: { event_name: "emails_sent", taxable: true, charge_type: USAGE, pricing_model: PER_UNIT, billing_frequency: MONTHLY, price_points: { "USD": { tiers: [{ min_quantity: 0, max_quantity: 10, unit_price: 100 }, { min_quantity: 11, max_quantity: 100, unit_price: 90 }, { min_quantity: 101, unit_price: 80 }] } } } }] })
396
406
  def update(agent_id:, request:, request_options: nil)
397
407
  Async do
398
408
  response = @request_client.conn.put do | req |
@@ -489,7 +499,7 @@ end
489
499
  # environment: Paid::Environment::PRODUCTION,
490
500
  # token: "YOUR_AUTH_TOKEN"
491
501
  # )
492
- # api.agents.update_by_external_id(external_id: "externalId", request: { })
502
+ # api.agents.update_by_external_id(external_id: "externalId", request: { name: "Acme Agent (Updated)", agent_attributes: [{ name: "Emails sent signal", active: true, pricing: { event_name: "emails_sent", taxable: true, charge_type: USAGE, pricing_model: PER_UNIT, billing_frequency: MONTHLY, price_points: { "USD": { unit_price: 150 } } } }] })
493
503
  def update_by_external_id(external_id:, request:, request_options: nil)
494
504
  Async do
495
505
  response = @request_client.conn.put do | req |
@@ -76,16 +76,13 @@ end
76
76
  # token: "YOUR_AUTH_TOKEN"
77
77
  # )
78
78
  # api.contacts.create(
79
+ # customer_external_id: "acme-inc",
79
80
  # salutation: MR,
80
- # first_name: "firstName",
81
- # last_name: "lastName",
82
- # email: "email",
83
- # billing_street: "billingStreet",
84
- # billing_city: "billingCity",
85
- # billing_country: "billingCountry",
86
- # billing_postal_code: "billingPostalCode"
81
+ # first_name: "John",
82
+ # last_name: "Doe",
83
+ # email: "john.doe@example.com"
87
84
  # )
88
- def create(external_id: nil, customer_id: nil, customer_external_id: nil, salutation:, first_name:, last_name:, email:, phone: nil, billing_street:, billing_city:, billing_state_province: nil, billing_country:, billing_postal_code:, request_options: nil)
85
+ def create(external_id: nil, customer_id: nil, customer_external_id: nil, salutation:, first_name:, last_name:, email:, phone: nil, billing_street: nil, billing_city: nil, billing_state_province: nil, billing_country: nil, billing_postal_code: nil, request_options: nil)
89
86
  response = @request_client.conn.post do | req |
90
87
  unless request_options&.timeout_in_seconds.nil?
91
88
  req.options.timeout = request_options.timeout_in_seconds
@@ -283,16 +280,13 @@ end
283
280
  # token: "YOUR_AUTH_TOKEN"
284
281
  # )
285
282
  # api.contacts.create(
283
+ # customer_external_id: "acme-inc",
286
284
  # salutation: MR,
287
- # first_name: "firstName",
288
- # last_name: "lastName",
289
- # email: "email",
290
- # billing_street: "billingStreet",
291
- # billing_city: "billingCity",
292
- # billing_country: "billingCountry",
293
- # billing_postal_code: "billingPostalCode"
285
+ # first_name: "John",
286
+ # last_name: "Doe",
287
+ # email: "john.doe@example.com"
294
288
  # )
295
- def create(external_id: nil, customer_id: nil, customer_external_id: nil, salutation:, first_name:, last_name:, email:, phone: nil, billing_street:, billing_city:, billing_state_province: nil, billing_country:, billing_postal_code:, request_options: nil)
289
+ def create(external_id: nil, customer_id: nil, customer_external_id: nil, salutation:, first_name:, last_name:, email:, phone: nil, billing_street: nil, billing_city: nil, billing_state_province: nil, billing_country: nil, billing_postal_code: nil, request_options: nil)
296
290
  Async do
297
291
  response = @request_client.conn.post do | req |
298
292
  unless request_options&.timeout_in_seconds.nil?
@@ -82,7 +82,7 @@ end
82
82
  # environment: Paid::Environment::PRODUCTION,
83
83
  # token: "YOUR_AUTH_TOKEN"
84
84
  # )
85
- # api.customers.create(name: "name")
85
+ # api.customers.create(name: "Acme, Inc.", external_id: "acme-inc")
86
86
  def create(name:, external_id: nil, phone: nil, employee_count: nil, annual_revenue: nil, tax_exempt_status: nil, creation_source: nil, website: nil, billing_address: nil, request_options: nil)
87
87
  response = @request_client.conn.post do | req |
88
88
  unless request_options&.timeout_in_seconds.nil?
@@ -154,7 +154,7 @@ end
154
154
  # environment: Paid::Environment::PRODUCTION,
155
155
  # token: "YOUR_AUTH_TOKEN"
156
156
  # )
157
- # api.customers.update(customer_id: "customerId", request: { })
157
+ # api.customers.update(customer_id: "customerId", request: { name: "Acme, Inc. (Updated)", phone: "123-456-7890", employee_count: 101, annual_revenue: 1000001 })
158
158
  def update(customer_id:, request:, request_options: nil)
159
159
  response = @request_client.conn.put do | req |
160
160
  unless request_options&.timeout_in_seconds.nil?
@@ -368,7 +368,7 @@ end
368
368
  # environment: Paid::Environment::PRODUCTION,
369
369
  # token: "YOUR_AUTH_TOKEN"
370
370
  # )
371
- # api.customers.create(name: "name")
371
+ # api.customers.create(name: "Acme, Inc.", external_id: "acme-inc")
372
372
  def create(name:, external_id: nil, phone: nil, employee_count: nil, annual_revenue: nil, tax_exempt_status: nil, creation_source: nil, website: nil, billing_address: nil, request_options: nil)
373
373
  Async do
374
374
  response = @request_client.conn.post do | req |
@@ -444,7 +444,7 @@ end
444
444
  # environment: Paid::Environment::PRODUCTION,
445
445
  # token: "YOUR_AUTH_TOKEN"
446
446
  # )
447
- # api.customers.update(customer_id: "customerId", request: { })
447
+ # api.customers.update(customer_id: "customerId", request: { name: "Acme, Inc. (Updated)", phone: "123-456-7890", employee_count: 101, annual_revenue: 1000001 })
448
448
  def update(customer_id:, request:, request_options: nil)
449
449
  Async do
450
450
  response = @request_client.conn.put do | req |
@@ -79,13 +79,14 @@ end
79
79
  # token: "YOUR_AUTH_TOKEN"
80
80
  # )
81
81
  # api.orders.create(
82
- # customer_id: "customerId",
83
- # billing_contact_id: "billingContactId",
84
- # name: "name",
85
- # start_date: "startDate",
86
- # currency: "currency"
82
+ # customer_external_id: "acme-inc",
83
+ # name: "Acme Order",
84
+ # description: "Acme Order is an order for Acme, Inc.",
85
+ # start_date: "2025-01-01",
86
+ # end_date: "2026-01-01",
87
+ # currency: "USD"
87
88
  # )
88
- def create(customer_id:, customer_external_id: nil, billing_contact_id:, name:, description: nil, start_date:, end_date: nil, currency:, order_lines: nil, request_options: nil)
89
+ def create(customer_id: nil, customer_external_id: nil, billing_contact_id: nil, name:, description: nil, start_date:, end_date: nil, currency:, order_lines: nil, request_options: nil)
89
90
  response = @request_client.conn.post do | req |
90
91
  unless request_options&.timeout_in_seconds.nil?
91
92
  req.options.timeout = request_options.timeout_in_seconds
@@ -258,13 +259,14 @@ end
258
259
  # token: "YOUR_AUTH_TOKEN"
259
260
  # )
260
261
  # api.orders.create(
261
- # customer_id: "customerId",
262
- # billing_contact_id: "billingContactId",
263
- # name: "name",
264
- # start_date: "startDate",
265
- # currency: "currency"
262
+ # customer_external_id: "acme-inc",
263
+ # name: "Acme Order",
264
+ # description: "Acme Order is an order for Acme, Inc.",
265
+ # start_date: "2025-01-01",
266
+ # end_date: "2026-01-01",
267
+ # currency: "USD"
266
268
  # )
267
- def create(customer_id:, customer_external_id: nil, billing_contact_id:, name:, description: nil, start_date:, end_date: nil, currency:, order_lines: nil, request_options: nil)
269
+ def create(customer_id: nil, customer_external_id: nil, billing_contact_id: nil, name:, description: nil, start_date:, end_date: nil, currency:, order_lines: nil, request_options: nil)
268
270
  Async do
269
271
  response = @request_client.conn.post do | req |
270
272
  unless request_options&.timeout_in_seconds.nil?
@@ -31,7 +31,7 @@ module Paid
31
31
  # environment: Paid::Environment::PRODUCTION,
32
32
  # token: "YOUR_AUTH_TOKEN"
33
33
  # )
34
- # api.orders.lines.update(order_id: "orderId")
34
+ # api.orders.lines.update(order_id: "orderId", lines: [{ agent_external_id: "acme-agent", name: "Order Line One", description: "Order Line One is an order line for Acme, Inc." }, { agent_external_id: "acme-agent-2", name: "Order Line Two", description: "Order Line Two is an order line for Acme, Inc." }])
35
35
  def update(order_id:, lines: nil, request_options: nil)
36
36
  response = @request_client.conn.put do | req |
37
37
  unless request_options&.timeout_in_seconds.nil?
@@ -74,7 +74,7 @@ end
74
74
  # environment: Paid::Environment::PRODUCTION,
75
75
  # token: "YOUR_AUTH_TOKEN"
76
76
  # )
77
- # api.orders.lines.update(order_id: "orderId")
77
+ # api.orders.lines.update(order_id: "orderId", lines: [{ agent_external_id: "acme-agent", name: "Order Line One", description: "Order Line One is an order line for Acme, Inc." }, { agent_external_id: "acme-agent-2", name: "Order Line Two", description: "Order Line Two is an order line for Acme, Inc." }])
78
78
  def update(order_id:, lines: nil, request_options: nil)
79
79
  Async do
80
80
  response = @request_client.conn.put do | req |
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "logger"
4
+
5
+ module Paid
6
+ module Tracing
7
+ # Provides a central logger for the gem.
8
+ # The log level can be configured via the PAID_LOG_LEVEL environment variable.
9
+ # Supported levels are DEBUG, INFO, WARN, ERROR, FATAL.
10
+ # If the variable is not set, the level defaults to FATAL to suppress output.
11
+ module Logging
12
+ def self.logger
13
+ @logger ||= begin
14
+ log_level_str = ENV["PAID_LOG_LEVEL"]&.upcase
15
+ level = if log_level_str && Logger.const_defined?(log_level_str)
16
+ Logger.const_get(log_level_str)
17
+ else
18
+ # Default to a level that shows no logs unless explicitly configured.
19
+ Logger::FATAL
20
+ end
21
+
22
+ logger = Logger.new($stdout)
23
+ logger.level = level
24
+ logger.formatter = proc do |severity, _datetime, _progname, msg|
25
+ "[Paid SDK] #{severity}: #{msg}\n"
26
+ end
27
+ logger
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "opentelemetry/sdk"
4
+ require "opentelemetry/exporter/otlp"
5
+ require_relative "logging"
6
+
7
+ module Paid
8
+ module Tracing
9
+ @token = nil
10
+
11
+ # Context keys to propagate external_customer_id and token to child spans.
12
+ # These are just keys, not the values themselves.
13
+ PAID_EXTERNAL_CUSTOMER_ID_KEY = OpenTelemetry::Context.create_key("paid.external_customer_id")
14
+ PAID_TOKEN_KEY = OpenTelemetry::Context.create_key("paid.token")
15
+
16
+ # @param api_key [String]
17
+ def self.initialize_tracing(api_key:)
18
+ endpoint = "https://collector.agentpaid.io:4318/v1/traces"
19
+ # endpoint = "http://localhost:4318/v1/traces"
20
+
21
+ @token = api_key
22
+
23
+ exporter = OpenTelemetry::Exporter::OTLP::Exporter.new(
24
+ endpoint: endpoint,
25
+ headers: {}
26
+ )
27
+ span_processor = OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(exporter)
28
+
29
+ OpenTelemetry::SDK.configure do |c|
30
+ c.add_span_processor(span_processor)
31
+ end
32
+
33
+ # Add an at_exit hook to ensure spans are flushed before the script exits.
34
+ at_exit do
35
+ OpenTelemetry.tracer_provider.shutdown
36
+ end
37
+
38
+ Logging.logger.info("Paid tracing initialized successfully")
39
+ rescue StandardError => e
40
+ Logging.logger.error("Failed to initialize Paid tracing: #{e.message}")
41
+ raise
42
+ end
43
+
44
+ def self.token
45
+ @token
46
+ end
47
+
48
+ # Getter for the OpenAI wrapper to retrieve the external_customer_id from the context.
49
+ def self.get_external_customer_id_from_context
50
+ OpenTelemetry::Context.current.value(PAID_EXTERNAL_CUSTOMER_ID_KEY)
51
+ end
52
+
53
+ # Getter for the OpenAI wrapper to retrieve the token from the context.
54
+ def self.get_token_from_context
55
+ OpenTelemetry::Context.current.value(PAID_TOKEN_KEY)
56
+ end
57
+
58
+ # @param external_customer_id [String]
59
+ # @param args [Array]
60
+ # @param block [Proc]
61
+ def self.capture(*args, external_customer_id:, &block)
62
+ token = self.token
63
+ unless token
64
+ Logging.logger.warn("No token found - tracing is not initialized and will not be captured")
65
+ return yield(*args) if block_given?
66
+ end
67
+
68
+ new_context_values = {
69
+ PAID_EXTERNAL_CUSTOMER_ID_KEY => external_customer_id,
70
+ PAID_TOKEN_KEY => token
71
+ }
72
+
73
+ # Execute the block within a new context containing our values.
74
+ OpenTelemetry::Context.with_values(new_context_values) do
75
+ tracer = OpenTelemetry.tracer_provider.tracer("paid.ruby")
76
+
77
+ tracer.in_span("paid.ruby:#{external_customer_id}") do |span|
78
+ span.set_attribute("external_customer_id", external_customer_id)
79
+ span.set_attribute("token", token)
80
+
81
+ begin
82
+ result = yield(*args) if block_given?
83
+ span.status = OpenTelemetry::Trace::Status.ok("Success")
84
+ result
85
+ rescue StandardError => e
86
+ span.status = OpenTelemetry::Trace::Status.error("Error: #{e.message}")
87
+ raise e
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openai"
4
+ require_relative "../tracing"
5
+
6
+ module Paid
7
+ module Tracing
8
+ module Wrappers
9
+ # A wrapper around the OpenAI::Client that provides automatic tracing for API calls.
10
+ class PaidOpenAI
11
+ def initialize(openai_client:)
12
+ @openai_client = openai_client
13
+ @tracer = OpenTelemetry.tracer_provider.tracer("paid.ruby")
14
+ end
15
+
16
+ # Wraps the OpenAI#chat method to create a child span.
17
+ def chat(parameters:)
18
+ wrap_call(operation: "chat", model: parameters[:model]) do
19
+ @openai_client.chat(parameters: parameters)
20
+ end
21
+ end
22
+
23
+ # Wraps the OpenAI#embeddings method to create a child span.
24
+ def embeddings(parameters:)
25
+ wrap_call(operation: "embeddings", model: parameters[:model]) do
26
+ @openai_client.embeddings(parameters: parameters)
27
+ end
28
+ end
29
+
30
+ # Returns a wrapper for the images API.
31
+ def images
32
+ ImagesWrapper.new(openai_client: @openai_client, tracer: @tracer)
33
+ end
34
+
35
+ private
36
+
37
+ # A private wrapper for the OpenAI Images API.
38
+ class ImagesWrapper
39
+ def initialize(openai_client:, tracer:)
40
+ @openai_client = openai_client
41
+ @tracer = tracer
42
+ end
43
+
44
+ def generate(parameters:)
45
+ current_span = OpenTelemetry::Trace.current_span
46
+ unless current_span.context.valid?
47
+ Paid::Tracing::Logging.logger.warn("No active span found, calling OpenAI directly without tracing.")
48
+ return @openai_client.images.generate(parameters: parameters)
49
+ end
50
+
51
+ external_customer_id = Paid::Tracing.get_external_customer_id_from_context
52
+ token = Paid::Tracing.get_token_from_context
53
+ model = parameters[:model] || "dall-e-3"
54
+ span_name = "trace.images #{model}"
55
+
56
+ @tracer.in_span(span_name) do |span|
57
+ attributes = {
58
+ "gen_ai.request.model" => model,
59
+ "gen_ai.system" => "openai",
60
+ "gen_ai.operation.name" => "image_generation"
61
+ }
62
+ attributes["external_customer_id"] = external_customer_id if external_customer_id
63
+ attributes["token"] = token if token
64
+ span.add_attributes(attributes)
65
+
66
+ begin
67
+ response = @openai_client.images.generate(parameters: parameters)
68
+ span.add_attributes({
69
+ "gen_ai.image.count" => parameters[:n] || 1,
70
+ "gen_ai.image.size" => parameters[:size] || "1024x1024",
71
+ "gen_ai.image.quality" => parameters[:quality] || "standard"
72
+ })
73
+ span.status = OpenTelemetry::Trace::Status.ok("Success")
74
+ response
75
+ rescue StandardError => e
76
+ span.record_exception(e)
77
+ span.status = OpenTelemetry::Trace::Status.error("Error: #{e.message}")
78
+ raise e
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ def wrap_call(operation:, model:, &block)
85
+ current_span = OpenTelemetry::Trace.current_span
86
+ unless current_span.context.valid?
87
+ Paid::Tracing::Logging.logger.warn("No active span found, calling OpenAI directly without tracing.")
88
+ return yield
89
+ end
90
+
91
+ external_customer_id = Paid::Tracing.get_external_customer_id_from_context
92
+ token = Paid::Tracing.get_token_from_context
93
+ model_name = model || "unknown"
94
+ span_name = "trace.#{operation} #{model_name}"
95
+
96
+ @tracer.in_span(span_name) do |span|
97
+ attributes = {
98
+ "gen_ai.system" => "openai",
99
+ "gen_ai.operation.name" => operation
100
+ }
101
+ attributes["external_customer_id"] = external_customer_id if external_customer_id
102
+ attributes["token"] = token if token
103
+ span.add_attributes(attributes)
104
+
105
+ begin
106
+ response = yield
107
+ add_response_attributes(span, response)
108
+ span.status = OpenTelemetry::Trace::Status.ok("Success")
109
+ response
110
+ rescue StandardError => e
111
+ span.record_exception(e)
112
+ span.status = OpenTelemetry::Trace::Status.error("Error: #{e.message}")
113
+ raise e
114
+ end
115
+ end
116
+ end
117
+
118
+ def add_response_attributes(span, response)
119
+ return unless response.is_a?(Hash) && response.dig("usage")
120
+
121
+ attributes = {
122
+ "gen_ai.usage.input_tokens" => response.dig("usage", "prompt_tokens"),
123
+ "gen_ai.usage.output_tokens" => response.dig("usage", "completion_tokens"),
124
+ "gen_ai.response.model" => response.dig("model")
125
+ }.compact
126
+
127
+ span.add_attributes(attributes)
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
@@ -3,9 +3,9 @@
3
3
  module Paid
4
4
  class BillingFrequency
5
5
 
6
- MONTHLY = "Monthly"
7
- QUARTERLY = "Quarterly"
8
- ANNUAL = "Annual"
6
+ MONTHLY = "monthly"
7
+ QUARTERLY = "quarterly"
8
+ ANNUAL = "annual"
9
9
 
10
10
  end
11
11
  end
@@ -11,6 +11,8 @@ module Paid
11
11
  attr_reader :event_name
12
12
  # @return [Boolean]
13
13
  attr_reader :taxable
14
+ # @return [Float]
15
+ attr_reader :credit_cost
14
16
  # @return [Paid::ChargeType]
15
17
  attr_reader :charge_type
16
18
  # @return [Paid::PricingModelType]
@@ -29,21 +31,23 @@ module Paid
29
31
 
30
32
  # @param event_name [String]
31
33
  # @param taxable [Boolean]
34
+ # @param credit_cost [Float]
32
35
  # @param charge_type [Paid::ChargeType]
33
36
  # @param pricing_model [Paid::PricingModelType]
34
37
  # @param billing_frequency [Paid::BillingFrequency]
35
38
  # @param price_points [Hash{String => Paid::AgentPricePoint}]
36
39
  # @param additional_properties [OpenStruct] Additional properties unmapped to the current class definition
37
40
  # @return [Paid::Pricing]
38
- def initialize(event_name: OMIT, taxable:, charge_type:, pricing_model:, billing_frequency:, price_points:, additional_properties: nil)
41
+ def initialize(event_name: OMIT, taxable:, credit_cost: OMIT, charge_type:, pricing_model:, billing_frequency:, price_points:, additional_properties: nil)
39
42
  @event_name = event_name if event_name != OMIT
40
43
  @taxable = taxable
44
+ @credit_cost = credit_cost if credit_cost != OMIT
41
45
  @charge_type = charge_type
42
46
  @pricing_model = pricing_model
43
47
  @billing_frequency = billing_frequency
44
48
  @price_points = price_points
45
49
  @additional_properties = additional_properties
46
- @_field_set = { "eventName": event_name, "taxable": taxable, "chargeType": charge_type, "pricingModel": pricing_model, "billingFrequency": billing_frequency, "pricePoints": price_points }.reject do | _k, v |
50
+ @_field_set = { "eventName": event_name, "taxable": taxable, "creditCost": credit_cost, "chargeType": charge_type, "pricingModel": pricing_model, "billingFrequency": billing_frequency, "pricePoints": price_points }.reject do | _k, v |
47
51
  v == OMIT
48
52
  end
49
53
  end
@@ -56,6 +60,7 @@ end
56
60
  parsed_json = JSON.parse(json_object)
57
61
  event_name = parsed_json["eventName"]
58
62
  taxable = parsed_json["taxable"]
63
+ credit_cost = parsed_json["creditCost"]
59
64
  charge_type = parsed_json["chargeType"]
60
65
  pricing_model = parsed_json["pricingModel"]
61
66
  billing_frequency = parsed_json["billingFrequency"]
@@ -66,6 +71,7 @@ end
66
71
  new(
67
72
  event_name: event_name,
68
73
  taxable: taxable,
74
+ credit_cost: credit_cost,
69
75
  charge_type: charge_type,
70
76
  pricing_model: pricing_model,
71
77
  billing_frequency: billing_frequency,
@@ -88,6 +94,7 @@ end
88
94
  def self.validate_raw(obj:)
89
95
  obj.event_name&.is_a?(String) != false || raise("Passed value for field obj.event_name is not the expected type, validation failed.")
90
96
  obj.taxable.is_a?(Boolean) != false || raise("Passed value for field obj.taxable is not the expected type, validation failed.")
97
+ obj.credit_cost&.is_a?(Float) != false || raise("Passed value for field obj.credit_cost is not the expected type, validation failed.")
91
98
  obj.charge_type.is_a?(Paid::ChargeType) != false || raise("Passed value for field obj.charge_type is not the expected type, validation failed.")
92
99
  obj.pricing_model.is_a?(Paid::PricingModelType) != false || raise("Passed value for field obj.pricing_model is not the expected type, validation failed.")
93
100
  obj.billing_frequency.is_a?(Paid::BillingFrequency) != false || raise("Passed value for field obj.billing_frequency is not the expected type, validation failed.")
@@ -6,6 +6,7 @@ module Paid
6
6
  PER_UNIT = "PerUnit"
7
7
  VOLUME_PRICING = "VolumePricing"
8
8
  GRADUATED_PRICING = "GraduatedPricing"
9
+ PREPAID_CREDITS = "PrepaidCredits"
9
10
 
10
11
  end
11
12
  end
@@ -8,6 +8,8 @@ module Paid
8
8
  attr_reader :event_name
9
9
  # @return [String]
10
10
  attr_reader :agent_id
11
+ # @return [String]
12
+ attr_reader :external_agent_id
11
13
  # @return [String]
12
14
  attr_reader :customer_id
13
15
  # @return [Hash{String => Object}]
@@ -22,17 +24,19 @@ module Paid
22
24
 
23
25
  # @param event_name [String]
24
26
  # @param agent_id [String]
27
+ # @param external_agent_id [String]
25
28
  # @param customer_id [String]
26
29
  # @param data [Hash{String => Object}]
27
30
  # @param additional_properties [OpenStruct] Additional properties unmapped to the current class definition
28
31
  # @return [Paid::Signal]
29
- def initialize(event_name: OMIT, agent_id: OMIT, customer_id: OMIT, data: OMIT, additional_properties: nil)
32
+ def initialize(event_name: OMIT, agent_id: OMIT, external_agent_id: OMIT, customer_id: OMIT, data: OMIT, additional_properties: nil)
30
33
  @event_name = event_name if event_name != OMIT
31
34
  @agent_id = agent_id if agent_id != OMIT
35
+ @external_agent_id = external_agent_id if external_agent_id != OMIT
32
36
  @customer_id = customer_id if customer_id != OMIT
33
37
  @data = data if data != OMIT
34
38
  @additional_properties = additional_properties
35
- @_field_set = { "event_name": event_name, "agent_id": agent_id, "customer_id": customer_id, "data": data }.reject do | _k, v |
39
+ @_field_set = { "event_name": event_name, "agent_id": agent_id, "external_agent_id": external_agent_id, "customer_id": customer_id, "data": data }.reject do | _k, v |
36
40
  v == OMIT
37
41
  end
38
42
  end
@@ -45,11 +49,13 @@ end
45
49
  parsed_json = JSON.parse(json_object)
46
50
  event_name = parsed_json["event_name"]
47
51
  agent_id = parsed_json["agent_id"]
52
+ external_agent_id = parsed_json["external_agent_id"]
48
53
  customer_id = parsed_json["customer_id"]
49
54
  data = parsed_json["data"]
50
55
  new(
51
56
  event_name: event_name,
52
57
  agent_id: agent_id,
58
+ external_agent_id: external_agent_id,
53
59
  customer_id: customer_id,
54
60
  data: data,
55
61
  additional_properties: struct
@@ -70,6 +76,7 @@ end
70
76
  def self.validate_raw(obj:)
71
77
  obj.event_name&.is_a?(String) != false || raise("Passed value for field obj.event_name is not the expected type, validation failed.")
72
78
  obj.agent_id&.is_a?(String) != false || raise("Passed value for field obj.agent_id is not the expected type, validation failed.")
79
+ obj.external_agent_id&.is_a?(String) != false || raise("Passed value for field obj.external_agent_id is not the expected type, validation failed.")
73
80
  obj.customer_id&.is_a?(String) != false || raise("Passed value for field obj.customer_id is not the expected type, validation failed.")
74
81
  obj.data&.is_a?(Hash) != false || raise("Passed value for field obj.data is not the expected type, validation failed.")
75
82
  end
@@ -19,6 +19,7 @@ module Paid
19
19
  # @param signals [Array<Hash>] Request of type Array<Paid::Signal>, as a Hash
20
20
  # * :event_name (String)
21
21
  # * :agent_id (String)
22
+ # * :external_agent_id (String)
22
23
  # * :customer_id (String)
23
24
  # * :data (Hash{String => Object})
24
25
  # @param request_options [Paid::RequestOptions]
@@ -29,7 +30,7 @@ module Paid
29
30
  # environment: Paid::Environment::PRODUCTION,
30
31
  # token: "YOUR_AUTH_TOKEN"
31
32
  # )
32
- # api.usage.record_bulk
33
+ # api.usage.record_bulk(signals: [{ }, { }, { }])
33
34
  def record_bulk(signals: nil, request_options: nil)
34
35
  response = @request_client.conn.post do | req |
35
36
  unless request_options&.timeout_in_seconds.nil?
@@ -62,6 +63,7 @@ end
62
63
  # @param signals [Array<Hash>] Request of type Array<Paid::Signal>, as a Hash
63
64
  # * :event_name (String)
64
65
  # * :agent_id (String)
66
+ # * :external_agent_id (String)
65
67
  # * :customer_id (String)
66
68
  # * :data (Hash{String => Object})
67
69
  # @param request_options [Paid::RequestOptions]
@@ -72,7 +74,7 @@ end
72
74
  # environment: Paid::Environment::PRODUCTION,
73
75
  # token: "YOUR_AUTH_TOKEN"
74
76
  # )
75
- # api.usage.record_bulk
77
+ # api.usage.record_bulk(signals: [{ }, { }, { }])
76
78
  def record_bulk(signals: nil, request_options: nil)
77
79
  Async do
78
80
  response = @request_client.conn.post do | req |
data/lib/paid_ruby.rb CHANGED
@@ -9,6 +9,8 @@ require_relative "paid_ruby/contacts/client"
9
9
  require_relative "paid_ruby/orders/client"
10
10
  require_relative "paid_ruby/usage/client"
11
11
  require_relative "extensions/batch"
12
+ require_relative "paid_ruby/tracing/tracing"
13
+ require_relative "paid_ruby/tracing/wrappers/open_ai_wrapper"
12
14
 
13
15
  module Paid
14
16
  class Client
@@ -44,6 +46,19 @@ module Paid
44
46
  @orders = Paid::OrdersClient.new(request_client: @request_client)
45
47
  @usage = Paid::BatchUsageClient.new(request_client: @request_client)
46
48
  end
49
+
50
+ def initialize_tracing
51
+ token = @request_client.token
52
+ api_key = token.gsub(/^Bearer /, "")
53
+ Paid::Tracing.initialize_tracing(api_key: api_key)
54
+ end
55
+
56
+ # @param external_customer_id [String]
57
+ # @param args [Array]
58
+ # @param block [Proc]
59
+ def capture(*args, external_customer_id:, &block)
60
+ Paid::Tracing.capture(*args, external_customer_id: external_customer_id, &block)
61
+ end
47
62
  end
48
63
 
49
64
  class AsyncClient
@@ -79,5 +94,18 @@ module Paid
79
94
  @orders = Paid::AsyncOrdersClient.new(request_client: @async_request_client)
80
95
  @usage = Paid::AsyncBatchUsageClient.new(request_client: @async_request_client)
81
96
  end
97
+
98
+ def initialize_tracing
99
+ token = @async_request_client.token
100
+ api_key = token.gsub(/^Bearer /, "")
101
+ Paid::Tracing.initialize_tracing(api_key: api_key)
102
+ end
103
+
104
+ # @param external_customer_id [String]
105
+ # @param args [Array]
106
+ # @param block [Proc]
107
+ def capture(*args, external_customer_id:, &block)
108
+ Paid::Tracing.capture(*args, external_customer_id: external_customer_id, &block)
109
+ end
82
110
  end
83
111
  end
data/lib/requests.rb CHANGED
@@ -47,7 +47,7 @@ end
47
47
  end
48
48
  # @return [Hash{String => String}]
49
49
  def get_headers
50
- headers = { "X-Fern-Language": 'Ruby', "X-Fern-SDK-Name": 'paid_ruby', "X-Fern-SDK-Version": '0.1.1' }
50
+ headers = { "X-Fern-Language": 'Ruby', "X-Fern-SDK-Name": 'paid_ruby', "X-Fern-SDK-Version": '0.1.2' }
51
51
  headers["Authorization"] = ((@token.is_a? Method) ? @token.call : @token) unless @token.nil?
52
52
  headers
53
53
  end
@@ -92,7 +92,7 @@ end
92
92
  end
93
93
  # @return [Hash{String => String}]
94
94
  def get_headers
95
- headers = { "X-Fern-Language": 'Ruby', "X-Fern-SDK-Name": 'paid_ruby', "X-Fern-SDK-Version": '0.1.1' }
95
+ headers = { "X-Fern-Language": 'Ruby', "X-Fern-SDK-Name": 'paid_ruby', "X-Fern-SDK-Version": '0.1.2' }
96
96
  headers["Authorization"] = ((@token.is_a? Method) ? @token.call : @token) unless @token.nil?
97
97
  headers
98
98
  end
metadata CHANGED
@@ -1,15 +1,70 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: paid_ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - ''
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2025-06-12 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: opentelemetry-api
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '1.5'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '1.5'
26
+ - !ruby/object:Gem::Dependency
27
+ name: opentelemetry-exporter-otlp
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: 0.30.0
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: 0.30.0
40
+ - !ruby/object:Gem::Dependency
41
+ name: opentelemetry-sdk
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '1.8'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '1.8'
54
+ - !ruby/object:Gem::Dependency
55
+ name: ruby-openai
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '8.1'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '8.1'
13
68
  - !ruby/object:Gem::Dependency
14
69
  name: faraday
15
70
  requirement: !ruby/object:Gem::Requirement
@@ -105,6 +160,9 @@ files:
105
160
  - lib/paid_ruby/customers/client.rb
106
161
  - lib/paid_ruby/orders/client.rb
107
162
  - lib/paid_ruby/orders/lines/client.rb
163
+ - lib/paid_ruby/tracing/logging.rb
164
+ - lib/paid_ruby/tracing/tracing.rb
165
+ - lib/paid_ruby/tracing/wrappers/open_ai_wrapper.rb
108
166
  - lib/paid_ruby/types/address.rb
109
167
  - lib/paid_ruby/types/agent.rb
110
168
  - lib/paid_ruby/types/agent_attribute.rb
@@ -141,7 +199,6 @@ metadata:
141
199
  homepage_uri: https://github.com/paid-ai/paid-ruby
142
200
  source_code_uri: https://github.com/paid-ai/paid-ruby
143
201
  changelog_uri: https://github.com/paid-ai/paid-ruby/blob/master/CHANGELOG.md
144
- post_install_message:
145
202
  rdoc_options: []
146
203
  require_paths:
147
204
  - lib
@@ -156,8 +213,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
156
213
  - !ruby/object:Gem::Version
157
214
  version: '0'
158
215
  requirements: []
159
- rubygems_version: 3.1.6
160
- signing_key:
216
+ rubygems_version: 3.6.7
161
217
  specification_version: 4
162
218
  summary: ''
163
219
  test_files: []