runapi-core 0.2.16 → 0.3.1

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: f199f45d065827bb1931ab5faff8dd945fd62b3cb0a5260b268fdfee41875a45
4
- data.tar.gz: 6d2c40ffded8406818798c40bfddca448e1a7149981fd0b012dc5c0f4d17e02e
3
+ metadata.gz: cb5369d4bc3ec00ba92341e7151b7e1d62b6972ef3e84fd35474638da86a9926
4
+ data.tar.gz: ed1fe305dba138db597e86e31421312bf0a017d9c3ed1564b0e44044a9a537a0
5
5
  SHA512:
6
- metadata.gz: 17e79439f00b8b10fbbbbcf00b3d074cfd9e3cf9d70c4b20b91743aee12cba2e122c9da364a66c0c21844d5e2153083102acacfb639871e1a24377899b3bda41
7
- data.tar.gz: 707cadac8b31ca82c31fa191cb017e58192eb7c6b4da7bd15aba8fe40209f783537ee2452bc4846538c123ddc0843c31c7b24a86158a766f7740ec309e269d38
6
+ metadata.gz: 7c885608b3f52f9e5ab11937e032149a164a781c8f413e19d394484a7b6576b622afd82c2c152ed52ca5ff67c389f9ee40d28aef174f7553947fd888c204a1cb
7
+ data.tar.gz: b0af7a1272a3f69003bb4fa80ca4848511f847dcd7a5a1e1c5678b358c90313962312ddc4f7a0f870a32de801cf2e5af94e33879fec14084ecf1b36e920be8ed
data/README.md CHANGED
@@ -10,7 +10,31 @@ gem install runapi-core
10
10
 
11
11
  ## Notes
12
12
 
13
- Use the core gem for common client options, error classes, request helpers, and task polling behavior that Provider Clients share. Public SDK docs live at https://runapi.ai/docs#runapi-sdks and the model catalog lives at https://runapi.ai/models.
13
+ Use the core gem for common client options, error classes, request helpers, and task polling behavior that Provider Clients share. Public SDK docs live at https://runapi.ai/docs/resources/sdks and the model catalog lives at https://runapi.ai/models.
14
+
15
+ ## Live Pricing
16
+
17
+ Every Provider Client exposes `pricing`. Schedule lookup and quotes use the live API response and do not retain a local price cache.
18
+
19
+ ```ruby
20
+ schedules = client.pricing.list(service: "suno")
21
+ quote = client.pricing.quote(
22
+ service: "suno",
23
+ action: "convert_audio",
24
+ params: {}
25
+ )
26
+ ```
27
+
28
+ Public schedule lookup and quotes that use only public parameters work without an API key. Pass a standard API key when a quote references an account-owned source Task.
29
+
30
+ Use `PricingClient` when pricing is the only capability needed:
31
+
32
+ ```ruby
33
+ require "runapi/core"
34
+
35
+ pricing = RunApi::Core::PricingClient.new
36
+ schedules = pricing.list(service: "suno")
37
+ ```
14
38
 
15
39
  ## Request Identifiers
16
40
 
@@ -19,15 +19,20 @@ module RunApi
19
19
  # @param explicit [String, nil] API key passed directly to a client constructor.
20
20
  # @return [String] the resolved API key
21
21
  def self.resolve_api_key(explicit = nil)
22
- resolved = normalize(explicit) ||
23
- normalize(RunApi.respond_to?(:api_key) ? RunApi.api_key : nil) ||
24
- normalize(ENV[ENV_VAR_NAME])
22
+ resolved = resolve_optional_api_key(explicit)
25
23
 
26
24
  raise AuthenticationError, MISSING_KEY_MESSAGE unless resolved
27
25
 
28
26
  resolved
29
27
  end
30
28
 
29
+ # Resolve an API key when present without requiring one for public resources.
30
+ def self.resolve_optional_api_key(explicit = nil)
31
+ normalize(explicit) ||
32
+ normalize(RunApi.respond_to?(:api_key) ? RunApi.api_key : nil) ||
33
+ normalize(ENV[ENV_VAR_NAME])
34
+ end
35
+
31
36
  def self.normalize(value)
32
37
  return nil unless value.is_a?(String)
33
38
 
@@ -3,7 +3,7 @@
3
3
  module RunApi
4
4
  module Core
5
5
  # Base class for every RunAPI client. Resolves the API key, builds the shared
