fal 0.0.3 → 0.1.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.
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fal
4
+ # Represents a cost estimate response from the Platform API.
5
+ # Computes estimates via POST /models/pricing/estimate.
6
+ class PriceEstimate
7
+ ESTIMATE_PATH = "/models/pricing/estimate"
8
+
9
+ # Supported estimate types.
10
+ module EstimateType
11
+ # @return [String]
12
+ HISTORICAL_API_PRICE = "historical_api_price"
13
+ # @return [String]
14
+ UNIT_PRICE = "unit_price"
15
+ end
16
+
17
+ # Simple value object for endpoint inputs.
18
+ class Endpoint
19
+ # @return [String]
20
+ attr_reader :endpoint_id
21
+ # @return [Integer, nil]
22
+ attr_reader :call_quantity
23
+ # @return [Float, nil]
24
+ attr_reader :unit_quantity
25
+
26
+ # @param endpoint_id [String]
27
+ # @param call_quantity [Integer, nil]
28
+ # @param unit_quantity [Float, nil]
29
+ def initialize(endpoint_id:, call_quantity: nil, unit_quantity: nil)
30
+ @endpoint_id = endpoint_id
31
+ @call_quantity = call_quantity
32
+ @unit_quantity = unit_quantity
33
+ end
34
+ end
35
+
36
+ # @return [String]
37
+ attr_reader :estimate_type
38
+ # @return [Float]
39
+ attr_reader :total_cost
40
+ # @return [String]
41
+ attr_reader :currency
42
+
43
+ # @param attributes [Hash]
44
+ # @param client [Fal::Client]
45
+ def initialize(attributes, client: Fal.client)
46
+ @client = client
47
+ reset_attributes(attributes)
48
+ end
49
+
50
+ class << self
51
+ # Create a new cost estimate.
52
+ # Corresponds to POST https://api.fal.ai/v1/models/pricing/estimate
53
+ # @param estimate_type [String] one of EstimateType constants
54
+ # @param endpoints [Array<Fal::PriceEstimate::Endpoint, Hash>]
55
+ # @param client [Fal::Client]
56
+ # @return [Fal::PriceEstimate]
57
+ def create(estimate_type:, endpoints:, client: Fal.client)
58
+ endpoint_map = {}
59
+ Array(endpoints).each do |ep|
60
+ endpoint = ep.is_a?(Endpoint) ? ep : Endpoint.new(**ep)
61
+ quantity = endpoint.unit_quantity || endpoint.call_quantity
62
+
63
+ if estimate_type == EstimateType::UNIT_PRICE
64
+ # Accept either unit_quantity or call_quantity (treated as units) for convenience.
65
+ endpoint_map[endpoint.endpoint_id] = { "unit_quantity" => quantity }
66
+ else
67
+ endpoint_map[endpoint.endpoint_id] = { "call_quantity" => quantity }
68
+ end
69
+ end
70
+
71
+ payload = {
72
+ estimate_type: estimate_type,
73
+ endpoints: endpoint_map
74
+ }
75
+
76
+ attributes = client.post_api(ESTIMATE_PATH, payload)
77
+ new(attributes, client: client)
78
+ end
79
+ end
80
+
81
+ private
82
+
83
+ def reset_attributes(attributes)
84
+ @estimate_type = attributes["estimate_type"]
85
+ @total_cost = attributes["total_cost"]
86
+ @currency = attributes["currency"]
87
+ end
88
+ end
89
+ end
data/lib/fal/request.rb CHANGED
@@ -27,57 +27,58 @@ module Fal
27
27
  # @return [Hash, nil] Response payload when status is COMPLETED
28
28
  attr_reader :response
29
29
  # @return [String] The model identifier used when creating this request
30
- attr_reader :model_id
30
+ attr_reader :endpoint_id
31
31
 
32
32
  # @param attributes [Hash] Raw attributes from fal Queue API
33
- # @param model_id [String] Model ID in "namespace/name" format
33
+ # @param endpoint_id [String] Model ID in "namespace/name" format
34
34
  # @param client [Fal::Client] HTTP client to use for subsequent calls
35
- def initialize(attributes, model_id:, client: Fal.client)
35
+ def initialize(attributes, endpoint_id:, client: Fal.client)
36
36
  @client = client
