ask-tokens-rails 0.2.2 → 0.4.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 +78 -0
- data/README.md +24 -9
- data/lib/ask/tokens/rails/concerns/has_token_wallet.rb +8 -0
- data/lib/ask/tokens/rails/configuration.rb +38 -0
- data/lib/ask/tokens/rails/jobs/sweep_expired_tokens_job.rb +85 -43
- data/lib/ask/tokens/rails/models/token_transaction.rb +14 -1
- data/lib/ask/tokens/rails/models/token_wallet.rb +4 -1
- data/lib/ask/tokens/rails/railtie.rb +7 -0
- data/lib/ask/tokens/rails/stores/active_record_store.rb +5 -1
- data/lib/ask/tokens/rails/version.rb +1 -1
- data/lib/ask-tokens-rails.rb +1 -0
- data/lib/generators/ask_tokens/install/install_generator.rb +1 -0
- data/lib/generators/ask_tokens/install/templates/migration.rb +19 -7
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fe4e15b2393197cf4983d5db195ed7e4d513de3593b553b4359f4a5abd114883
|
|
4
|
+
data.tar.gz: 56f42f53d6899ea2c7fdba9d8e664cf083398abe0b510f207663e817564387b2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bcb48f78eed19e4777b2236b7b0a98a53645173a122845db5f8b26c35c7e9f004f98766889b2b366d94814820e1d7d3cb5fdec9d68081aa28c2e3912ec518b14
|
|
7
|
+
data.tar.gz: 2f5d0a2ae26cd3997b2964c4e6d804b757382b5db26af238ae2a5cd809e616e3c16d8bfece4446bf289c17d5bca56a0853ee197c0f2ea1b69cfe4b41526b4e80
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,81 @@
|
|
|
1
|
+
## [0.4.0] — 2026-09-14
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
|
|
5
|
+
- **Billing-detail columns on the ledger.** `model_id`, `provider`,
|
|
6
|
+
`input_tokens`, `output_tokens`, `cached_tokens`, `llm_cost_usd`, and
|
|
7
|
+
`multiplier` — already lifted out of `metadata` by the ActiveRecord store —
|
|
8
|
+
are now in the install migration, so ledgers can be summed and grouped
|
|
9
|
+
(model spend, margin) without JSON casts. The columns are optional: the
|
|
10
|
+
store intersects the extraction keys with the table's actual columns, so
|
|
11
|
+
installs created before this release keep the detail in `metadata`.
|
|
12
|
+
Existing installs can add the columns with the equivalent migration.
|
|
13
|
+
- **`credits` scope and `debit?`/`credit?` predicates** on the ledger model,
|
|
14
|
+
matching the `debits`/`grants` scopes.
|
|
15
|
+
- **`has_token_wallet` macro.** Including `Ask::Tokens::Rails::HasTokenWallet`
|
|
16
|
+
still works; the railtie also makes the documented `has_token_wallet` macro
|
|
17
|
+
available on every model.
|
|
18
|
+
- **`expires_at` index** on the transactions table, for the sweep job.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- **`SweepExpiredTokensJob` never loaded and had a broken query.** The job is
|
|
23
|
+
now required by the railtie, and its grant lookup no longer hardcodes the
|
|
24
|
+
legacy `token_transactions` table or a nonexistent `source_transaction_id`
|
|
25
|
+
column (the source grant lives in `metadata`).
|
|
26
|
+
- **Expiry no longer eats a later top-up.** The sweep replays the wallet's
|
|
27
|
+
ledger first-to-expire-first and removes only each expired grant's unspent
|
|
28
|
+
remainder, instead of capping at whatever the wallet balance happens to be.
|
|
29
|
+
Repeated runs are idempotent.
|
|
30
|
+
|
|
31
|
+
## [0.3.0] — 2026-09-14
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
- **Default table names are now gem-prefixed: `ask_tokens_wallets` and
|
|
38
|
+
|
|
39
|
+
`ask_tokens_transactions`.** They can no longer collide with an app's own
|
|
40
|
+
|
|
41
|
+
ledger, so no app configuration is needed. To keep the old names (or adopt
|
|
42
|
+
|
|
43
|
+
existing tables), configure them:
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
|
|
49
|
+
Ask::Tokens::Rails.configure do |config|
|
|
50
|
+
|
|
51
|
+
config.wallets_table = "token_wallets"
|
|
52
|
+
|
|
53
|
+
config.transactions_table = "token_transactions"
|
|
54
|
+
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
Existing installs must either set the configuration above or rename their
|
|
62
|
+
|
|
63
|
+
tables in a migration.
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
### Added
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
- `Ask::Tokens::Rails.configure` and `reset_config!` — configurable wallet
|
|
72
|
+
|
|
73
|
+
and transaction table names, honoured by the models and the install
|
|
74
|
+
|
|
75
|
+
generator.
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
1
79
|
# Changelog
|
|
2
80
|
|
|
3
81
|
## [0.1.0] - 2026-08-19
|
data/README.md
CHANGED
|
@@ -1,22 +1,31 @@
|
|
|
1
|
-
# ask-
|
|
1
|
+
# ask-tokens-rails
|
|
2
2
|
|
|
3
|
-
ActiveRecord persistence for the [ask-
|
|
3
|
+
ActiveRecord persistence for the [ask-tokens](https://github.com/ask-rb/ask-tokens) wallet engine.
|
|
4
4
|
|
|
5
|
-
Ships an ActiveRecord store adapter, a `has_token_wallet`
|
|
5
|
+
Ships an ActiveRecord store adapter, a `has_token_wallet` macro, install generator with migrations, and an expiry sweep job.
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
9
9
|
```ruby
|
|
10
10
|
# Gemfile
|
|
11
|
-
gem "ask-
|
|
11
|
+
gem "ask-tokens-rails"
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
```sh
|
|
15
15
|
bundle install
|
|
16
|
-
rails g
|
|
16
|
+
rails g ask_tokens:install
|
|
17
17
|
rails db:migrate
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
+
Table names default to `ask_tokens_wallets` and `ask_tokens_transactions`. To adopt existing tables:
|
|
21
|
+
|
|
22
|
+
```ruby
|
|
23
|
+
Ask::Tokens::Rails.configure do |config|
|
|
24
|
+
config.wallets_table = "token_wallets"
|
|
25
|
+
config.transactions_table = "token_transactions"
|
|
26
|
+
end
|
|
27
|
+
```
|
|
28
|
+
|
|
20
29
|
## Usage
|
|
21
30
|
|
|
22
31
|
```ruby
|
|
@@ -35,19 +44,25 @@ end
|
|
|
35
44
|
# Check balance
|
|
36
45
|
user.token_balance # => 9_998
|
|
37
46
|
|
|
47
|
+
# Read the ledger
|
|
48
|
+
user.token_transactions.credits
|
|
49
|
+
user.token_transactions.debits.since(30.days.ago)
|
|
50
|
+
|
|
38
51
|
# Use the PORO wallet directly
|
|
39
52
|
wallet = user.ask_token_wallet
|
|
40
53
|
wallet.entries
|
|
41
54
|
wallet.used_since(30.days.ago)
|
|
42
55
|
```
|
|
43
56
|
|
|
57
|
+
Pass `metadata: {model_id:, provider:, input_tokens:, output_tokens:, cached_tokens:, llm_cost_usd:, multiplier:}` on a grant/deduction and the store lifts those keys into the ledger's billing columns, so model spend and margin can be summed without JSON casts.
|
|
58
|
+
|
|
44
59
|
## Scheduled jobs
|
|
45
60
|
|
|
46
|
-
`SweepExpiredTokensJob` expires grant entries whose `expires_at` has passed.
|
|
61
|
+
`SweepExpiredTokensJob` expires grant entries whose `expires_at` has passed, removing only each grant's unspent remainder (first-to-expire-first allocation). Add an `expires_at` index if your install predates migration column v0.4.0.
|
|
47
62
|
|
|
48
63
|
```yaml
|
|
49
64
|
# config/recurring.yml (Solid Queue)
|
|
50
|
-
|
|
51
|
-
class: Ask::
|
|
52
|
-
schedule:
|
|
65
|
+
ask_tokens_sweep:
|
|
66
|
+
class: Ask::Tokens::Rails::SweepExpiredTokensJob
|
|
67
|
+
schedule: every hour
|
|
53
68
|
```
|
|
@@ -5,6 +5,14 @@ require "active_support/concern"
|
|
|
5
5
|
module Ask
|
|
6
6
|
module Tokens
|
|
7
7
|
module Rails
|
|
8
|
+
# Included into ActiveRecord::Base by the railtie, so any model can say
|
|
9
|
+
# `has_token_wallet` — the macro documented in the README.
|
|
10
|
+
module TokenWalletOwner
|
|
11
|
+
def has_token_wallet
|
|
12
|
+
include Ask::Tokens::Rails::HasTokenWallet
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
8
16
|
# Include this concern in any ActiveRecord model to give it a token
|
|
9
17
|
# wallet backed by ask-tokens-rails.
|
|
10
18
|
#
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ask
|
|
4
|
+
module Tokens
|
|
5
|
+
module Rails
|
|
6
|
+
# Table names are prefixed with the gem's name by default, so they can
|
|
7
|
+
# never collide with an app's own ledger — no configuration needed.
|
|
8
|
+
# Override only to adopt existing tables:
|
|
9
|
+
#
|
|
10
|
+
# Ask::Tokens::Rails.configure do |config|
|
|
11
|
+
# config.wallets_table = "token_wallets"
|
|
12
|
+
# config.transactions_table = "token_transactions"
|
|
13
|
+
# end
|
|
14
|
+
class Configuration
|
|
15
|
+
attr_accessor :wallets_table, :transactions_table
|
|
16
|
+
|
|
17
|
+
def initialize
|
|
18
|
+
@wallets_table = "ask_tokens_wallets"
|
|
19
|
+
@transactions_table = "ask_tokens_transactions"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class << self
|
|
24
|
+
def config
|
|
25
|
+
@config ||= Configuration.new
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def configure
|
|
29
|
+
yield config
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def reset_config!
|
|
33
|
+
@config = Configuration.new
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -8,67 +8,109 @@ module Ask
|
|
|
8
8
|
# Expire grant entries whose +expires_at+ has passed. Scheduled as a
|
|
9
9
|
# recurring job (Solid Queue cron, Sidekiq Cron, etc.):
|
|
10
10
|
#
|
|
11
|
-
# # config/recurring.yml
|
|
11
|
+
# # config/recurring.yml (Solid Queue)
|
|
12
12
|
# ask_tokens_sweep:
|
|
13
13
|
# class: Ask::Tokens::Rails::SweepExpiredTokensJob
|
|
14
|
-
# schedule:
|
|
14
|
+
# schedule: every hour
|
|
15
15
|
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
16
|
+
# Grants are consumed first-to-expire-first: a debit draws from the
|
|
17
|
+
# grant that will expire soonest (non-expiring grants last). Replaying
|
|
18
|
+
# the wallet's ledger therefore tells us exactly how much of each
|
|
19
|
+
# expired grant is left unspent — only that remainder is removed, so
|
|
20
|
+
# expiry can never eat a later top-up. Recorded expiry rows count as
|
|
21
|
+
# consumption of their source grant, which makes repeated runs
|
|
22
|
+
# idempotent.
|
|
20
23
|
#
|
|
21
24
|
class SweepExpiredTokensJob < ActiveJob::Base
|
|
22
25
|
queue_as :default
|
|
23
26
|
|
|
24
27
|
def perform(now: Time.current)
|
|
25
|
-
Ask::Tokens::TokenTransaction
|
|
28
|
+
wallet_ids = Ask::Tokens::TokenTransaction
|
|
26
29
|
.grants
|
|
27
30
|
.where("expires_at IS NOT NULL AND expires_at <= ?", now)
|
|
28
|
-
.
|
|
29
|
-
.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
.distinct
|
|
32
|
+
.pluck(:token_wallet_id)
|
|
33
|
+
|
|
34
|
+
Ask::Tokens::TokenWallet.where(id: wallet_ids).find_each do |wallet|
|
|
35
|
+
sweep_wallet(wallet, now: now)
|
|
36
|
+
end
|
|
33
37
|
end
|
|
34
38
|
|
|
35
39
|
private
|
|
36
40
|
|
|
37
|
-
def
|
|
38
|
-
wallet = grant.token_wallet
|
|
39
|
-
return unless wallet
|
|
40
|
-
|
|
41
|
+
def sweep_wallet(wallet, now:)
|
|
41
42
|
wallet.with_lock do
|
|
42
|
-
|
|
43
|
-
return if
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
43
|
+
expired = expired_remainders(wallet, now: now)
|
|
44
|
+
return if expired.empty?
|
|
45
|
+
|
|
46
|
+
balance = wallet.balance
|
|
47
|
+
expired.each do |grant, remaining|
|
|
48
|
+
actual = [remaining, balance].min
|
|
49
|
+
next if actual <= 0
|
|
50
|
+
|
|
51
|
+
Ask::Tokens::TokenTransaction.create!(
|
|
52
|
+
token_wallet_id: wallet.id,
|
|
53
|
+
entry_type: "expiry",
|
|
54
|
+
amount: -actual,
|
|
55
|
+
reason: "token_expiry",
|
|
56
|
+
balance: balance - actual,
|
|
57
|
+
metadata: {
|
|
58
|
+
source_transaction_id: grant.id,
|
|
59
|
+
expired_amount: remaining,
|
|
60
|
+
actual_expired: actual
|
|
61
|
+
},
|
|
62
|
+
created_at: now
|
|
63
|
+
)
|
|
64
|
+
balance -= actual
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
wallet.update_column(:balance, balance)
|
|
63
68
|
end
|
|
64
69
|
end
|
|
65
70
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
.
|
|
71
|
+
# [[grant, unspent_remainder], ...] for every grant whose window has
|
|
72
|
+
# closed and that still has tokens left after replaying the wallet's
|
|
73
|
+
# ledger.
|
|
74
|
+
def expired_remainders(wallet, now:)
|
|
75
|
+
rows = Ask::Tokens::TokenTransaction
|
|
76
|
+
.where(token_wallet_id: wallet.id)
|
|
77
|
+
.order(:created_at, :id)
|
|
78
|
+
.to_a
|
|
79
|
+
|
|
80
|
+
lots = rows
|
|
81
|
+
.select { |row| row.entry_type == "grant" }
|
|
82
|
+
.sort_by { |grant| [grant.expires_at ? 0 : 1, grant.expires_at || grant.created_at, grant.id] }
|
|
83
|
+
.map { |grant| {grant: grant, remaining: grant.amount} }
|
|
84
|
+
by_grant_id = lots.index_by { |lot| lot[:grant].id }
|
|
85
|
+
|
|
86
|
+
rows.each do |row|
|
|
87
|
+
case row.entry_type
|
|
88
|
+
when "debit"
|
|
89
|
+
consume(lots, -row.amount)
|
|
90
|
+
when "expiry"
|
|
91
|
+
lot = by_grant_id[row.metadata["source_transaction_id"].to_i]
|
|
92
|
+
consume_lot(lot, -row.amount) if lot
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
lots
|
|
97
|
+
.select { |lot| lot[:remaining] > 0 && lot[:grant].expires_at && lot[:grant].expires_at <= now }
|
|
98
|
+
.map { |lot| [lot[:grant], lot[:remaining]] }
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def consume(lots, amount)
|
|
102
|
+
lots.each do |lot|
|
|
103
|
+
amount = consume_lot(lot, amount)
|
|
104
|
+
break if amount <= 0
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def consume_lot(lot, amount)
|
|
109
|
+
return amount if lot.nil? || amount <= 0
|
|
110
|
+
|
|
111
|
+
taken = [lot[:remaining], amount].min
|
|
112
|
+
lot[:remaining] -= taken
|
|
113
|
+
amount - taken
|
|
72
114
|
end
|
|
73
115
|
end
|
|
74
116
|
end
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "active_record"
|
|
4
|
+
require_relative "../configuration"
|
|
4
5
|
|
|
5
6
|
module Ask
|
|
6
7
|
module Tokens
|
|
7
8
|
# Append-only ledger row. Every grant, debit, adjustment, or expiry is
|
|
8
9
|
# written here as an immutable record. Never updated or deleted.
|
|
9
10
|
class TokenTransaction < ::ActiveRecord::Base
|
|
10
|
-
self.table_name
|
|
11
|
+
def self.table_name
|
|
12
|
+
Ask::Tokens::Rails.config.transactions_table
|
|
13
|
+
end
|
|
11
14
|
|
|
12
15
|
belongs_to :token_wallet, class_name: "Ask::Tokens::TokenWallet",
|
|
13
16
|
foreign_key: :token_wallet_id,
|
|
@@ -22,9 +25,19 @@ module Ask
|
|
|
22
25
|
scope :debits, -> { where(entry_type: "debit") }
|
|
23
26
|
scope :adjustments, -> { where(entry_type: "adjustment") }
|
|
24
27
|
scope :expiries, -> { where(entry_type: "expiry") }
|
|
28
|
+
# Everything that adds tokens to a wallet (grants and adjustments).
|
|
29
|
+
scope :credits, -> { where(entry_type: %w[grant adjustment]) }
|
|
25
30
|
scope :since, ->(time) { where("created_at >= ?", time) }
|
|
26
31
|
scope :newest_first, -> { order(created_at: :desc) }
|
|
27
32
|
|
|
33
|
+
def debit?
|
|
34
|
+
entry_type == "debit"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def credit?
|
|
38
|
+
%w[grant adjustment].include?(entry_type)
|
|
39
|
+
end
|
|
40
|
+
|
|
28
41
|
validate :immutable_after_creation, on: :update
|
|
29
42
|
|
|
30
43
|
private
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "active_record"
|
|
4
|
+
require_relative "../configuration"
|
|
4
5
|
|
|
5
6
|
module Ask
|
|
6
7
|
module Tokens
|
|
7
8
|
# Polymorphic wallet record. One per owner. Holds the cached balance.
|
|
8
9
|
class TokenWallet < ::ActiveRecord::Base
|
|
9
|
-
self.table_name
|
|
10
|
+
def self.table_name
|
|
11
|
+
Ask::Tokens::Rails.config.wallets_table
|
|
12
|
+
end
|
|
10
13
|
|
|
11
14
|
has_many :token_transactions, class_name: "Ask::Tokens::TokenTransaction",
|
|
12
15
|
foreign_key: :token_wallet_id,
|
|
@@ -11,11 +11,18 @@ module Ask
|
|
|
11
11
|
require "ask/tokens/rails/models/token_transaction"
|
|
12
12
|
require "ask/tokens/rails/stores/active_record_store"
|
|
13
13
|
require "ask/tokens/rails/concerns/has_token_wallet"
|
|
14
|
+
require "ask/tokens/rails/jobs/sweep_expired_tokens_job"
|
|
14
15
|
|
|
15
16
|
Ask::Tokens.configure do |c|
|
|
16
17
|
c.store = Ask::Tokens::Rails::ActiveRecordStore.new
|
|
17
18
|
end
|
|
18
19
|
end
|
|
20
|
+
|
|
21
|
+
initializer "ask_tokens.has_token_wallet_macro" do
|
|
22
|
+
ActiveSupport.on_load(:active_record) do
|
|
23
|
+
extend Ask::Tokens::Rails::TokenWalletOwner
|
|
24
|
+
end
|
|
25
|
+
end
|
|
19
26
|
end
|
|
20
27
|
end
|
|
21
28
|
end
|
|
@@ -94,10 +94,14 @@ module Ask
|
|
|
94
94
|
# Extract well-known metadata keys into their matching model columns.
|
|
95
95
|
# Only writes keys that exist in metadata, so grants/debits without
|
|
96
96
|
# LLM context are unaffected. Symbol and string keys are both handled.
|
|
97
|
+
# Intersected with the table's actual columns so installs created
|
|
98
|
+
# before the billing columns existed keep the detail in metadata.
|
|
97
99
|
COLUMN_KEYS = %i[model_id provider input_tokens output_tokens cached_tokens llm_cost_usd multiplier].freeze
|
|
98
100
|
|
|
99
101
|
def extract_columns_from_metadata(meta)
|
|
100
|
-
COLUMN_KEYS.
|
|
102
|
+
keys = COLUMN_KEYS & Ask::Tokens::TokenTransaction.column_names.map(&:to_sym)
|
|
103
|
+
|
|
104
|
+
keys.each_with_object({}) do |key, hash|
|
|
101
105
|
value = meta[key] || meta[key.to_s]
|
|
102
106
|
hash[key] = value if value
|
|
103
107
|
end
|
data/lib/ask-tokens-rails.rb
CHANGED
|
@@ -1,25 +1,37 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
class
|
|
3
|
+
class CreateAskTokensTables < ActiveRecord::Migration[7.0]
|
|
4
4
|
def change
|
|
5
|
-
create_table
|
|
5
|
+
create_table :<%= Ask::Tokens::Rails.config.wallets_table %> do |t|
|
|
6
6
|
t.references :owner, polymorphic: true, null: false
|
|
7
7
|
t.bigint :balance, null: false, default: 0
|
|
8
8
|
t.timestamps
|
|
9
9
|
end
|
|
10
|
-
add_index
|
|
10
|
+
add_index :<%= Ask::Tokens::Rails.config.wallets_table %>, %i[owner_type owner_id], unique: true,
|
|
11
|
+
name: "idx_<%= Ask::Tokens::Rails.config.wallets_table %>_owner"
|
|
11
12
|
|
|
12
|
-
create_table
|
|
13
|
-
t.references :token_wallet, null: false, foreign_key:
|
|
13
|
+
create_table :<%= Ask::Tokens::Rails.config.transactions_table %> do |t|
|
|
14
|
+
t.references :token_wallet, null: false, foreign_key: {to_table: Ask::Tokens::Rails.config.wallets_table}
|
|
14
15
|
t.string :entry_type, null: false
|
|
15
16
|
t.bigint :amount, null: false
|
|
16
17
|
t.string :reason, null: false
|
|
17
18
|
t.jsonb :metadata, null: false, default: {}
|
|
18
19
|
t.bigint :balance, null: false
|
|
19
20
|
t.datetime :expires_at
|
|
21
|
+
# Billing detail the ActiveRecord store lifts out of :metadata when
|
|
22
|
+
# present. Columns exist so ledgers can be summed and grouped (model
|
|
23
|
+
# spend, margin) without JSON casts.
|
|
24
|
+
t.string :model_id
|
|
25
|
+
t.string :provider
|
|
26
|
+
t.bigint :input_tokens
|
|
27
|
+
t.bigint :output_tokens
|
|
28
|
+
t.bigint :cached_tokens
|
|
29
|
+
t.decimal :llm_cost_usd, precision: 12, scale: 8
|
|
30
|
+
t.decimal :multiplier, precision: 6, scale: 4, default: 1.0, null: false
|
|
20
31
|
t.datetime :created_at, null: false
|
|
21
32
|
end
|
|
22
|
-
add_index
|
|
23
|
-
add_index
|
|
33
|
+
add_index :<%= Ask::Tokens::Rails.config.transactions_table %>, %i[token_wallet_id created_at]
|
|
34
|
+
add_index :<%= Ask::Tokens::Rails.config.transactions_table %>, :entry_type
|
|
35
|
+
add_index :<%= Ask::Tokens::Rails.config.transactions_table %>, :expires_at
|
|
24
36
|
end
|
|
25
37
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ask-tokens-rails
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kaka Ruto
|
|
@@ -93,6 +93,7 @@ files:
|
|
|
93
93
|
- README.md
|
|
94
94
|
- lib/ask-tokens-rails.rb
|
|
95
95
|
- lib/ask/tokens/rails/concerns/has_token_wallet.rb
|
|
96
|
+
- lib/ask/tokens/rails/configuration.rb
|
|
96
97
|
- lib/ask/tokens/rails/jobs/sweep_expired_tokens_job.rb
|
|
97
98
|
- lib/ask/tokens/rails/models/token_transaction.rb
|
|
98
99
|
- lib/ask/tokens/rails/models/token_wallet.rb
|