6
- # HTTP client, and exposes the Universal Resources (file upload, account) that
6
+ # HTTP client, and exposes the Universal Resources (file upload, account, pricing) that
7
7
  # are available on any client regardless of which model gem was required.
8
8
  #
9
9
  # Provider clients inherit from this and build their model resources from the
@@ -13,6 +13,8 @@ module RunApi
13
13
  attr_reader :files
14
14
  # @return [Account] Account info and balance operations.
15
15
  attr_reader :account
16
+ # @return [Pricing] Live Price Schedule lookup and Price Quote operations.
17
+ attr_reader :pricing
16
18
 
17
19
  def initialize(api_key: nil, **options)
18
20
  @api_key = Core::Auth.resolve_api_key(api_key)
@@ -21,6 +23,7 @@ module RunApi
21
23
  @http = client_options.http_client || Core::HttpClient.new(client_options)
22
24
  @files = Files.new(@http)
23
25
  @account = Account.new(@http)
26
+ @pricing = Pricing.new(@http)
24
27
  end
25
28
 
26
29
  # Closes the SDK-created HTTP client. Injected clients remain caller-owned.
@@ -37,6 +37,10 @@ module RunApi
37
37
  raise NetworkError, e.message
38
38
  end
39
39
 
40
+ if response.is_a?(Net::HTTPNotModified) && options&.allow_not_modified
41
+ return Response.new(body: {"not_modified" => true}, headers: response_headers(response))
42
+ end
43
+
40
44
  if response.is_a?(Net::HTTPSuccess)
41
45
  body = parse_body(response.body)
42
46
  return nil if body.nil?
@@ -106,7 +110,7 @@ module RunApi
106
110
  klass = Net::HTTP.const_get(method.to_s.capitalize)
107
111
  req = klass.new(uri.request_uri)
108
112
 
109
- req["Authorization"] = "Bearer #{@options.api_key}"
113
+ req["Authorization"] = "Bearer #{@options.api_key}" if @options.api_key
110
114
  req["Accept"] = "application/json"
111
115
  req["User-Agent"] = Constants::SDK_USER_AGENT
112
116
 
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RunApi
4
+ module Core
5
+ class Pricing
6
+ include RunApi::Core::ResourceHelpers
7
+
8
+ SCHEDULES_ENDPOINT = "/api/v1/price_schedules"
9
+ QUOTES_ENDPOINT = "/api/v1/price_quotes"
10
+
11
+ class Schedule < RunApi::Core::BaseModel
12
+ required :service, String
13
+ required :action, String
14
+ optional :model, String
15
+ required :pricing_status, String
16
+ required :catalog_status, String
17
+ required :currency, String
18
+ required :billing_unit, String
19
+ required :billing_strategy, String
20
+ optional :unit_price_cents, Numeric
21
+ optional :input_price_per_1m_cents, Numeric
22
+ optional :output_price_per_1m_cents, Numeric
23
+ optional :cache_read_price_per_1m_cents, Numeric
24
+ optional :cache_write_price_per_1m_cents, Numeric
25
+ optional :cache_write_5m_price_per_1m_cents, Numeric
26
+ optional :cache_write_1h_price_per_1m_cents, Numeric
27
+ required :billing_config, Hash
28
+ end
29
+
30
+ class ScheduleListResponse < RunApi::Core::BaseModel
31
+ required :as_of, String
32
+ required :price_schedules, [Schedule]
33
+ end
34
+
35
+ class ScheduleNotModifiedResponse < RunApi::Core::BaseModel
36
+ required :not_modified, TrueClass
37
+ end
38
+
39
+ class QuoteResponse < RunApi::Core::BaseModel
40
+ required :service, String
41
+ required :action, String
42
+ optional :model, String
43
+ required :pricing_status, String
44
+ required :currency, String
45
+ required :reservation_amount_cents, Numeric
46
+ required :estimate_basis, String
47
+ required :as_of, String
48
+ end
49
+
50
+ class QuoteEnvelope < RunApi::Core::BaseModel
51
+ required :price_quote, QuoteResponse
52
+ end
53
+
54
+ def initialize(http)
55
+ @http = http
56
+ end
57
+
58
+ def list(service: nil, action: nil, model: nil, options: nil)
59
+ query = URI.encode_www_form(compact_params(service:, action:, model:))
60
+ path = query.empty? ? SCHEDULES_ENDPOINT : "#{SCHEDULES_ENDPOINT}?#{query}"
61
+ request_options = (options || RequestOptions.new).dup
62
+ request_options.allow_not_modified = true
63
+ response = @http.request(:get, path, options: request_options)
64
+ payload = response.is_a?(Core::Response) ? response.body : response
65
+ response_class = payload["not_modified"] ? ScheduleNotModifiedResponse : ScheduleListResponse
66
+ result = RunApi::Core::BaseModel.coerce(payload, as: response_class)
67
+ result.with_response_headers(response.response_headers) if response.is_a?(Core::Response)
68
+ result
69
+ end
70
+
71
+ def quote(service:, action:, model: nil, params: {}, options: nil)
72
+ response = request(
73
+ :post,
74
+ QUOTES_ENDPOINT,
75
+ body: compact_params(service:, action:, model:, params:),
76
+ options:,
77
+ response_class: QuoteEnvelope
78
+ )
79
+ response.price_quote
80
+ end
81
+ end
82
+
83
+ # Standalone live Pricing client with optional API authentication.
84
+ class PricingClient < Pricing
85
+ def initialize(api_key: nil, **options)
86
+ client_options = Core::ClientOptions.new(
87
+ api_key: Core::Auth.resolve_optional_api_key(api_key),
88
+ **options
89
+ )
90
+ @owns_http_client = client_options.http_client.nil?
91
+ super(client_options.http_client || Core::HttpClient.new(client_options))
92
+ end
93
+
94
+ # Closes the SDK-created HTTP client. Injected clients remain caller-owned.
95
+ def close
96
+ @http.close if @owns_http_client
97
+ end
98
+ end
99
+ end
100
+ end
@@ -22,7 +22,7 @@ module RunApi
22
22
  # )
