ask-guests 0.1.0 → 0.2.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/CHANGELOG.md +17 -0
- data/README.md +24 -9
- data/lib/ask/guests/budget/category.rb +87 -0
- data/lib/ask/guests/budget.rb +71 -33
- data/lib/ask/guests/version.rb +1 -1
- data/lib/ask/guests.rb +5 -8
- data/lib/generators/ask/guests/install/templates/initializer.rb +9 -6
- metadata +2 -5
- data/lib/ask/guests/budget/policy.rb +0 -42
- data/lib/ask/guests/budget/token_quota.rb +0 -24
- data/lib/ask/guests/budget/turn_limit.rb +0 -24
- data/lib/ask/guests/budget/unlimited.rb +0 -16
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4714198941d2dca3569099d2c4f4091bd06adcfa00b74ecbea2d06b1cc9c6977
|
|
4
|
+
data.tar.gz: 5bcfc52ebd6b29d135bd923b3392221c9342ba1057d131349c4f7f0b0d2025e8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4c921fa2484da89d6aef5f00bdf4213bef01bab9df1c25dbc167f61e32672a3b68da54f9f58a47bf7c80b2ad6dcb583dfadb0a7b4a5b187507f209d65c6bdf1f
|
|
7
|
+
data.tar.gz: 5b8755b9083f995df2adbc6a0679336f343e20d27cb38713f6fab44c2ddc2ebe510878bb08829d8e54fd6cedddab8c88cd1b30377b17294dad23b184d0837740
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.2.0] — 2026-09-16
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- **Budgets are now named categories instead of policy classes.** Replace
|
|
8
|
+
`config.policies = [Budget::TurnLimit.new(per_day: 40)]` with
|
|
9
|
+
`config.categories = {turns: {limit: 40, per: :day}}`. Each category is a
|
|
10
|
+
named limit with a period (`:day`, `:week`, `:month`) and optionally its
|
|
11
|
+
own usage key (`parses: {limit: 20, per: :day, usage_key: :uploads}`).
|
|
12
|
+
Any usage key is meterable — turns, tokens, uploads, messages, or your
|
|
13
|
+
own.
|
|
14
|
+
- **New budget API:** `allowed?(subject, *categories)`, `remaining(subject,
|
|
15
|
+
:turns)`, `exceeded(subject)` (categories out of budget), `record!` with
|
|
16
|
+
keyword usage (`record!(session, turns: 1, tokens: 850)`).
|
|
17
|
+
- `Budget::TurnLimit`, `Budget::TokenQuota`, `Budget::Unlimited`, and
|
|
18
|
+
`Budget::Policy` are gone; an empty category list means unlimited.
|
|
19
|
+
|
|
3
20
|
## [0.1.0] — 2026-09-15
|
|
4
21
|
|
|
5
22
|
### Added
|
data/README.md
CHANGED
|
@@ -11,7 +11,7 @@ Four concepts, one gem:
|
|
|
11
11
|
|---|---|
|
|
12
12
|
| **Identity** | A random session id behind an HMAC-signed token. No fake `User` rows, nothing to enumerate without the secret. |
|
|
13
13
|
| **Ownership** | Hosts register claimable handlers; records owned by a guest carry its session id. |
|
|
14
|
-
| **Metering** |
|
|
14
|
+
| **Metering** | Named budget categories (`turns: 40/day`, `tokens: 10k/day`, `uploads: 5/day`, …) with automatic period rollover — bound anonymous AI spend. |
|
|
15
15
|
| **Claim** | On sign-up/sign-in, transfer all guest records in one transaction, exactly once. |
|
|
16
16
|
|
|
17
17
|
Plus retention: unclaimed sessions are swept after a configurable window.
|
|
@@ -46,7 +46,10 @@ the model and controller wiring.
|
|
|
46
46
|
Ask::Guests.configure do |config|
|
|
47
47
|
config.secret = ENV.fetch("GUEST_TOKEN_SECRET")
|
|
48
48
|
config.store = Ask::Guests::Stores::Memory.new
|
|
49
|
-
config.
|
|
49
|
+
config.categories = {
|
|
50
|
+
turns: {limit: 40, per: :day},
|
|
51
|
+
tokens: {limit: 10_000, per: :day}
|
|
52
|
+
}
|
|
50
53
|
config.retention = 14 * 24 * 60 * 60 # seconds
|
|
51
54
|
end
|
|
52
55
|
|
|
@@ -77,14 +80,26 @@ response = agent.run(prompt)
|
|
|
77
80
|
budget.record!(guest_session, turns: 1, tokens: response.input_tokens + response.output_tokens)
|
|
78
81
|
```
|
|
79
82
|
|
|
80
|
-
|
|
81
|
-
left, and counters roll over
|
|
83
|
+
Categories compose — a guest is allowed only while every category has
|
|
84
|
+
budget left in its period, and counters roll over with no scheduled work.
|
|
85
|
+
Periods are `:day`, `:week`, or `:month`; any usage key can be metered,
|
|
86
|
+
including your own (uploads, parses, messages):
|
|
82
87
|
|
|
83
88
|
```ruby
|
|
84
|
-
config.
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
89
|
+
config.categories = {
|
|
90
|
+
turns: {limit: 40, per: :day},
|
|
91
|
+
tokens: {limit: 10_000, per: :day},
|
|
92
|
+
uploads: {limit: 5, per: :day},
|
|
93
|
+
messages: {limit: 100, per: :month},
|
|
94
|
+
parses: {limit: 20, per: :day, usage_key: :uploads} # meter one key as another category
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
budget = Ask::Guests.budget
|
|
98
|
+
budget.allowed?(guest) # all categories
|
|
99
|
+
budget.allowed?(guest, :turns) # one category
|
|
100
|
+
budget.remaining(guest, :turns) # => 12
|
|
101
|
+
budget.exceeded(guest) # => [:tokens]
|
|
102
|
+
budget.record!(guest, turns: 1, tokens: 850)
|
|
88
103
|
```
|
|
89
104
|
|
|
90
105
|
### Claiming on sign-up
|
|
@@ -121,7 +136,7 @@ end
|
|
|
121
136
|
Ask::Guests.configure do |config|
|
|
122
137
|
config.secret = Rails.application.secret_key_base
|
|
123
138
|
config.store = Ask::Guests::ActiveRecord::SessionStore.new(model: GuestSession)
|
|
124
|
-
config.
|
|
139
|
+
config.categories = {turns: {limit: 40, per: :day}}
|
|
125
140
|
end
|
|
126
141
|
```
|
|
127
142
|
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ask
|
|
4
|
+
module Guests
|
|
5
|
+
class Budget
|
|
6
|
+
# A named budget line: a limit on one usage category per period.
|
|
7
|
+
#
|
|
8
|
+
# Category.new(:turns, limit: 40, per: :day)
|
|
9
|
+
# Category.new(:tokens, limit: 10_000, per: :day)
|
|
10
|
+
# Category.new(:uploads, limit: 5, per: :day)
|
|
11
|
+
# Category.new(:messages, limit: 100, per: :month)
|
|
12
|
+
#
|
|
13
|
+
# Usage is recorded by keyword and consumed by the category with the
|
|
14
|
+
# same name:
|
|
15
|
+
#
|
|
16
|
+
# budget.record!(session, turns: 1, tokens: 850, uploads: 1)
|
|
17
|
+
#
|
|
18
|
+
# A category may meter a different usage key with +usage_key+ (for
|
|
19
|
+
# example, counting "parses" while the call site reports uploads).
|
|
20
|
+
#
|
|
21
|
+
# Counters live on the subject and store a period bucket alongside
|
|
22
|
+
# the count ("day" resets at midnight UTC, "week" on ISO weeks,
|
|
23
|
+
# "month" on calendar months), so rollover is automatic and needs no
|
|
24
|
+
# scheduled work.
|
|
25
|
+
class Category
|
|
26
|
+
PERIODS = %i[day week month].freeze
|
|
27
|
+
|
|
28
|
+
attr_reader :name, :limit, :period, :usage_key
|
|
29
|
+
|
|
30
|
+
def initialize(name, limit:, per: :day, usage_key: nil)
|
|
31
|
+
@name = name.to_sym
|
|
32
|
+
@limit = limit.to_i
|
|
33
|
+
@period = per.to_sym
|
|
34
|
+
@usage_key = (usage_key || name).to_sym
|
|
35
|
+
|
|
36
|
+
unless PERIODS.include?(@period)
|
|
37
|
+
raise ArgumentError, "unknown period #{per.inspect} (expected one of #{PERIODS.inspect})"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @return [Integer] how much this usage costs the category
|
|
42
|
+
def amount_for(usage)
|
|
43
|
+
usage.fetch(@usage_key, 0).to_i
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# @return [Integer] remaining budget for the current period
|
|
47
|
+
def remaining(subject, time)
|
|
48
|
+
value = subject.counter_value(counter_key, bucket(time))
|
|
49
|
+
[@limit - value, 0].max
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def exceeded?(subject, time)
|
|
53
|
+
remaining(subject, time).zero?
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Records usage on the subject (without persisting — the budget
|
|
57
|
+
# persists through the store).
|
|
58
|
+
def record!(subject, usage, time)
|
|
59
|
+
amount = amount_for(usage)
|
|
60
|
+
return subject if amount.zero?
|
|
61
|
+
|
|
62
|
+
count = subject.counter_value(counter_key, bucket(time)) + amount
|
|
63
|
+
subject.set_counter!(counter_key, count, bucket(time))
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Counter name on the subject.
|
|
67
|
+
def counter_key
|
|
68
|
+
@name.to_s
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Period bucket key for +time+; a different bucket reads as zero.
|
|
72
|
+
def bucket(time)
|
|
73
|
+
utc = time.utc
|
|
74
|
+
case @period
|
|
75
|
+
when :day then utc.strftime("%Y-%m-%d")
|
|
76
|
+
when :week then utc.strftime("%G-W%V")
|
|
77
|
+
when :month then utc.strftime("%Y-%m")
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def to_h
|
|
82
|
+
{name: name, limit: limit, period: period, usage_key: usage_key}
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
data/lib/ask/guests/budget.rb
CHANGED
|
@@ -2,66 +2,104 @@
|
|
|
2
2
|
|
|
3
3
|
module Ask
|
|
4
4
|
module Guests
|
|
5
|
-
# Meters
|
|
6
|
-
# afterwards.
|
|
7
|
-
# while every
|
|
5
|
+
# Meters usage against named category budgets and records usage
|
|
6
|
+
# afterwards. Categories are checked collectively: a subject is allowed
|
|
7
|
+
# only while every category has budget remaining in its period.
|
|
8
8
|
#
|
|
9
9
|
# budget = Ask::Guests.budget
|
|
10
|
-
# budget.allowed?(session)
|
|
11
|
-
# budget.
|
|
10
|
+
# budget.allowed?(session) # => true/false
|
|
11
|
+
# budget.allowed?(session, :turns) # => one category
|
|
12
|
+
# budget.remaining(session, :turns) # => 12
|
|
13
|
+
# budget.exceeded(session) # => [:tokens]
|
|
12
14
|
# budget.record!(session, turns: 1, tokens: 850)
|
|
13
15
|
#
|
|
14
|
-
# Counters live on the
|
|
16
|
+
# Counters live on the subject and roll over per period, so limits
|
|
15
17
|
# reset without any scheduled work.
|
|
16
18
|
class Budget
|
|
17
|
-
attr_reader :
|
|
19
|
+
attr_reader :categories, :store
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
# @param categories [Array<Category>, Hash, nil] Category objects or
|
|
22
|
+
# a hash of +name => {limit:, per:, usage_key:}+. Empty/nil means
|
|
23
|
+
# unlimited.
|
|
24
|
+
def initialize(categories: nil, store: Guests.store, clock: Guests.configuration.clock)
|
|
25
|
+
@categories = normalize(categories)
|
|
21
26
|
@store = store
|
|
22
27
|
@clock = clock
|
|
23
28
|
end
|
|
24
29
|
|
|
25
|
-
# @
|
|
26
|
-
|
|
27
|
-
|
|
30
|
+
# @param names [Array<Symbol>] restrict the check to these categories
|
|
31
|
+
# @return [Boolean] true while every checked category has budget left
|
|
32
|
+
def allowed?(subject, *names)
|
|
33
|
+
return true if subject.nil?
|
|
28
34
|
|
|
29
|
-
|
|
35
|
+
selected(names).all? { |category| category.remaining(subject, now) > 0 }
|
|
30
36
|
end
|
|
31
37
|
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
def remaining(
|
|
35
|
-
return Float::INFINITY if
|
|
38
|
+
# Remaining budget for one category, or the minimum across all
|
|
39
|
+
# categories (Float::INFINITY when unlimited).
|
|
40
|
+
def remaining(subject, name = nil)
|
|
41
|
+
return Float::INFINITY if subject.nil?
|
|
36
42
|
|
|
37
|
-
|
|
43
|
+
if name
|
|
44
|
+
category(name).remaining(subject, now)
|
|
45
|
+
else
|
|
46
|
+
categories.map { |category| category.remaining(subject, now) }.min || Float::INFINITY
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# @return [Array<Symbol>] categories with no budget left this period
|
|
51
|
+
def exceeded(subject)
|
|
52
|
+
return [] if subject.nil?
|
|
53
|
+
|
|
54
|
+
categories.select { |category| category.exceeded?(subject, now) }.map(&:name)
|
|
38
55
|
end
|
|
39
56
|
|
|
40
|
-
# Records usage, persists the
|
|
41
|
-
|
|
42
|
-
|
|
57
|
+
# Records usage, persists the subject through the store, and returns
|
|
58
|
+
# it. Usage is keyed by category name (or the category's usage_key):
|
|
59
|
+
#
|
|
60
|
+
# budget.record!(session, turns: 1, tokens: 850, uploads: 2)
|
|
61
|
+
def record!(subject, **usage)
|
|
62
|
+
return subject if subject.nil?
|
|
43
63
|
|
|
44
|
-
|
|
45
|
-
store.update(
|
|
46
|
-
|
|
64
|
+
categories.each { |category| category.record!(subject, usage, now) }
|
|
65
|
+
store.update(subject)
|
|
66
|
+
subject
|
|
47
67
|
end
|
|
48
68
|
|
|
49
69
|
def unlimited?
|
|
50
|
-
|
|
70
|
+
categories.empty?
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# @return [Category]
|
|
74
|
+
def category(name)
|
|
75
|
+
categories.find { |category| category.name == name.to_sym } ||
|
|
76
|
+
raise(ArgumentError, "unknown budget category #{name.inspect}")
|
|
51
77
|
end
|
|
52
78
|
|
|
53
79
|
private
|
|
54
80
|
|
|
55
|
-
def
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
81
|
+
def selected(names)
|
|
82
|
+
return categories if names.nil? || names.empty?
|
|
83
|
+
|
|
84
|
+
names.map { |name| category(name) }
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def now
|
|
88
|
+
@clock.call
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def normalize(categories)
|
|
92
|
+
case categories
|
|
93
|
+
when nil then []
|
|
94
|
+
when Hash then categories.flat_map { |name, options| build(name, options) }
|
|
95
|
+
when Array then categories.flat_map { |entry| entry.is_a?(Hash) ? normalize(entry) : [entry] }
|
|
96
|
+
else [categories]
|
|
97
|
+
end.map { |entry| entry.is_a?(Category) ? entry : build(entry, {}) }
|
|
61
98
|
end
|
|
62
99
|
|
|
63
|
-
def
|
|
64
|
-
|
|
100
|
+
def build(name, options)
|
|
101
|
+
options = options.to_h.transform_keys(&:to_sym)
|
|
102
|
+
[Category.new(name, **options)]
|
|
65
103
|
end
|
|
66
104
|
end
|
|
67
105
|
end
|
data/lib/ask/guests/version.rb
CHANGED
data/lib/ask/guests.rb
CHANGED
|
@@ -7,10 +7,7 @@ require_relative "guests/session"
|
|
|
7
7
|
require_relative "guests/stores/base"
|
|
8
8
|
require_relative "guests/stores/memory"
|
|
9
9
|
require_relative "guests/budget"
|
|
10
|
-
require_relative "guests/budget/
|
|
11
|
-
require_relative "guests/budget/unlimited"
|
|
12
|
-
require_relative "guests/budget/turn_limit"
|
|
13
|
-
require_relative "guests/budget/token_quota"
|
|
10
|
+
require_relative "guests/budget/category"
|
|
14
11
|
require_relative "guests/claim"
|
|
15
12
|
require_relative "guests/sweep"
|
|
16
13
|
|
|
@@ -79,7 +76,7 @@ module Ask
|
|
|
79
76
|
# so configuration changes (and test resets) always take effect.
|
|
80
77
|
def budget
|
|
81
78
|
Budget.new(
|
|
82
|
-
|
|
79
|
+
categories: configuration.categories,
|
|
83
80
|
store: configuration.store,
|
|
84
81
|
clock: configuration.clock
|
|
85
82
|
)
|
|
@@ -121,8 +118,8 @@ module Ask
|
|
|
121
118
|
attr_accessor :secret
|
|
122
119
|
# Persistence adapter (see Stores::Base). Defaults to in-memory.
|
|
123
120
|
attr_accessor :store
|
|
124
|
-
#
|
|
125
|
-
attr_accessor :
|
|
121
|
+
# Budget categories (see Budget::Category): named limits per period.
|
|
122
|
+
attr_accessor :categories
|
|
126
123
|
# Unclaimed sessions older than this many seconds are swept.
|
|
127
124
|
attr_accessor :retention
|
|
128
125
|
# Injectable clock returning a Time (used for daily counter rollover
|
|
@@ -141,7 +138,7 @@ module Ask
|
|
|
141
138
|
def initialize
|
|
142
139
|
@secret = nil
|
|
143
140
|
@store = Stores::Memory.new
|
|
144
|
-
@
|
|
141
|
+
@categories = []
|
|
145
142
|
@retention = 14 * 24 * 60 * 60
|
|
146
143
|
@clock = -> { Time.now.utc }
|
|
147
144
|
@cookie_name = "guest_session_token"
|
|
@@ -19,12 +19,15 @@ Ask::Guests.configure do |config|
|
|
|
19
19
|
# end
|
|
20
20
|
config.store = Ask::Guests::ActiveRecord::SessionStore.new(model: GuestSession)
|
|
21
21
|
|
|
22
|
-
# Usage caps for anonymous visitors
|
|
23
|
-
# signed-in request never touches the guest budget.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
# Usage caps for anonymous visitors, one entry per category. Signing up
|
|
23
|
+
# lifts them because a signed-in request never touches the guest budget.
|
|
24
|
+
# Periods are :day, :week, or :month; counters roll over automatically.
|
|
25
|
+
config.categories = {
|
|
26
|
+
turns: {limit: 40, per: :day}
|
|
27
|
+
# tokens: {limit: 10_000, per: :day},
|
|
28
|
+
# uploads: {limit: 5, per: :day},
|
|
29
|
+
# messages: {limit: 100, per: :month}
|
|
30
|
+
}
|
|
28
31
|
|
|
29
32
|
# Unclaimed sessions inactive for this long are swept (seconds).
|
|
30
33
|
config.retention = 14 * 24 * 60 * 60
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ask-guests
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kaka Ruto
|
|
@@ -114,10 +114,7 @@ files:
|
|
|
114
114
|
- lib/ask/guests/active_record/session_base.rb
|
|
115
115
|
- lib/ask/guests/active_record/session_store.rb
|
|
116
116
|
- lib/ask/guests/budget.rb
|
|
117
|
-
- lib/ask/guests/budget/
|
|
118
|
-
- lib/ask/guests/budget/token_quota.rb
|
|
119
|
-
- lib/ask/guests/budget/turn_limit.rb
|
|
120
|
-
- lib/ask/guests/budget/unlimited.rb
|
|
117
|
+
- lib/ask/guests/budget/category.rb
|
|
121
118
|
- lib/ask/guests/claim.rb
|
|
122
119
|
- lib/ask/guests/errors.rb
|
|
123
120
|
- lib/ask/guests/rails/claim_on_authentication.rb
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Ask
|
|
4
|
-
module Guests
|
|
5
|
-
class Budget
|
|
6
|
-
# Base class for usage policies. A policy owns one counter key (for
|
|
7
|
-
# example "turns" or "tokens") and a daily limit for it.
|
|
8
|
-
#
|
|
9
|
-
# Subclasses implement +counter_key+, +limit+, and +amount_for+.
|
|
10
|
-
class Policy
|
|
11
|
-
# @return [String] counter name on the session
|
|
12
|
-
def counter_key
|
|
13
|
-
raise NotImplementedError
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
# @return [Integer, Float::INFINITY] daily limit
|
|
17
|
-
def limit
|
|
18
|
-
raise NotImplementedError
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
# @return [Integer] how much this usage costs in counter units
|
|
22
|
-
def amount_for(turns:, tokens:)
|
|
23
|
-
raise NotImplementedError
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
# @return [Integer, Float] remaining budget for today
|
|
27
|
-
def remaining(session, today)
|
|
28
|
-
value = session.counter_value(counter_key, today)
|
|
29
|
-
[limit - value, 0].max
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def record!(session, turns:, tokens:, today:)
|
|
33
|
-
amount = amount_for(turns: turns, tokens: tokens)
|
|
34
|
-
return session if amount.to_i.zero?
|
|
35
|
-
|
|
36
|
-
count = session.counter_value(counter_key, today) + amount.to_i
|
|
37
|
-
session.set_counter!(counter_key, count, today)
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
end
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Ask
|
|
4
|
-
module Guests
|
|
5
|
-
class Budget
|
|
6
|
-
# Limits tokens per day, for apps that meter actual consumption
|
|
7
|
-
# (input + output tokens) instead of turns.
|
|
8
|
-
#
|
|
9
|
-
# Budget::TokenQuota.new(per_day: 10_000)
|
|
10
|
-
class TokenQuota < Policy
|
|
11
|
-
attr_reader :per_day
|
|
12
|
-
|
|
13
|
-
def initialize(per_day:)
|
|
14
|
-
super()
|
|
15
|
-
@per_day = per_day.to_i
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def counter_key = "tokens"
|
|
19
|
-
def limit = per_day
|
|
20
|
-
def amount_for(turns:, tokens:) = tokens
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
end
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Ask
|
|
4
|
-
module Guests
|
|
5
|
-
class Budget
|
|
6
|
-
# Limits agent turns per day. One "turn" is one agent run — the unit
|
|
7
|
-
# Kawibot meters.
|
|
8
|
-
#
|
|
9
|
-
# Budget::TurnLimit.new(per_day: 40)
|
|
10
|
-
class TurnLimit < Policy
|
|
11
|
-
attr_reader :per_day
|
|
12
|
-
|
|
13
|
-
def initialize(per_day:)
|
|
14
|
-
super()
|
|
15
|
-
@per_day = per_day.to_i
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def counter_key = "turns"
|
|
19
|
-
def limit = per_day
|
|
20
|
-
def amount_for(turns:, tokens:) = turns
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
end
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Ask
|
|
4
|
-
module Guests
|
|
5
|
-
class Budget
|
|
6
|
-
# No limits — the default policy. Useful for development and for apps
|
|
7
|
-
# that meter elsewhere.
|
|
8
|
-
class Unlimited < Policy
|
|
9
|
-
def counter_key = "unlimited"
|
|
10
|
-
def limit = Float::INFINITY
|
|
11
|
-
def amount_for(turns:, tokens:) = 0
|
|
12
|
-
def remaining(session, today) = Float::INFINITY
|
|
13
|
-
end
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
-
end
|