Class: Ask::TokenUsage::Wallet
- Inherits:
-
Object
- Object
- Ask::TokenUsage::Wallet
- Defined in:
- lib/ask/token_usage/wallet.rb
Overview
The wallet engine: all the business logic of token accounting, written once against the Store port. No database knowledge lives here.
wallet = Ask::TokenUsage.wallet_for("user:42")
wallet.grant!(1000, reason: :signup, expires_at: 7.days.from_now)
wallet.spend!(:chat_message, input: "hi", output: "yo") { LLM.chat(...) }
wallet.balance # => 998
Instance Attribute Summary collapse
-
#owner ⇒ Object
readonly
Returns the value of attribute owner.
Instance Method Summary collapse
-
#adjust_balance_to!(amount, reason:, metadata: {}) ⇒ Object
Set the balance to an exact amount, recording the delta as an adjustment entry.
-
#balance ⇒ Object
Current token balance.
-
#deduct!(amount, reason:, metadata: {}, &block) ⇒ Object
Deduct
amounttokens, raising InsufficientTokens when the balance is too low (unless negatives are allowed). -
#enough_for?(activity_name, params = {}) ⇒ Boolean
True when the balance covers the estimated cost of an activity.
-
#entries(**filters) ⇒ Object
Ledger entries, oldest first.
-
#estimate(activity_name, params = {}) ⇒ Object
Estimated token cost of running
activitywithparams. -
#grant!(amount, reason:, expires_at: nil, metadata: {}) ⇒ Object
Add
amounttokens. -
#has?(amount) ⇒ Boolean
True when the balance covers
amount. -
#initialize(owner:, store:, time: -> { Time.now }) ⇒ Wallet
constructor
A new instance of Wallet.
-
#spend!(activity_name, params = {}, &block) ⇒ Object
Spend the estimated token cost of
activitygivenparams. -
#spend_amount!(amount, reason:, metadata: {}, &block) ⇒ Object
Deduct an exact token cost (same as deduct!, but stays at wallet level for callers that prefer namespacing).
-
#used_since(time) ⇒ Object
Total tokens debited since
time(absolute value).
Constructor Details
#initialize(owner:, store:, time: -> { Time.now }) ⇒ Wallet
Returns a new instance of Wallet.
16 17 18 19 20 |
# File 'lib/ask/token_usage/wallet.rb', line 16 def initialize(owner:, store:, time: -> { Time.now }) @owner = owner @store = store @time = time end |
Instance Attribute Details
#owner ⇒ Object (readonly)
Returns the value of attribute owner.
14 15 16 |
# File 'lib/ask/token_usage/wallet.rb', line 14 def owner @owner end |
Instance Method Details
#adjust_balance_to!(amount, reason:, metadata: {}) ⇒ Object
Set the balance to an exact amount, recording the delta as an adjustment entry. Used by billing systems to reset monthly allowances (replace remaining balance) or fix incorrect totals.
wallet.adjust_balance_to!(10_000, reason: :monthly_reset)
104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
# File 'lib/ask/token_usage/wallet.rb', line 104 def adjust_balance_to!(amount, reason:, metadata: {}) amount = amount.to_i raise ArgumentError, "balance cannot be negative (got #{amount})" if amount.negative? && !Ask::TokenUsage.config.negatives @store.with_lock(owner) do previous = @store.balance(owner).to_i delta = amount - previous return nil if delta.zero? entry = record(:adjustment, delta, reason.to_s, amount, expires_at: nil, metadata: ) dispatch(:on_adjust, :adjustment, delta, reason, , entry, previous, amount) entry end end |
#balance ⇒ Object
Current token balance.
25 26 27 |
# File 'lib/ask/token_usage/wallet.rb', line 25 def balance @store.balance(owner).to_i end |
#deduct!(amount, reason:, metadata: {}, &block) ⇒ Object
Deduct amount tokens, raising InsufficientTokens when the balance
is too low (unless negatives are allowed). With a block, the block
runs first and tokens are charged only when it succeeds.
73 74 75 76 77 78 79 80 81 82 |
# File 'lib/ask/token_usage/wallet.rb', line 73 def deduct!(amount, reason:, metadata: {}, &block) amount = amount.to_i raise ArgumentError, "deduct amount must be positive (got #{amount})" unless amount.positive? if block spend_after_success(amount, reason, , &block) else charge(amount, reason, ) end end |
#enough_for?(activity_name, params = {}) ⇒ Boolean
True when the balance covers the estimated cost of an activity.
46 47 48 |
# File 'lib/ask/token_usage/wallet.rb', line 46 def enough_for?(activity_name, params = {}) has?(estimate(activity_name, params)) end |
#entries(**filters) ⇒ Object
Ledger entries, oldest first. Filter by :kind (grant/debit/...) and :since (Time).
31 32 33 |
# File 'lib/ask/token_usage/wallet.rb', line 31 def entries(**filters) @store.entries(owner, **filters) end |
#estimate(activity_name, params = {}) ⇒ Object
Estimated token cost of running activity with params.
41 42 43 |
# File 'lib/ask/token_usage/wallet.rb', line 41 def estimate(activity_name, params = {}) Ask::TokenUsage.estimate(activity_name, params) end |
#grant!(amount, reason:, expires_at: nil, metadata: {}) ⇒ Object
Add amount tokens. expires_at marks the grant as expiring.
Returns the written LedgerEntry.
60 61 62 63 64 65 66 67 68 |
# File 'lib/ask/token_usage/wallet.rb', line 60 def grant!(amount, reason:, expires_at: nil, metadata: {}) amount = amount.to_i raise ArgumentError, "grant amount must be positive (got #{amount})" unless amount.positive? mutate(:grant, amount, reason, expires_at: expires_at, metadata: ) do |current| previous = current [previous + amount, previous, amount] end end |
#has?(amount) ⇒ Boolean
True when the balance covers amount.
36 37 38 |
# File 'lib/ask/token_usage/wallet.rb', line 36 def has?(amount) balance >= amount.to_i end |
#spend!(activity_name, params = {}, &block) ⇒ Object
Spend the estimated token cost of activity given params. With a
block, charge only if the block succeeds — the standard wrapper for
LLM calls and render jobs.
87 88 89 90 |
# File 'lib/ask/token_usage/wallet.rb', line 87 def spend!(activity_name, params = {}, &block) amount = estimate(activity_name, params) deduct!(amount, reason: activity_name.to_s, metadata: params.dup, &block) end |
#spend_amount!(amount, reason:, metadata: {}, &block) ⇒ Object
Deduct an exact token cost (same as deduct!, but stays at wallet level for callers that prefer namespacing).
94 95 96 |
# File 'lib/ask/token_usage/wallet.rb', line 94 def spend_amount!(amount, reason:, metadata: {}, &block) deduct!(amount, reason: reason, metadata: , &block) end |
#used_since(time) ⇒ Object
Total tokens debited since time (absolute value). Useful for
monthly-usage reports.
52 53 54 |
# File 'lib/ask/token_usage/wallet.rb', line 52 def used_since(time) entries(kind: :debit, since: time).sum(&:amount).abs end |