23
23
  #
24
24
  # @!attribute [rw] api_key
25
- # @return [String] API key for authentication. Required.
25
+ # @return [String, nil] API key for authenticated resources and protected Price Quotes.
26
26
  # @!attribute [rw] base_url
27
27
  # @return [String] Base URL for API requests. Defaults to RunApi.base_url.
28
28
  # @!attribute [rw] timeout
@@ -78,6 +78,7 @@ module RunApi
78
78
  :headers,
79
79
  :timeout,
80
80
  :max_retries,
81
+ :allow_not_modified,
81
82
  keyword_init: true
82
83
  )
83
84
 
@@ -100,6 +101,25 @@ module RunApi
100
101
  end
101
102
  end
102
103
 
104
+ class TaskReservation < BaseModel
105
+ required :amount_cents, Numeric
106
+ end
107
+
108
+ class TaskSettlement < BaseModel
109
+ required :charged_amount_cents, Numeric
110
+ required :amount_micro_cents, Numeric
111
+ end
112
+
113
+ class TaskRefund < BaseModel
114
+ required :refunded_at, String
115
+ end
116
+
117
+ class TaskBillingFacts < BaseModel
118
+ optional :reservation, TaskReservation
119
+ optional :settlement, TaskSettlement
120
+ optional :refund, TaskRefund
121
+ end
122
+
103
123
  # Typed response structure for async task operations.
104
124
  # Additional API-specific fields are preserved and exposed via dot notation.
105
125
  #
@@ -122,6 +142,7 @@ module RunApi
122
142
  optional :id, String
123
143
  optional :status, String
124
144
  optional :error, String
145
+ optional :billing, TaskBillingFacts
125
146
  end
126
147
  end
127
148
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module RunApi
4
4
  module Core
5
- VERSION = "0.2.16"
5
+ VERSION = "0.3.1"
6
6
  end
7
7
 
8
8
  VERSION = Core::VERSION
data/lib/runapi/core.rb CHANGED
@@ -21,4 +21,5 @@ require_relative "core/polling"
21
21
  require_relative "core/resource_helpers"
22
22
  require_relative "core/files"
23
23
  require_relative "core/account"
24
+ require_relative "core/pricing"
24
25
  require_relative "core/client"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: runapi-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.16
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - RunAPI
@@ -55,6 +55,7 @@ files:
55
55
  - lib/runapi/core/http_client.rb
56
56
  - lib/runapi/core/multipart_body.rb
57
57
  - lib/runapi/core/polling.rb
58
+ - lib/runapi/core/pricing.rb
58
59
  - lib/runapi/core/resource_helpers.rb
59
60
  - lib/runapi/core/response.rb
60
61
  - lib/runapi/core/types.rb