runapi-core 0.2.15 → 0.3.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/README.md +30 -1
- data/lib/runapi/core/auth.rb +8 -3
- data/lib/runapi/core/client.rb +10 -1
- data/lib/runapi/core/http_client.rb +11 -1
- data/lib/runapi/core/pricing.rb +99 -0
- data/lib/runapi/core/types.rb +22 -1
- data/lib/runapi/core/version.rb +1 -1
- data/lib/runapi/core.rb +1 -0
- metadata +10 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9a0bc423a77bb7770c6db1e3a6fa10ae1679ef58f7762231b02c28e86f85b4e3
|
|
4
|
+
data.tar.gz: fce5aa1e8e03dfc0c3c873a6f499bd5c51b173b87f91b75a74955413f59b19e2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fc56fca2936974941318607a19905d77542bf56f529fdf68577a7da9b18bc186cdbe4a3ea45f367b1d64d8f48622f384cfc8ef089503a1fdf87df399ed6dc84f
|
|
7
|
+
data.tar.gz: 548078f4a0b851f5a15f3f76fbadfd5164a122100c0842921bf9e6efa572049b3b715fbfdec7f82a8b7cdfe7773d0565a46ae648772cc75304c1f9a06c348838
|
data/README.md
CHANGED
|
@@ -12,10 +12,36 @@ gem install runapi-core
|
|
|
12
12
|
|
|
13
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.
|
|
14
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
|
+
```
|
|
38
|
+
|
|
15
39
|
## Request Identifiers
|
|
16
40
|
|
|
17
41
|
RunAPI accepts an optional `X-Client-Request-Id` header on public API calls. Use printable ASCII values up to 512 characters. Accepted values are echoed in the response and stored with the RunAPI task for support and reconciliation.
|
|
18
42
|
|
|
43
|
+
Task-creation calls also accept an optional opaque `Idempotency-Key` up to 512 characters. Generate one value per logical task and reuse it only with identical input after an unknown result. Reusing the value with different input returns `409 Conflict`; do not derive it from `X-Client-Request-Id`.
|
|
44
|
+
|
|
19
45
|
High-level Ruby Provider Client resource methods accept per-request options and keep response headers on the returned model object. This example uses the Suno Provider Client; install `runapi-suno` to run it.
|
|
20
46
|
|
|
21
47
|
```ruby
|
|
@@ -23,7 +49,10 @@ require "runapi/suno"
|
|
|
23
49
|
|
|
24
50
|
client = RunApi::Suno::Client.new(api_key: ENV.fetch("RUNAPI_API_KEY"))
|
|
25
51
|
options = RunApi::Core::RequestOptions.new(
|
|
26
|
-
headers: {
|
|
52
|
+
headers: {
|
|
53
|
+
"X-Client-Request-Id" => "order-123",
|
|
54
|
+
"Idempotency-Key" => "opaque-logical-task-123"
|
|
55
|
+
}
|
|
27
56
|
)
|
|
28
57
|
|
|
29
58
|
response = client.text_to_music.create(
|
data/lib/runapi/core/auth.rb
CHANGED
|
@@ -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 =
|
|
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
|
|
data/lib/runapi/core/client.rb
CHANGED
|
@@ -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,13 +13,22 @@ 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)
|
|
19
21
|
client_options = Core::ClientOptions.new(api_key: @api_key, **options)
|
|
22
|
+
@owns_http_client = client_options.http_client.nil?
|
|
20
23
|
@http = client_options.http_client || Core::HttpClient.new(client_options)
|
|
21
24
|
@files = Files.new(@http)
|
|
22
25
|
@account = Account.new(@http)
|
|
26
|
+
@pricing = Pricing.new(@http)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Closes the SDK-created HTTP client. Injected clients remain caller-owned.
|
|
30
|
+
def close
|
|
31
|
+
@http.close if @owns_http_client
|
|
23
32
|
end
|
|
24
33
|
|
|
25
34
|
protected
|
|
@@ -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?
|
|
@@ -60,6 +64,12 @@ module RunApi
|
|
|
60
64
|
close_multipart_files(req)
|
|
61
65
|
end
|
|
62
66
|
|
|
67
|
+
def close
|
|
68
|
+
@pool.shutdown do |http|
|
|
69
|
+
http.finish if http.started?
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
63
73
|
# PUT bytes straight to a pre-authorized upload URL with the exact headers
|
|
64
74
|
# issued for it. Skips the base URL, auth, and retries: the URL is single-use
|
|
65
75
|
# and the body is not safe to replay.
|
|
@@ -100,7 +110,7 @@ module RunApi
|
|
|
100
110
|
klass = Net::HTTP.const_get(method.to_s.capitalize)
|
|
101
111
|
req = klass.new(uri.request_uri)
|
|
102
112
|
|
|
103
|
-
req["Authorization"] = "Bearer #{@options.api_key}"
|
|
113
|
+
req["Authorization"] = "Bearer #{@options.api_key}" if @options.api_key
|
|
104
114
|
req["Accept"] = "application/json"
|
|
105
115
|
req["User-Agent"] = Constants::SDK_USER_AGENT
|
|
106
116
|
|
|
@@ -0,0 +1,99 @@
|
|
|
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_5m_price_per_1m_cents, Numeric
|
|
25
|
+
optional :cache_write_1h_price_per_1m_cents, Numeric
|
|
26
|
+
required :billing_config, Hash
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
class ScheduleListResponse < RunApi::Core::BaseModel
|
|
30
|
+
required :as_of, String
|
|
31
|
+
required :price_schedules, [Schedule]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class ScheduleNotModifiedResponse < RunApi::Core::BaseModel
|
|
35
|
+
required :not_modified, TrueClass
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
class QuoteResponse < RunApi::Core::BaseModel
|
|
39
|
+
required :service, String
|
|
40
|
+
required :action, String
|
|
41
|
+
optional :model, String
|
|
42
|
+
required :pricing_status, String
|
|
43
|
+
required :currency, String
|
|
44
|
+
required :reservation_amount_cents, Numeric
|
|
45
|
+
required :estimate_basis, String
|
|
46
|
+
required :as_of, String
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
class QuoteEnvelope < RunApi::Core::BaseModel
|
|
50
|
+
required :price_quote, QuoteResponse
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def initialize(http)
|
|
54
|
+
@http = http
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def list(service: nil, action: nil, model: nil, options: nil)
|
|
58
|
+
query = URI.encode_www_form(compact_params(service:, action:, model:))
|
|
59
|
+
path = query.empty? ? SCHEDULES_ENDPOINT : "#{SCHEDULES_ENDPOINT}?#{query}"
|
|
60
|
+
request_options = (options || RequestOptions.new).dup
|
|
61
|
+
request_options.allow_not_modified = true
|
|
62
|
+
response = @http.request(:get, path, options: request_options)
|
|
63
|
+
payload = response.is_a?(Core::Response) ? response.body : response
|
|
64
|
+
response_class = payload["not_modified"] ? ScheduleNotModifiedResponse : ScheduleListResponse
|
|
65
|
+
result = RunApi::Core::BaseModel.coerce(payload, as: response_class)
|
|
66
|
+
result.with_response_headers(response.response_headers) if response.is_a?(Core::Response)
|
|
67
|
+
result
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def quote(service:, action:, model: nil, params: {}, options: nil)
|
|
71
|
+
response = request(
|
|
72
|
+
:post,
|
|
73
|
+
QUOTES_ENDPOINT,
|
|
74
|
+
body: compact_params(service:, action:, model:, params:),
|
|
75
|
+
options:,
|
|
76
|
+
response_class: QuoteEnvelope
|
|
77
|
+
)
|
|
78
|
+
response.price_quote
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Standalone live Pricing client with optional API authentication.
|
|
83
|
+
class PricingClient < Pricing
|
|
84
|
+
def initialize(api_key: nil, **options)
|
|
85
|
+
client_options = Core::ClientOptions.new(
|
|
86
|
+
api_key: Core::Auth.resolve_optional_api_key(api_key),
|
|
87
|
+
**options
|
|
88
|
+
)
|
|
89
|
+
@owns_http_client = client_options.http_client.nil?
|
|
90
|
+
super(client_options.http_client || Core::HttpClient.new(client_options))
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Closes the SDK-created HTTP client. Injected clients remain caller-owned.
|
|
94
|
+
def close
|
|
95
|
+
@http.close if @owns_http_client
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
data/lib/runapi/core/types.rb
CHANGED
|
@@ -22,7 +22,7 @@ module RunApi
|
|
|
22
22
|
# )
|
|
23
23
|
#
|
|
24
24
|
# @!attribute [rw] api_key
|
|
25
|
-
# @return [String] API key for
|
|
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
|
data/lib/runapi/core/version.rb
CHANGED
data/lib/runapi/core.rb
CHANGED
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.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- RunAPI
|
|
@@ -13,16 +13,22 @@ dependencies:
|
|
|
13
13
|
name: connection_pool
|
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
|
15
15
|
requirements:
|
|
16
|
-
- - "
|
|
16
|
+
- - ">="
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
18
|
version: '2.4'
|
|
19
|
+
- - "<"
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '4'
|
|
19
22
|
type: :runtime
|
|
20
23
|
prerelease: false
|
|
21
24
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
25
|
requirements:
|
|
23
|
-
- - "
|
|
26
|
+
- - ">="
|
|
24
27
|
- !ruby/object:Gem::Version
|
|
25
28
|
version: '2.4'
|
|
29
|
+
- - "<"
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: '4'
|
|
26
32
|
description: The RunAPI Core Ruby SDK provides shared authentication, HTTP, retry,
|
|
27
33
|
error, and polling primitives for RunAPI model gems. Install `runapi-core` only
|
|
28
34
|
when you are building SDK infrastructure or shared Ruby tooling; application code
|
|
@@ -49,6 +55,7 @@ files:
|
|
|
49
55
|
- lib/runapi/core/http_client.rb
|
|
50
56
|
- lib/runapi/core/multipart_body.rb
|
|
51
57
|
- lib/runapi/core/polling.rb
|
|
58
|
+
- lib/runapi/core/pricing.rb
|
|
52
59
|
- lib/runapi/core/resource_helpers.rb
|
|
53
60
|
- lib/runapi/core/response.rb
|
|
54
61
|
- lib/runapi/core/types.rb
|