smith-agents 0.12.3 → 0.12.5
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/CHANGELOG.md +44 -0
- data/README.md +1 -1
- data/docs/AGENTS.md +11 -1
- data/docs/ERRORS_AND_RETRIES.md +18 -0
- data/lib/smith/agent/attempt_chat.rb +44 -0
- data/lib/smith/agent/provider_call_timing.rb +2 -0
- data/lib/smith/agent/provider_completion.rb +20 -6
- data/lib/smith/agent/provider_streaming.rb +4 -0
- data/lib/smith/billing_refusals.rb +68 -0
- data/lib/smith/providers/anthropic/batches.rb +9 -3
- data/lib/smith/version.rb +1 -1
- data/lib/smith.rb +1 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 663a12c4a26041ded1da57a8bd899213cc28d330eae1d42a9dc3e4fdbda4dd87
|
|
4
|
+
data.tar.gz: c997927bbcea376c0db21a92aacdc21e510ac3c8e664d4ebb1eb0f8282cd2b32
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 27cc5f8437f8325afde26cd1816f8ca007a1c0f43b0ea02cc14cdcd49c17504cc5b2a22934c8b26ddcac132f5d2dc28f40e9e6fcde8976e01b3d4438523235b2
|
|
7
|
+
data.tar.gz: d621799d104e1b3b1ba97f14974afb6fa01c99cd179f8817471a55148d0587c9b2e333d47a0ab016855e48b8ab7739badb5db72c22e6730294f23da4947bc96d
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,50 @@ All notable changes to Smith are documented in this file.
|
|
|
4
4
|
|
|
5
5
|
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Smith is pre-1.0 and under active development; expect occasional contract tightening between minor versions until 1.0.
|
|
6
6
|
|
|
7
|
+
## [0.12.5] - 2026-10-13
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- A streamed step's requests are each sent once: its chat takes a copy of
|
|
12
|
+
the agent's config (its context, or RubyLLM's) with RubyLLM's HTTP retries
|
|
13
|
+
(`max_retries`) off, its model resolved again as RubyLLM resolves a chat
|
|
14
|
+
built with a context (the same id and provider, and the agent's own
|
|
15
|
+
`assume_model_exists`, so a model RubyLLM lists keeps its output limit and
|
|
16
|
+
thinking options). Retrying a stream read the next response's error into
|
|
17
|
+
the buffer the first had filled, so the provider's words were lost: a
|
|
18
|
+
refusal for want of credit reached Smith as a bare "Invalid request"
|
|
19
|
+
(`RubyLLM::BadRequestError`) and was never classified as
|
|
20
|
+
`RubyLLM::PaymentRequiredError`, and a spend-cap 429 as a plain rate limit.
|
|
21
|
+
A stream that failed part way was sent again, its text streamed twice.
|
|
22
|
+
Every provider failure of a streamed request, a transient one at connect
|
|
23
|
+
time included, now goes straight to the step's fallbacks and retries, so
|
|
24
|
+
a streamed step that should ride out an outage declares them. Calls that
|
|
25
|
+
do not stream, and requests OpenAI's Responses route sends whole, keep
|
|
26
|
+
RubyLLM's retries. The global config and the agent's are never changed.
|
|
27
|
+
|
|
28
|
+
## [0.12.4] - 2026-10-13
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
|
|
32
|
+
- A provider's refusal for want of credit or past a spending limit is
|
|
33
|
+
`RubyLLM::PaymentRequiredError` whatever status it came with
|
|
34
|
+
(`Smith::BillingRefusals`), with the error RubyLLM raised as its cause. A
|
|
35
|
+
refusal is told by what the provider says it is: Anthropic's `billing_error`,
|
|
36
|
+
its tier's monthly spend cap (a 429 whose `error_code` is
|
|
37
|
+
`enforced_spend_limit_reached`), its 400s for spent credit ("Your credit
|
|
38
|
+
balance is too low") and for a spend limit the account set ("You have
|
|
39
|
+
reached your specified API usage limits"), and OpenAI's
|
|
40
|
+
`insufficient_quota`. A streamed request's refusal, which RubyLLM raises as
|
|
41
|
+
a `RubyLLM::ServerError`, is one too, and so is one met by
|
|
42
|
+
`Smith::Providers::Anthropic::Batches#submit`. It is the provider account's
|
|
43
|
+
failure, so another provider in the fallback chain is tried, and a host can
|
|
44
|
+
name `RubyLLM::PaymentRequiredError` in `interrupting_errors` to have a step
|
|
45
|
+
wait for credit instead of failing. Any other error is unchanged: a rate
|
|
46
|
+
limit that mentions a quota (Gemini's) stays a rate limit.
|
|
47
|
+
- A model candidate that met an interrupting error outlasts how a later
|
|
48
|
+
fallback fails: the step is cut short, as it would have been with no
|
|
49
|
+
fallback, instead of failing on the fallback's error.
|
|
50
|
+
|
|
7
51
|
## [0.12.3] - 2026-10-12
|
|
8
52
|
|
|
9
53
|
### Changed
|
data/README.md
CHANGED
data/docs/AGENTS.md
CHANGED
|
@@ -155,7 +155,17 @@ reason and message id, with `mode: :stream`), and a replayed step streams again
|
|
|
155
155
|
(unless a response memo kept its reply: then its sink gets the text at once).
|
|
156
156
|
Graph inspection shows the stream, and runtime readiness reports a missing sink
|
|
157
157
|
(`:stream_sink_missing`) and an agent whose calls go to the Responses API
|
|
158
|
-
(`:stream_whole_replies`).
|
|
158
|
+
(`:stream_whole_replies`). A streamed step's requests are each sent once:
|
|
159
|
+
its chat takes a copy of the agent's config with RubyLLM's HTTP retries
|
|
160
|
+
(`max_retries`) off and its model resolved again as RubyLLM resolves a chat
|
|
161
|
+
built with a context (a model RubyLLM lists keeps its limits), since a
|
|
162
|
+
retried stream loses the first response's error body (a refusal for want of
|
|
163
|
+
credit would reach Smith as a bare "Invalid request") or streams its text
|
|
164
|
+
again. Every provider failure of a streamed request, a transient one at
|
|
165
|
+
connect time included, takes the step's fallbacks and retries directly, so
|
|
166
|
+
declare them (`fallback_models`, `retry_on Smith::AgentError`) on a streamed
|
|
167
|
+
step that should ride out an outage. A request OpenAI's Responses route sends
|
|
168
|
+
whole keeps the retries.
|
|
159
169
|
|
|
160
170
|
## Batch mode
|
|
161
171
|
|
data/docs/ERRORS_AND_RETRIES.md
CHANGED
|
@@ -99,6 +99,24 @@ next advanced; a strict run is found under way on restore
|
|
|
99
99
|
(`Smith::StepInProgressOnRestore`), as after a crash. The instance that
|
|
100
100
|
raised is not advanced again: restore the run.
|
|
101
101
|
|
|
102
|
+
A provider's refusal for want of credit or past a spending limit is
|
|
103
|
+
`RubyLLM::PaymentRequiredError` whatever status it came with, its cause the
|
|
104
|
+
error RubyLLM raised. Smith tells one by what the provider says it is:
|
|
105
|
+
Anthropic's `billing_error` (402), its tier's monthly spend cap (a 429 whose
|
|
106
|
+
`error.details.error_code` is `enforced_spend_limit_reached`), its 400s for
|
|
107
|
+
spent credit ("Your credit balance is too low") and for a spend limit the
|
|
108
|
+
account set ("You have reached your specified API usage limits"), and
|
|
109
|
+
OpenAI's `insufficient_quota` (429). RubyLLM raises a streamed request's
|
|
110
|
+
refusal as a `RubyLLM::ServerError`; it is one all the same. It is the
|
|
111
|
+
provider account's, so another provider in the fallback chain is tried;
|
|
112
|
+
naming `RubyLLM::PaymentRequiredError` makes a step that meets it wait for
|
|
113
|
+
credit instead of failing, even when a fallback then fails some other way.
|
|
114
|
+
`Smith::Providers::Anthropic::Batches#submit` raises it too. Any other error
|
|
115
|
+
stays as it is: a rate limit whose message mentions a quota (Gemini's) is a
|
|
116
|
+
rate limit. RubyLLM's own HTTP retries (`max_retries`) may repeat a 429 of a
|
|
117
|
+
call that does not stream before Smith sees it (a refused request is not
|
|
118
|
+
charged); a streamed call is sent once, so its refusal reaches Smith whole.
|
|
119
|
+
|
|
102
120
|
Name only errors that mean your own systems failed. A provider's own failure
|
|
103
121
|
(`RubyLLM::Error`, or its HTTP client's `Faraday::Error`) is the provider's
|
|
104
122
|
whatever it was raised from, so naming a low-level class such as
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Smith
|
|
4
|
+
class Agent
|
|
5
|
+
module AttemptChat
|
|
6
|
+
private
|
|
7
|
+
|
|
8
|
+
# An attempt's chat. A streamed step's sends each request it streams
|
|
9
|
+
# over the wire once: RubyLLM's HTTP retries would send it again on an
|
|
10
|
+
# error, its error body already read into the first request's buffer
|
|
11
|
+
# (so a refusal for want of credit reaches Smith as a bare "Invalid
|
|
12
|
+
# request", never classified) or its text already streamed (the
|
|
13
|
+
# retry's text would join it). Smith's fallbacks and the step's
|
|
14
|
+
# retries take their place. Its chat takes a copy of the agent's config
|
|
15
|
+
# (its context, or RubyLLM's) without the retries, and resolves its
|
|
16
|
+
# model again as RubyLLM builds a chat with a context (the same id and
|
|
17
|
+
# provider, and the agent's own assume_model_exists, so a model RubyLLM
|
|
18
|
+
# knows keeps its limits), before any hold attaches to its provider. A
|
|
19
|
+
# request OpenAI's Responses route sends answers whole, unstreamed,
|
|
20
|
+
# and keeps the retries.
|
|
21
|
+
def attempt_chat(agent_class, model_reference)
|
|
22
|
+
chat = agent_class.chat(**model_reference.chat_options, **bridge_workflow_inputs(agent_class))
|
|
23
|
+
return chat unless @step_stream && streams_over_the_wire?(chat)
|
|
24
|
+
|
|
25
|
+
sent_once(chat, agent_class)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def sent_once(chat, agent_class)
|
|
29
|
+
model = chat.model
|
|
30
|
+
config = (agent_class.context&.config || RubyLLM.config).dup
|
|
31
|
+
config.max_retries = 0
|
|
32
|
+
chat.with_context(RubyLLM::Context.new(config))
|
|
33
|
+
chat.with_model(model.id, provider: model.provider,
|
|
34
|
+
assume_exists: agent_class.chat_kwargs.fetch(:assume_model_exists, false))
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def streams_over_the_wire?(chat)
|
|
38
|
+
return false unless chat.respond_to?(:with_context) && chat.respond_to?(:with_model)
|
|
39
|
+
|
|
40
|
+
(chat.params[:openai_api_mode] || chat.params["openai_api_mode"]).to_s != "responses"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -123,7 +123,9 @@ module Smith
|
|
|
123
123
|
# have their join target; the error then propagates unchanged. A
|
|
124
124
|
# provider failure's own reported usage is accounted beside the
|
|
125
125
|
# completed prefix, so its trace totals both.
|
|
126
|
+
# A billing refusal is recorded as the provider account's (BillingRefusals).
|
|
126
127
|
def failed_provider_attempt(error, observed_reference, timer, facts, prefix_usages)
|
|
128
|
+
error = BillingRefusals.classified(error)
|
|
127
129
|
aborted = !provider_failure?(error)
|
|
128
130
|
usages = aborted ? prefix_usages : [*prefix_usages, ProviderUsage.from_message(error)].compact
|
|
129
131
|
attempt = ProviderAttempt.failure(
|
|
@@ -34,18 +34,16 @@ module Smith
|
|
|
34
34
|
|
|
35
35
|
def attempt_candidates(agent_class, prepared_input, chain, shape)
|
|
36
36
|
candidates = ProviderCandidateSequence.new(chain)
|
|
37
|
+
interrupted = nil
|
|
37
38
|
candidates.each do |model_reference, index|
|
|
38
39
|
check_deadline! if index.positive?
|
|
39
40
|
withdraw_streamed(index.positive? ? :fallback : :retry)
|
|
40
41
|
attempt = attempt_model(agent_class, prepared_input, model_reference, shape:, attempt_index: index)
|
|
41
42
|
return attempt if attempt.success?
|
|
42
43
|
|
|
44
|
+
interrupted ||= attempt if Interruptions.find(attempt.error)
|
|
43
45
|
candidates.suppress(account_failed_provider(attempt, model_reference))
|
|
44
|
-
|
|
45
|
-
attempt.error, attempt.model_reference, agent_class,
|
|
46
|
-
fallback_available: candidates.fallback_available?,
|
|
47
|
-
attempt_id: attempt.attempt_id
|
|
48
|
-
)
|
|
46
|
+
fail_over!(attempt, agent_class, candidates, interrupted)
|
|
49
47
|
end
|
|
50
48
|
|
|
51
49
|
# Fail closed: an empty candidate chain (agent without a model)
|
|
@@ -55,6 +53,22 @@ module Smith
|
|
|
55
53
|
"no executable model candidate for #{agent_class}; declare a model or fallback_models"
|
|
56
54
|
end
|
|
57
55
|
|
|
56
|
+
# Returns when another candidate is left to try. A candidate that met
|
|
57
|
+
# an interrupting error (an account out of credit) outlasts how a later
|
|
58
|
+
# one fails: the step was cut short, not failed, and is taken again
|
|
59
|
+
# from its first candidate once the host can do its part.
|
|
60
|
+
def fail_over!(attempt, agent_class, candidates, interrupted)
|
|
61
|
+
handle_provider_failure!(
|
|
62
|
+
attempt.error, attempt.model_reference, agent_class,
|
|
63
|
+
fallback_available: candidates.fallback_available?,
|
|
64
|
+
attempt_id: attempt.attempt_id
|
|
65
|
+
)
|
|
66
|
+
rescue StandardError => e
|
|
67
|
+
raise if interrupted.nil? || Interruptions.find(e)
|
|
68
|
+
|
|
69
|
+
raise terminal_provider_error(interrupted.error, interrupted.model_reference), cause: interrupted.error
|
|
70
|
+
end
|
|
71
|
+
|
|
58
72
|
# Suppression keys on the provider that actually rejected the
|
|
59
73
|
# account; when the chat is unobservable the attempted reference's
|
|
60
74
|
# declared provider is the best available attribution, so a dead
|
|
@@ -133,7 +147,7 @@ module Smith
|
|
|
133
147
|
# rubocop:enable Metrics/AbcSize
|
|
134
148
|
|
|
135
149
|
def prepared_attempt_chat(agent_class, prepared_input, model_reference, shape)
|
|
136
|
-
chat = agent_class
|
|
150
|
+
chat = attempt_chat(agent_class, model_reference)
|
|
137
151
|
add_prepared_input(chat, prepared_input, observed_model_reference(chat, fallback: model_reference).provider)
|
|
138
152
|
continue_paused_loop(chat)
|
|
139
153
|
shaped_chat(chat, model_reference, shape)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "attempt_chat"
|
|
4
|
+
|
|
3
5
|
module Smith
|
|
4
6
|
class Agent
|
|
5
7
|
# The provider calls of a streamed step (Smith::Streaming): before a
|
|
@@ -8,6 +10,8 @@ module Smith
|
|
|
8
10
|
# reason is read from the stream (Providers::StreamFacts), as a
|
|
9
11
|
# streamed reply keeps no raw body.
|
|
10
12
|
module ProviderStreaming
|
|
13
|
+
include AttemptChat
|
|
14
|
+
|
|
11
15
|
# Room for what a pause adds besides its messages (its retry, the
|
|
12
16
|
# attempt's usage).
|
|
13
17
|
PAUSE_MARGIN = 4096
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "ruby_llm"
|
|
5
|
+
|
|
6
|
+
module Smith
|
|
7
|
+
# A provider's refusal for want of credit or past a spending limit, whatever
|
|
8
|
+
# status it came with. Smith takes each as RubyLLM::PaymentRequiredError,
|
|
9
|
+
# the error RubyLLM raises for a 402 (its cause the error RubyLLM raised),
|
|
10
|
+
# so the refusal is the provider account's (another provider in the
|
|
11
|
+
# fallback chain is tried) and a host can name that one class in
|
|
12
|
+
# interrupting_errors to have a step wait for credit instead of failing.
|
|
13
|
+
#
|
|
14
|
+
# A refusal is told by what the provider says it is, never by its status
|
|
15
|
+
# alone: Anthropic's billing_error (402), its tier's monthly spend cap (429,
|
|
16
|
+
# error_code enforced_spend_limit_reached), its spent credit and a spend
|
|
17
|
+
# limit the account set (400s known only by how their message begins), and
|
|
18
|
+
# OpenAI's insufficient_quota (429). A streamed request's refusal arrives
|
|
19
|
+
# as a RubyLLM::ServerError, since RubyLLM takes every streamed error as a
|
|
20
|
+
# 500. Any other error stays as it is: a rate limit that mentions a quota
|
|
21
|
+
# (Gemini's) is a rate limit.
|
|
22
|
+
module BillingRefusals
|
|
23
|
+
CLASSES = [RubyLLM::BadRequestError, RubyLLM::RateLimitError, RubyLLM::ServerError].freeze
|
|
24
|
+
TYPES = %w[billing_error insufficient_quota].freeze
|
|
25
|
+
CODES = %w[enforced_spend_limit_reached].freeze
|
|
26
|
+
MESSAGES = [
|
|
27
|
+
/\AYour credit balance is too low/i,
|
|
28
|
+
/\AYou have reached your specified (?:workspace )?API usage limits/i
|
|
29
|
+
].freeze
|
|
30
|
+
private_constant :CLASSES, :TYPES, :CODES, :MESSAGES
|
|
31
|
+
|
|
32
|
+
module_function
|
|
33
|
+
|
|
34
|
+
# The error as Smith takes it: a billing refusal as a
|
|
35
|
+
# RubyLLM::PaymentRequiredError, anything else unchanged.
|
|
36
|
+
def classified(error)
|
|
37
|
+
return error unless refusal?(error)
|
|
38
|
+
|
|
39
|
+
begin
|
|
40
|
+
raise RubyLLM::PaymentRequiredError.new(error.response, error.message), cause: error
|
|
41
|
+
rescue RubyLLM::PaymentRequiredError => e
|
|
42
|
+
e
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def refusal?(error)
|
|
47
|
+
return false unless CLASSES.any? { error.is_a?(_1) }
|
|
48
|
+
|
|
49
|
+
said = provider_error(error)
|
|
50
|
+
details = said["details"].is_a?(Hash) ? said["details"] : {}
|
|
51
|
+
|
|
52
|
+
TYPES.include?(said["type"]) ||
|
|
53
|
+
CODES.include?(details["error_code"]) ||
|
|
54
|
+
MESSAGES.any? { error.message.to_s.match?(_1) }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# The provider's own error object in the response body, or {} when the
|
|
58
|
+
# body carries none.
|
|
59
|
+
def provider_error(error)
|
|
60
|
+
body = error.response.respond_to?(:body) ? error.response.body : nil
|
|
61
|
+
body = JSON.parse(body) if body.is_a?(String)
|
|
62
|
+
said = body["error"] if body.is_a?(Hash)
|
|
63
|
+
said.is_a?(Hash) ? said : {}
|
|
64
|
+
rescue JSON::ParserError
|
|
65
|
+
{}
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -34,13 +34,13 @@ module Smith
|
|
|
34
34
|
@connection = RubyLLM::Providers::Anthropic.new(config).connection
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
-
# Submits the requests; returns the batch id.
|
|
37
|
+
# Submits the requests; returns the batch id. A refusal for want of
|
|
38
|
+
# credit raises RubyLLM::PaymentRequiredError (BillingRefusals).
|
|
38
39
|
def submit(requests)
|
|
39
40
|
entries = Array(requests).map { |entry| entry.transform_keys(&:to_sym) }
|
|
40
41
|
body = { requests: entries.map { |entry| { custom_id: entry.fetch(:custom_id), params: params(entry) } } }
|
|
41
42
|
check_batch!(entries, body)
|
|
42
|
-
|
|
43
|
-
@connection.post(PATH, body) { |request| add_headers(request.headers, headers) }.body.fetch("id")
|
|
43
|
+
create(body, shared_headers(entries))
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
# The batch's processing status ("in_progress", "canceling" or
|
|
@@ -67,6 +67,12 @@ module Smith
|
|
|
67
67
|
|
|
68
68
|
private
|
|
69
69
|
|
|
70
|
+
def create(body, headers)
|
|
71
|
+
@connection.post(PATH, body) { |request| add_headers(request.headers, headers) }.body.fetch("id")
|
|
72
|
+
rescue RubyLLM::Error => e
|
|
73
|
+
raise BillingRefusals.classified(e)
|
|
74
|
+
end
|
|
75
|
+
|
|
70
76
|
def params(entry)
|
|
71
77
|
request = entry.fetch(:request).transform_keys(&:to_s)
|
|
72
78
|
raise ArgumentError, "a request for #{request["provider"]} cannot go in an Anthropic batch" unless
|
data/lib/smith/version.rb
CHANGED
data/lib/smith.rb
CHANGED
|
@@ -212,6 +212,7 @@ require_relative "smith/tool_failure_notification_failed"
|
|
|
212
212
|
require_relative "smith/persistence_payload_conflict"
|
|
213
213
|
require_relative "smith/exponential_backoff"
|
|
214
214
|
require_relative "smith/interruptions"
|
|
215
|
+
require_relative "smith/billing_refusals"
|
|
215
216
|
|
|
216
217
|
# Event system (depends on Types)
|
|
217
218
|
require_relative "smith/event"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: smith-agents
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.12.
|
|
4
|
+
version: 0.12.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Samuel Ralak
|
|
@@ -161,6 +161,7 @@ files:
|
|
|
161
161
|
- lib/generators/smith/install/templates/workflow_state.rb.tt
|
|
162
162
|
- lib/smith.rb
|
|
163
163
|
- lib/smith/agent.rb
|
|
164
|
+
- lib/smith/agent/attempt_chat.rb
|
|
164
165
|
- lib/smith/agent/chat_construction.rb
|
|
165
166
|
- lib/smith/agent/completion.rb
|
|
166
167
|
- lib/smith/agent/completion_usage_recording.rb
|
|
@@ -203,6 +204,7 @@ files:
|
|
|
203
204
|
- lib/smith/attribution.rb
|
|
204
205
|
- lib/smith/batch.rb
|
|
205
206
|
- lib/smith/batch/message_shape.rb
|
|
207
|
+
- lib/smith/billing_refusals.rb
|
|
206
208
|
- lib/smith/budget.rb
|
|
207
209
|
- lib/smith/budget/amount.rb
|
|
208
210
|
- lib/smith/budget/amount_contract.rb
|