37
- @model_id = model_id
37
+ @endpoint_id = endpoint_id
38
38
  reset_attributes(attributes)
39
39
  end
40
40
 
41
41
  class << self
42
42
  # Create a new queued request for a model.
43
- # Corresponds to POST https://queue.fal.run/{model_id}
43
+ # Corresponds to POST https://queue.fal.run/{endpoint_id}
44
44
  # Optionally appends fal_webhook query param per docs.
45
- # @param model_id [String]
45
+ # @param endpoint_id [String]
46
46
  # @param input [Hash]
47
47
  # @param webhook_url [String, nil]
48
48
  # @param client [Fal::Client]
49
49
  # @return [Fal::Request]
50
- def create!(model_id:, input:, webhook_url: nil, client: Fal.client)
51
- path = "/#{model_id}"
50
+ def create!(endpoint_id:, input:, webhook_url: nil, client: Fal.client)
51
+ path = "/#{endpoint_id}"
52
52
  body = input || {}
53
53
  path = "#{path}?fal_webhook=#{CGI.escape(webhook_url)}" if webhook_url
54
- attrs = client.post(path, body)
55
- new(attrs, model_id: model_id, client: client)
54
+ attributes = client.post(path, body)
55
+ new(attributes, endpoint_id: endpoint_id, client: client)
56
56
  end
57
57
 
58
58
  # Find the current status for a given request.
59
- # Corresponds to GET https://queue.fal.run/{model_id}/requests/{request_id}/status
59
+ # Corresponds to GET https://queue.fal.run/{endpoint_id}/requests/{request_id}/status
60
60
  # @param id [String]
61
- # @param model_id [String]
61
+ # @param endpoint_id [String]
62
62
  # @param logs [Boolean] include logs if true
63
63
  # @param client [Fal::Client]
64
64
  # @return [Fal::Request]
65
- def find_by!(id:, model_id:, logs: false, client: Fal.client)
66
- model_id_without_subpath = model_id.split("/").slice(0, 2).join("/")
67
- attrs = client.get("/#{model_id_without_subpath}/requests/#{id}/status", query: (logs ? { logs: 1 } : nil))
68
- new(attrs, model_id: model_id, client: client)
65
+ def find_by!(id:, endpoint_id:, logs: false, client: Fal.client)
66
+ endpoint_id_without_subpath = endpoint_id.split("/").slice(0, 2).join("/")
67
+ attributes = client.get("/#{endpoint_id_without_subpath}/requests/#{id}/status",
68
+ query: (logs ? { logs: 1 } : nil))
69
+ new(attributes, endpoint_id: endpoint_id, client: client)
69
70
  end
70
71
 
71
72
  # Stream a synchronous request using SSE and yield response chunks as they arrive.
72
73
  # It returns a Fal::Request initialized with the last streamed data in the response field.
73
- # @param model_id [String]
74
+ # @param endpoint_id [String]
74
75
  # @param input [Hash]
75
76
  # @param client [Fal::Client]
76
77
  # @yield [chunk] yields each parsed chunk Hash from the stream
77
78
  # @yieldparam chunk [Hash]
78
79
  # @return [Fal::Request]
79
- def stream!(model_id:, input:, client: Fal.client, &block)
80
- path = "/#{model_id}/stream"
80
+ def stream!(endpoint_id:, input:, client: Fal.client, &block)
81
+ path = "/#{endpoint_id}/stream"
81
82
  last_data = nil
82
83
 
83
84
  Stream.new(path: path, input: input, client: client).each do |event|
@@ -93,18 +94,18 @@ module Fal
93
94
  else
94
95
  last_data
95
96
  end
96
- attrs = {
97
+ attributes = {
97
98
  "request_id" => last_data && last_data["request_id"],
98
99
  "status" => last_data && last_data["status"],
99
100
  "response" => response_payload
100
101
  }.compact
101
- new(attrs, model_id: model_id, client: client)
102
+ new(attributes, endpoint_id: endpoint_id, client: client)
102
103
  end
103
104
  end
104
105
 
105
106
  # @return [String] The model ID without the subpath
106
- def model_id_without_subpath
107
- @model_id.split("/").slice(0, 2).join("/")
107
+ def endpoint_id_without_subpath
108
+ @endpoint_id.split("/").slice(0, 2).join("/")
108
109
  end
109
110
 
110
111
  # Reload the current status from the Queue API.
@@ -112,11 +113,12 @@ module Fal
112
113
  # @return [Fal::Request]
113
114
  def reload!(logs: false)
114
115
  if @status == Status::IN_PROGRESS || @status == Status::IN_QUEUE
115
- attrs = @client.get("/#{model_id_without_subpath}/requests/#{@id}/status", query: (logs ? { logs: 1 } : nil))
116
- reset_attributes(attrs)
116
+ attributes = @client.get("/#{endpoint_id_without_subpath}/requests/#{@id}/status",
117
+ query: (logs ? { logs: 1 } : nil))
118
+ reset_attributes(attributes)
117
119
  end
118
120
 
119
- @response = @client.get("/#{model_id_without_subpath}/requests/#{@id}") if @status == Status::COMPLETED
121
+ @response = @client.get("/#{endpoint_id_without_subpath}/requests/#{@id}") if @status == Status::COMPLETED
120
122
 
121
123
  self
122
124
  end
@@ -124,7 +126,7 @@ module Fal
124
126
  # Attempt to cancel the request if still in queue.
125
127
  # @return [Hash] cancellation response
126
128
  def cancel!
127
- @client.put("/#{model_id_without_subpath}/requests/#{@id}/cancel")
129
+ @client.put("/#{endpoint_id_without_subpath}/requests/#{@id}/cancel")
128
130
  end
129
131
 
130
132
  # @return [Boolean]
data/lib/fal/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Fal
4
- VERSION = "0.0.3"
4
+ VERSION = "0.1.0"
5
5
  end
@@ -46,11 +46,11 @@ module Fal
46
46
  end
47
47
 
48
48
  # Build from a Rack::Request.
49
- # @param req [#body]
49
+ # @param request [#body]
50
50
  # @return [Fal::WebhookRequest]
51
- def from_rack_request(req)
52
- body = req.body.read
53
- req.body.rewind if req.body.respond_to?(:rewind)
51
+ def from_rack_request(request)
52
+ body = request.body.read
53
+ request.body.rewind if request.body.respond_to?(:rewind)
54
54
  from_json(body)
55
55
  end
56
56
 
data/lib/fal.rb CHANGED
@@ -26,6 +26,7 @@ module Fal
26
26
  class Configuration
27
27
  DEFAULT_QUEUE_BASE = "https://queue.fal.run"
28
28
  DEFAULT_SYNC_BASE = "https://fal.run"
29
+ DEFAULT_API_BASE = "https://api.fal.ai/v1"
29
30
  DEFAULT_REQUEST_TIMEOUT = 120
30
31
 
31
32
  # API key used for authenticating with fal endpoints.
@@ -41,6 +42,10 @@ module Fal
41
42
  # @return [String]
42
43
  attr_accessor :sync_base
43
44
 
45
+ # Base URL for platform API endpoints (api.fal.ai/v1).
46
+ # @return [String]
47
+ attr_accessor :api_base
48
+
44
49
  # Timeout in seconds for opening and processing HTTP requests.
45
50
  # @return [Integer]
46
51
  attr_accessor :request_timeout
@@ -51,6 +56,7 @@ module Fal
51
56
  @api_key = ENV.fetch("FAL_KEY", nil)
52
57
  @queue_base = DEFAULT_QUEUE_BASE
53
58
  @sync_base = DEFAULT_SYNC_BASE
59
+ @api_base = DEFAULT_API_BASE
54
60
  @request_timeout = DEFAULT_REQUEST_TIMEOUT
55
61
  end
56
62
  end
@@ -98,3 +104,6 @@ require_relative "fal/client"
98
104
  require_relative "fal/request"
99
105
  require_relative "fal/stream"
100
106
  require_relative "fal/webhook_request"
107
+ require_relative "fal/price"
108
+ require_relative "fal/price_estimate"
109
+ require_relative "fal/model"
@@ -0,0 +1,59 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Fal
5
+ class Client
6
+ sig { returns(Fal::Configuration) }
7
+ def configuration; end
8
+
9
+ sig { params(value: Fal::Configuration).returns(Fal::Configuration) }
10
+ def configuration=(value); end
11
+
12
+ sig { params(configuration: T.nilable(Fal::Configuration)).void }
13
+ def initialize(configuration = nil); end
14
+
15
+ sig { params(path: String, payload: T.untyped, headers: T.untyped).returns(T.untyped) }
16
+ def post(path, payload = nil, headers: {}); end
17
+
18
+ sig do
19
+ params(
20
+ path: String,
21
+ payload: T.untyped,
22
+ on_data: T.proc.params(arg0: String, arg1: T.untyped).void
23
+ ).void
24
+ end
25
+ def post_stream(path, payload = nil, on_data:); end
26
+
27
+ sig { params(path: String, query: T.untyped, headers: T.untyped).returns(T.untyped) }
28
+ def get(path, query: nil, headers: {}); end
29
+
30
+ sig { params(path: String, query: T.untyped, headers: T.untyped).returns(T.untyped) }
31
+ def get_api(path, query: nil, headers: {}); end
32
+
33
+ sig { params(path: String, payload: T.untyped, headers: T.untyped).returns(T.untyped) }
34
+ def post_api(path, payload = nil, headers: {}); end
35
+
36
+ sig { params(path: String).returns(T.untyped) }
37
+ def put(path); end
38
+
39
+ sig { params(response: T.untyped).void }
40
+ def handle_error(response); end
41
+
42
+ private
43
+
44
+ sig { params(body: T.untyped).returns(T.untyped) }
45
+ def parse_json(body); end
46
+
47
+ sig { params(path: String).returns(String) }
48
+ def build_url(path); end
49
+
50
+ sig { params(path: String).returns(String) }
51
+ def build_sync_url(path); end
52
+
53
+ sig { params(path: String).returns(String) }
54
+ def build_api_url(path); end
55
+
56
+ sig { returns(T.untyped) }
57
+ def connection; end
58
+ end
59
+ end
data/rbi/fal/fal.rbi ADDED
@@ -0,0 +1,68 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Fal
5
+ class Error < StandardError; end
6
+ class UnauthorizedError < Error; end
7
+ class NotFoundError < Error; end
8
+ class ServerError < Error; end
9
+ class ConfigurationError < Error; end
10
+ class ForbiddenError < Error; end
11
+
12
+ class Configuration
13
+ # api_key
14
+ sig { returns(T.nilable(String)) }
15
+ def api_key; end
16
+
17
+ sig { params(value: T.nilable(String)).returns(T.nilable(String)) }
18
+ def api_key=(value); end
19
+
20
+ # queue_base
21
+ sig { returns(String) }
22
+ def queue_base; end
23
+
24
+ sig { params(value: String).returns(String) }
25
+ def queue_base=(value); end
26
+
27
+ # sync_base
28
+ sig { returns(String) }
29
+ def sync_base; end
30
+
31
+ sig { params(value: String).returns(String) }
32
+ def sync_base=(value); end
33
+
34
+ # api_base
35
+ sig { returns(String) }
36
+ def api_base; end
37
+
38
+ sig { params(value: String).returns(String) }
39
+ def api_base=(value); end
40
+
41
+ # request_timeout
42
+ sig { returns(Integer) }
43
+ def request_timeout; end
44
+
45
+ sig { params(value: Integer).returns(Integer) }
46
+ def request_timeout=(value); end
47
+
48
+ sig { void }
49
+ def initialize; end
50
+ end
51
+
52
+ class << self
53
+ sig { returns(Fal::Configuration) }
54
+ def configuration; end
55
+
56
+ sig { params(value: Fal::Configuration).returns(Fal::Configuration) }
57
+ def configuration=(value); end
58
+
59
+ sig { params(block: T.proc.params(arg0: Fal::Configuration).void).void }
60
+ def configure(&block); end
61
+
62
+ sig { returns(Fal::Client) }
63
+ def client; end
64
+
65
+ sig { params(obj: T.untyped).returns(T.untyped) }
66
+ def deep_symbolize_keys(obj); end
67
+ end
68
+ end
data/rbi/fal/model.rbi ADDED
@@ -0,0 +1,130 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Fal
5
+ class Model
6
+ MODELS_PATH = T.let(T.unsafe(nil), String)
7
+
8
+ sig { returns(String) }
9
+ def endpoint_id; end
10
+
11
+ sig { returns(T.nilable(String)) }
12
+ def display_name; end
13
+
14
+ sig { returns(T.nilable(String)) }
15
+ def category; end
16
+
17
+ sig { returns(T.nilable(String)) }
18
+ def description; end
19
+
20
+ sig { returns(T.nilable(String)) }
21
+ def status; end
22
+
23
+ sig { returns(T.nilable(T::Array[String])) }
24
+ def tags; end
25
+
26
+ sig { returns(T.nilable(String)) }
27
+ def updated_at; end
28
+
29
+ sig { returns(T.nilable(T::Boolean)) }
30
+ def is_favorited; end
31
+
32
+ sig { returns(T.nilable(String)) }
33
+ def thumbnail_url; end
34
+
35
+ sig { returns(T.nilable(String)) }
36
+ def thumbnail_animated_url; end
37
+
38
+ sig { returns(T.nilable(String)) }
39
+ def model_url; end
40
+
41
+ sig { returns(T.nilable(String)) }
42
+ def github_url; end
43
+
44
+ sig { returns(T.nilable(String)) }
45
+ def license_type; end
46
+
47
+ sig { returns(T.nilable(String)) }
48
+ def date; end
49
+
50
+ sig { returns(T.nilable(T::Hash[T.untyped, T.untyped])) }
51
+ def group; end
52
+
53
+ sig { returns(T.nilable(T::Boolean)) }
54
+ def highlighted; end
55
+
56
+ sig { returns(T.nilable(String)) }
57
+ def kind; end
58
+
59
+ sig { returns(T.nilable(T::Array[String])) }
60
+ def training_endpoint_ids; end
61
+
62
+ sig { returns(T.nilable(T::Array[String])) }
63
+ def inference_endpoint_ids; end
64
+
65
+ sig { returns(T.nilable(String)) }
66
+ def stream_url; end
67
+
68
+ sig { returns(T.nilable(Float)) }
69
+ def duration_estimate; end
70
+
71
+ sig { returns(T.nilable(T::Boolean)) }
72
+ def pinned; end
73
+
74
+ sig { returns(T.nilable(T::Hash[T.untyped, T.untyped])) }
75
+ def openapi; end
76
+
77
+ sig { params(attributes: T.untyped, client: Fal::Client).void }
78
+ def initialize(attributes, client: Fal.client); end
79
+
80
+ sig { returns(T.nilable(Fal::Price)) }
81
+ def price; end
82
+
83
+ sig { params(input: T.untyped, webhook_url: T.nilable(String)).returns(Fal::Request) }
84
+ def run(input:, webhook_url: nil); end
85
+
86
+ class << self
87
+ sig { params(endpoint_id: String, client: Fal::Client).returns(T.nilable(Fal::Model)) }
88
+ def find_by(endpoint_id:, client: Fal.client); end
89
+
90
+ sig do
91
+ params(
92
+ client: Fal::Client,
93
+ query: T.nilable(String),
94
+ category: T.nilable(String),
95
+ status: T.nilable(String),
96
+ expand: T.nilable(T.any(String, T::Array[String])),
97
+ block: T.proc.params(arg0: Fal::Model).void
98
+ ).void
99
+ end
100
+ def each(client: Fal.client, query: nil, category: nil, status: nil, expand: nil, &block); end
101
+
102
+ sig do
103
+ params(
104
+ client: Fal::Client,
105
+ query: T.nilable(String),
106
+ category: T.nilable(String),
107
+ status: T.nilable(String),
108
+ expand: T.nilable(T.any(String, T::Array[String]))
109
+ ).returns(T::Array[Fal::Model])
110
+ end
111
+ def all(client: Fal.client, query: nil, category: nil, status: nil, expand: nil); end
112
+
113
+ sig do
114
+ params(
115
+ query: T.nilable(String),
116
+ category: T.nilable(String),
117
+ status: T.nilable(String),
118
+ expand: T.nilable(T.any(String, T::Array[String])),
119
+ client: Fal::Client
120
+ ).returns(T::Array[Fal::Model])
121
+ end
122
+ def search(query: nil, category: nil, status: nil, expand: nil, client: Fal.client); end
123
+ end
124
+
125
+ private
126
+
127
+ sig { params(attributes: T.untyped).void }
128
+ def reset_attributes(attributes); end
129
+ end
130
+ end
data/rbi/fal/price.rbi ADDED
@@ -0,0 +1,49 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Fal
5
+ class Price
6
+ MODELS_PATH = T.let(T.unsafe(nil), String)
7
+ PRICING_PATH = T.let(T.unsafe(nil), String)
8
+
9
+ module Unit
10
+ IMAGES = T.let(T.unsafe(nil), String)
11
+ VIDEOS = T.let(T.unsafe(nil), String)
12
+ MEGAPIXELS = T.let(T.unsafe(nil), String)
13
+ GPU_SECONDS = T.let(T.unsafe(nil), String)
14
+ GPU_MINUTES = T.let(T.unsafe(nil), String)
15
+ GPU_HOURS = T.let(T.unsafe(nil), String)
16
+ end
17
+
18
+ sig { returns(String) }
19
+ def endpoint_id; end
20
+
21
+ sig { returns(Float) }
22
+ def unit_price; end
23
+
24
+ sig { returns(String) }
25
+ def unit; end
26
+
27
+ sig { returns(String) }
28
+ def currency; end
29
+
30
+ sig { params(attributes: T.untyped, client: Fal::Client).void }
31
+ def initialize(attributes, client: Fal.client); end
32
+
33
+ class << self
34
+ sig { params(endpoint_id: String, client: Fal::Client).returns(T.nilable(Fal::Price)) }
35
+ def find_by(endpoint_id:, client: Fal.client); end
36
+
37
+ sig { params(client: Fal::Client, block: T.proc.params(arg0: Fal::Price).void).void }
38
+ def each(client: Fal.client, &block); end
39
+
40
+ sig { params(client: Fal::Client).returns(T::Array[Fal::Price]) }
41
+ def all(client: Fal.client); end
42
+ end
43
+
44
+ private
45
+
46
+ sig { params(attributes: T.untyped).void }
47
+ def reset_attributes(attributes); end
48
+ end
49
+ end
@@ -0,0 +1,61 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Fal
5
+ class PriceEstimate
6
+ ESTIMATE_PATH = T.let(T.unsafe(nil), String)
7
+
8
+ module EstimateType
9
+ HISTORICAL_API_PRICE = T.let(T.unsafe(nil), String)
10
+ UNIT_PRICE = T.let(T.unsafe(nil), String)
11
+ end
12
+
13
+ class Endpoint
14
+ sig { returns(String) }
15
+ def endpoint_id; end
16
+
17
+ sig { returns(T.nilable(Integer)) }
18
+ def call_quantity; end
19
+
20
+ sig { returns(T.nilable(Float)) }
21
+ def unit_quantity; end
22
+
23
+ sig do
24
+ params(
25
+ endpoint_id: String,
26
+ call_quantity: T.nilable(Integer),
27
+ unit_quantity: T.nilable(Float)
28
+ ).void
29
+ end
30
+ def initialize(endpoint_id:, call_quantity: nil, unit_quantity: nil); end
31
+ end
32
+
33
+ sig { returns(String) }
34
+ def estimate_type; end
35
+
36
+ sig { returns(Float) }
37
+ def total_cost; end
38
+
39
+ sig { returns(String) }
40
+ def currency; end
41
+
42
+ sig { params(attributes: T.untyped, client: Fal::Client).void }
43
+ def initialize(attributes, client: Fal.client); end
44
+
45
+ class << self
46
+ sig do
47
+ params(
48
+ estimate_type: String,
49
+ endpoints: T::Array[T.any(Fal::PriceEstimate::Endpoint, T.untyped)],
50
+ client: Fal::Client
51
+ ).returns(Fal::PriceEstimate)
52
+ end
53
+ def create(estimate_type:, endpoints:, client: Fal.client); end
54
+ end
55
+
56
+ private
57
+
58
+ sig { params(attributes: T.untyped).void }
59
+ def reset_attributes(attributes); end
60
+ end
61
+ end