llm_cost_tracker 0.3.2 → 0.3.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6952282e6f93b4e5658ef9d2b9527d2a332cb2d6f483da25540c3a0d6672ed9b
4
- data.tar.gz: e66eaaeb99698abf9c0ff9e3f1305e6bb27a8b6c25355e94bce4baec5f5d3a50
3
+ metadata.gz: b966913d302d5c5c3466615d1fa3983855c241f6cd9e3e26558c0fcc5fc4e7d5
4
+ data.tar.gz: 52804e702d5f01e5a4d247e8b50e601dede2b328bd7075c68ffd5f472b3b0d58
5
5
  SHA512:
6
- metadata.gz: '078d695498ed6f254a700ccb3381ace3feaa1f4880691a1a69be4bb435097202ecf28f2cbf065202a543186bdd3894aaedcc44bfacc2d2ffa25a54ddf6d1cc76'
7
- data.tar.gz: 2638ae3c579bd2c0f71a73b06719eb0a35d7b82e8614a74c5100f41b69693babfd3b613ecf18e7f6e7ea33210222432efa5a7ca718325c4c8fd12be9b8ab806e
6
+ metadata.gz: 609ba1a18be86dce0b567b2ea33b3f3123da88683f0c65d9aef780f2e4854d1dde6686adfa505fc154d13da6dd6cb2b31d9f38c303de5fb22f6fda65c7f44aa7
7
+ data.tar.gz: de372e0940b4cfc400dacfc6dbf9e00f256c6944209da8cceaadd20a318b8c7aa8982d5190e21d38640ad20d04cc86400a4e872ec640189796e045acf1f7dfad
data/CHANGELOG.md CHANGED
@@ -4,6 +4,26 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versioning: [S
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.3.3] - 2026-04-24
8
+
9
+ ### Added
10
+
11
+ - Monthly rollup totals for ActiveRecord budget checks, plus `llm_cost_tracker:add_monthly_totals` for upgrading existing installs.
12
+
13
+ ### Changed
14
+
15
+ - ActiveRecord monthly totals now update through a single atomic upsert.
16
+ - Faraday stream capture overflow now records `usage_source: "unknown"` instead of dropping the tracked event.
17
+ - Budget `:notify` callbacks now fire only on the first event that crosses the monthly limit.
18
+
19
+ ### Fixed
20
+
21
+ - Treat `config.enabled = false` as a global kill switch for direct `track` and `track_stream` calls too.
22
+ - Deduplicate unknown-pricing warnings per model.
23
+ - Detect streaming requests from parsed JSON instead of raw body substring matching.
24
+ - Cap automatic SSE capture to avoid unbounded memory growth on large streaming responses.
25
+ - Warn that the generated PostgreSQL `tags -> jsonb` upgrade migration rewrites large tables and should run in a maintenance window.
26
+
7
27
  ## [0.3.2] - 2026-04-22
8
28
 
9
29
  ### Added
data/README.md CHANGED
@@ -343,12 +343,15 @@ On other adapters tags fall back to JSON in a text column. `by_tag` uses JSONB c
343
343
  Upgrade an existing install:
344
344
 
345
345
  ```bash
346
+ bin/rails generate llm_cost_tracker:add_monthly_totals # shared monthly budget rollups
346
347
  bin/rails generate llm_cost_tracker:upgrade_tags_to_jsonb # PG: text → jsonb + GIN
347
348
  bin/rails generate llm_cost_tracker:upgrade_cost_precision # widen cost columns
348
349
  bin/rails generate llm_cost_tracker:add_latency_ms
349
350
  bin/rails db:migrate
350
351
  ```
351
352
 
353
+ On PostgreSQL, the generated `upgrade_tags_to_jsonb` migration rewrites `llm_api_calls`. Run it during a maintenance window on large tables, or replace it with a two-phase backfill for zero-downtime deploys.
354
+
352
355
  ## Mounting the dashboard
353
356
 
354
357
  Optional Rails Engine. Plain ERB, no JavaScript framework, no asset pipeline required. Requires Rails 7.1+; the core middleware works without Rails.
@@ -23,7 +23,7 @@ module LlmCostTracker
23
23
  return unless event.cost
24
24
 
25
25
  monthly_total = if config.active_record?
26
- active_record_monthly_total
26
+ active_record_monthly_total(time: event.tracked_at)
27
27
  else
28
28
  event.cost.total_cost
29
29
  end
@@ -34,11 +34,11 @@ module LlmCostTracker
34
34
 
35
35
  private
36
36
 
37
- def active_record_monthly_total
37
+ def active_record_monthly_total(time: Time.now.utc)
38
38
  require_relative "llm_api_call" unless defined?(LlmCostTracker::LlmApiCall)
39
39
  require_relative "storage/active_record_store" unless defined?(LlmCostTracker::Storage::ActiveRecordStore)
40
40
 
41
- LlmCostTracker::Storage::ActiveRecordStore.monthly_total
41
+ LlmCostTracker::Storage::ActiveRecordStore.monthly_total(time: time)
42
42
  rescue LoadError => e
43
43
  raise Error, "ActiveRecord storage requires the active_record gem: #{e.message}"
44
44
  end
@@ -51,10 +51,20 @@ module LlmCostTracker
51
51
  last_event: last_event
52
52
  }
53
53
 
54
- config.on_budget_exceeded&.call(payload)
54
+ if notify_exceeded?(config, monthly_total: monthly_total, last_event: last_event)
55
+ config.on_budget_exceeded&.call(payload)
56
+ end
55
57
  raise BudgetExceededError.new(**payload) if raise_on_exceeded?(config)
56
58
  end
57
59
 
60
+ def notify_exceeded?(config, monthly_total:, last_event:)
61
+ return false unless config.on_budget_exceeded
62
+ return true unless config.budget_exceeded_behavior == :notify
63
+ return true unless last_event&.cost
64
+
65
+ monthly_total - last_event.cost.total_cost < config.monthly_budget
66
+ end
67
+
58
68
  def raise_on_exceeded?(config)
59
69
  %i[raise block_requests].include?(config.budget_exceeded_behavior)
60
70
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+ require "rails/generators/active_record"
5
+
6
+ module LlmCostTracker
7
+ module Generators
8
+ class AddMonthlyTotalsGenerator < Rails::Generators::Base
9
+ include ActiveRecord::Generators::Migration
10
+
11
+ source_root File.expand_path("templates", __dir__)
12
+
13
+ desc "Creates a migration to add llm_cost_tracker_monthly_totals"
14
+
15
+ def create_migration_file
16
+ migration_template(
17
+ "add_monthly_totals_to_llm_cost_tracker.rb.erb",
18
+ "db/migrate/add_monthly_totals_to_llm_cost_tracker.rb"
19
+ )
20
+ end
21
+
22
+ private
23
+
24
+ def migration_version
25
+ "[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,48 @@
1
+ class AddMonthlyTotalsToLlmCostTracker < ActiveRecord::Migration<%= migration_version %>
2
+ def up
3
+ create_table :llm_cost_tracker_monthly_totals do |t|
4
+ t.date :month_start, null: false
5
+ t.decimal :total_cost, precision: 20, scale: 8, null: false, default: 0
6
+
7
+ t.timestamps
8
+ end unless table_exists?(:llm_cost_tracker_monthly_totals)
9
+
10
+ add_index :llm_cost_tracker_monthly_totals, :month_start,
11
+ unique: true unless index_exists?(:llm_cost_tracker_monthly_totals, :month_start)
12
+
13
+ backfill_monthly_totals
14
+ end
15
+
16
+ def down
17
+ remove_index :llm_cost_tracker_monthly_totals, :month_start if index_exists?(:llm_cost_tracker_monthly_totals, :month_start)
18
+ drop_table :llm_cost_tracker_monthly_totals if table_exists?(:llm_cost_tracker_monthly_totals)
19
+ end
20
+
21
+ private
22
+
23
+ def backfill_monthly_totals
24
+ return unless table_exists?(:llm_api_calls)
25
+
26
+ execute <<~SQL
27
+ INSERT INTO llm_cost_tracker_monthly_totals (month_start, total_cost, created_at, updated_at)
28
+ SELECT #{month_bucket_sql} AS month_start,
29
+ SUM(total_cost) AS total_cost,
30
+ CURRENT_TIMESTAMP,
31
+ CURRENT_TIMESTAMP
32
+ FROM llm_api_calls
33
+ WHERE total_cost IS NOT NULL
34
+ GROUP BY #{month_bucket_sql}
35
+ SQL
36
+ end
37
+
38
+ def month_bucket_sql
39
+ case connection.adapter_name
40
+ when /postgres/i
41
+ "DATE_TRUNC('month', tracked_at)::date"
42
+ when /mysql/i
43
+ "DATE_FORMAT(tracked_at, '%Y-%m-01')"
44
+ else
45
+ "strftime('%Y-%m-01', tracked_at)"
46
+ end
47
+ end
48
+ end
@@ -23,6 +23,13 @@ class CreateLlmApiCalls < ActiveRecord::Migration<%= migration_version %>
23
23
  t.timestamps
24
24
  end
25
25
 
26
+ create_table :llm_cost_tracker_monthly_totals do |t|
27
+ t.date :month_start, null: false
28
+ t.decimal :total_cost, precision: 20, scale: 8, null: false, default: 0
29
+
30
+ t.timestamps
31
+ end
32
+
26
33
  add_index :llm_api_calls, :provider
27
34
  add_index :llm_api_calls, :model
28
35
  add_index :llm_api_calls, :tracked_at
@@ -31,6 +38,7 @@ class CreateLlmApiCalls < ActiveRecord::Migration<%= migration_version %>
31
38
  add_index :llm_api_calls, :usage_source
32
39
  add_index :llm_api_calls, :provider_response_id
33
40
  add_index :llm_api_calls, :tags, using: :gin if postgresql?
41
+ add_index :llm_cost_tracker_monthly_totals, :month_start, unique: true
34
42
  end
35
43
 
36
44
  private
@@ -8,6 +8,7 @@ class UpgradeLlmApiCallTagsToJsonb < ActiveRecord::Migration<%= migration_versio
8
8
  return if tags_jsonb?
9
9
 
10
10
  remove_index :llm_api_calls, :tags if index_exists?(:llm_api_calls, :tags)
11
+ say "Upgrading llm_api_calls.tags to jsonb rewrites the table on PostgreSQL. Run this migration during a maintenance window on large datasets."
11
12
 
12
13
  change_column(
13
14
  :llm_api_calls,
@@ -8,6 +8,8 @@ require_relative "../logging"
8
8
  module LlmCostTracker
9
9
  module Middleware
10
10
  class Faraday < ::Faraday::Middleware
11
+ STREAM_CAPTURE_LIMIT_BYTES = 1_048_576
12
+
11
13
  def initialize(app, **options)
12
14
  super(app)
13
15
  @tags = options.fetch(:tags, {})
@@ -85,15 +87,12 @@ module LlmCostTracker
85
87
  end
86
88
 
87
89
  def parse_stream(parser, request_url, request_body, response_env, stream_buffer)
88
- body = stream_buffer&.string
90
+ body = stream_buffer&.dig(:buffer)&.string
89
91
  body = read_body(response_env.body) if body.nil? || body.empty?
90
92
 
91
93
  if body.nil? || body.empty?
92
- Logging.warn(
93
- "Unable to capture streaming response for #{request_url}; " \
94
- "fall back to LlmCostTracker.track_stream for manual capture."
95
- )
96
- return nil
94
+ Logging.warn(capture_warning(request_url, stream_buffer))
95
+ return parser.parse_stream(request_url, request_body, response_env.status, [])
97
96
  end
98
97
 
99
98
  events = Parsers::SSE.parse(body)
@@ -106,12 +105,21 @@ module LlmCostTracker
106
105
  original = request_env.request.on_data
107
106
  return nil unless original
108
107
 
109
- buffer = StringIO.new
108
+ state = { buffer: StringIO.new, bytes: 0, overflowed: false }
110
109
  request_env.request.on_data = proc do |chunk, size, env|
111
- buffer << chunk.to_s
110
+ chunk = chunk.to_s
111
+ unless state[:overflowed]
112
+ if state[:bytes] + chunk.bytesize <= STREAM_CAPTURE_LIMIT_BYTES
113
+ state[:buffer] << chunk
114
+ state[:bytes] += chunk.bytesize
115
+ else
116
+ state[:overflowed] = true
117
+ state[:buffer] = nil
118
+ end
119
+ end
112
120
  original.call(chunk, size, env)
113
121
  end
114
- buffer
122
+ state
115
123
  rescue StandardError => e
116
124
  Logging.warn("Unable to install streaming tap: #{e.class}: #{e.message}")
117
125
  nil
@@ -145,6 +153,16 @@ module LlmCostTracker
145
153
  def elapsed_ms(started_at)
146
154
  ((monotonic_time - started_at) * 1000).round
147
155
  end
156
+
157
+ def capture_warning(request_url, stream_buffer)
158
+ unless stream_buffer&.dig(:overflowed)
159
+ return "Unable to capture streaming response for #{request_url}; " \
160
+ "recording usage_source=unknown. Use LlmCostTracker.track_stream for manual capture."
161
+ end
162
+
163
+ "Streaming response for #{request_url} exceeded #{STREAM_CAPTURE_LIMIT_BYTES} bytes; " \
164
+ "recording usage_source=unknown. Use LlmCostTracker.track_stream for manual capture."
165
+ end
148
166
  end
149
167
  end
150
168
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record"
4
+
5
+ module LlmCostTracker
6
+ class MonthlyTotal < ActiveRecord::Base
7
+ self.table_name = "llm_cost_tracker_monthly_totals"
8
+ end
9
+ end
@@ -23,7 +23,8 @@ module LlmCostTracker
23
23
  body = request_body.to_s
24
24
  return false if body.empty?
25
25
 
26
- body.include?('"stream":true') || body.include?('"stream": true') || body.include?("stream: true")
26
+ request = safe_json_parse(body)
27
+ request.is_a?(Hash) && request["stream"] == true
27
28
  end
28
29
 
29
30
  def parse_stream(_request_url, _request_body, _response_status, _events)
@@ -3,6 +3,7 @@
3
3
  module LlmCostTracker
4
4
  class Railtie < Rails::Railtie
5
5
  generators do
6
+ require_relative "generators/llm_cost_tracker/add_monthly_totals_generator"
6
7
  require_relative "generators/llm_cost_tracker/add_latency_ms_generator"
7
8
  require_relative "generators/llm_cost_tracker/add_provider_response_id_generator"
8
9
  require_relative "generators/llm_cost_tracker/add_streaming_generator"
@@ -4,6 +4,10 @@ module LlmCostTracker
4
4
  module Storage
5
5
  class ActiveRecordStore
6
6
  class << self
7
+ def reset!
8
+ remove_instance_variable(:@monthly_totals_enabled) if instance_variable_defined?(:@monthly_totals_enabled)
9
+ end
10
+
7
11
  def save(event)
8
12
  tags = stringify_tags(event.tags || {})
9
13
 
@@ -26,18 +30,86 @@ module LlmCostTracker
26
30
  attributes[:provider_response_id] = event.provider_response_id
27
31
  end
28
32
 
29
- LlmCostTracker::LlmApiCall.create!(attributes)
33
+ LlmCostTracker::LlmApiCall.transaction do
34
+ call = LlmCostTracker::LlmApiCall.create!(attributes)
35
+ increment_monthly_total(event)
36
+ call
37
+ end
30
38
  end
31
39
 
32
40
  def monthly_total(time: Time.now.utc)
33
- LlmCostTracker::LlmApiCall
34
- .where(tracked_at: time.beginning_of_month..time)
35
- .sum(:total_cost)
36
- .to_f
41
+ if monthly_totals_enabled?
42
+ monthly_total_model.where(month_start: month_start_for(time)).pick(:total_cost).to_f
43
+ else
44
+ LlmCostTracker::LlmApiCall
45
+ .where(tracked_at: time.beginning_of_month..time)
46
+ .sum(:total_cost)
47
+ .to_f
48
+ end
37
49
  end
38
50
 
39
51
  private
40
52
 
53
+ def increment_monthly_total(event)
54
+ return unless monthly_totals_enabled?
55
+ return unless event.cost&.total_cost
56
+
57
+ monthly_total_model.upsert_all(
58
+ [
59
+ {
60
+ month_start: month_start_for(event.tracked_at),
61
+ total_cost: event.cost.total_cost
62
+ }
63
+ ],
64
+ on_duplicate: monthly_total_upsert_sql,
65
+ record_timestamps: true,
66
+ unique_by: monthly_total_unique_by
67
+ )
68
+ end
69
+
70
+ def monthly_totals_enabled?
71
+ return @monthly_totals_enabled unless @monthly_totals_enabled.nil?
72
+
73
+ @monthly_totals_enabled =
74
+ LlmCostTracker::LlmApiCall.connection.data_source_exists?("llm_cost_tracker_monthly_totals")
75
+ end
76
+
77
+ def monthly_total_model
78
+ require_relative "../monthly_total" unless defined?(LlmCostTracker::MonthlyTotal)
79
+
80
+ LlmCostTracker::MonthlyTotal
81
+ end
82
+
83
+ def month_start_for(time)
84
+ time.to_time.utc.beginning_of_month.to_date
85
+ end
86
+
87
+ def monthly_total_unique_by
88
+ return unless monthly_total_model.connection.supports_insert_conflict_target?
89
+
90
+ :month_start
91
+ end
92
+
93
+ def monthly_total_upsert_sql
94
+ Arel.sql(case monthly_total_model.connection.adapter_name
95
+ when /mysql/i
96
+ mysql_upsert_sql
97
+ else
98
+ "total_cost = total_cost + excluded.total_cost, updated_at = excluded.updated_at"
99
+ end)
100
+ end
101
+
102
+ def mysql_upsert_sql
103
+ connection = monthly_total_model.connection
104
+ if connection.respond_to?(:supports_insert_raw_alias_syntax?, true) &&
105
+ connection.send(:supports_insert_raw_alias_syntax?)
106
+ values_reference = connection.quote_table_name("#{monthly_total_model.table_name}_values")
107
+ "total_cost = total_cost + #{values_reference}.total_cost, updated_at = #{values_reference}.updated_at"
108
+ else
109
+ "total_cost = total_cost + VALUES(total_cost), updated_at = VALUES(updated_at)"
110
+ end
111
+ end
112
+
41
113
  def stringify_tags(tags)
42
114
  tags.transform_keys(&:to_s).transform_values { |value| stringify_tag_value(value) }
43
115
  end
@@ -69,7 +69,7 @@ module LlmCostTracker
69
69
 
70
70
  @finished = true
71
71
  {
72
- events: ValueHelpers.deep_dup(@events),
72
+ events: @events.dup,
73
73
  explicit_usage: ValueHelpers.deep_dup(@explicit_usage),
74
74
  model: @model,
75
75
  latency_ms: @latency_ms,
@@ -10,11 +10,15 @@ module LlmCostTracker
10
10
 
11
11
  class << self
12
12
  def enforce_budget!
13
+ return unless LlmCostTracker.configuration.enabled
14
+
13
15
  Budget.enforce!
14
16
  end
15
17
 
16
18
  def record(provider:, model:, input_tokens:, output_tokens:, latency_ms: nil, stream: false,
17
19
  usage_source: nil, provider_response_id: nil, metadata: {})
20
+ return unless LlmCostTracker.configuration.enabled
21
+
18
22
  usage = EventMetadata.usage_data(input_tokens, output_tokens, metadata)
19
23
 
20
24
  cost_data = Pricing.cost_for(
@@ -1,9 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "monitor"
4
+
3
5
  require_relative "logging"
4
6
 
5
7
  module LlmCostTracker
6
8
  class UnknownPricing
9
+ MUTEX = Monitor.new
10
+
7
11
  class << self
8
12
  def handle!(model)
9
13
  model = normalized_model_name(model)
@@ -18,6 +22,10 @@ module LlmCostTracker
18
22
  end
19
23
  end
20
24
 
25
+ def reset!
26
+ MUTEX.synchronize { @warned_models = Set.new }
27
+ end
28
+
21
29
  private
22
30
 
23
31
  def normalized_model_name(model)
@@ -25,6 +33,12 @@ module LlmCostTracker
25
33
  end
26
34
 
27
35
  def warn_missing(model)
36
+ should_warn = MUTEX.synchronize do
37
+ @warned_models ||= Set.new
38
+ @warned_models.add?(model)
39
+ end
40
+ return unless should_warn
41
+
28
42
  Logging.warn(
29
43
  "No pricing configured for model #{model.inspect}. " \
30
44
  "Cost and budget guardrails will be skipped for this event. " \
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LlmCostTracker
4
- VERSION = "0.3.2"
4
+ VERSION = "0.3.3"
5
5
  end
@@ -60,6 +60,8 @@ module LlmCostTracker
60
60
 
61
61
  def reset_configuration!
62
62
  CONFIGURATION_MUTEX.synchronize { @configuration = Configuration.new }
63
+ UnknownPricing.reset! if defined?(UnknownPricing)
64
+ Storage::ActiveRecordStore.reset! if defined?(Storage::ActiveRecordStore)
63
65
  end
64
66
 
65
67
  def enforce_budget!
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: llm_cost_tracker
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sergii Khomenko
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-04-22 00:00:00.000000000 Z
11
+ date: 2026-04-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -278,11 +278,13 @@ files:
278
278
  - lib/llm_cost_tracker/event.rb
279
279
  - lib/llm_cost_tracker/event_metadata.rb
280
280
  - lib/llm_cost_tracker/generators/llm_cost_tracker/add_latency_ms_generator.rb
281
+ - lib/llm_cost_tracker/generators/llm_cost_tracker/add_monthly_totals_generator.rb
281
282
  - lib/llm_cost_tracker/generators/llm_cost_tracker/add_provider_response_id_generator.rb
282
283
  - lib/llm_cost_tracker/generators/llm_cost_tracker/add_streaming_generator.rb
283
284
  - lib/llm_cost_tracker/generators/llm_cost_tracker/install_generator.rb
284
285
  - lib/llm_cost_tracker/generators/llm_cost_tracker/prices_generator.rb
285
286
  - lib/llm_cost_tracker/generators/llm_cost_tracker/templates/add_latency_ms_to_llm_api_calls.rb.erb
287
+ - lib/llm_cost_tracker/generators/llm_cost_tracker/templates/add_monthly_totals_to_llm_cost_tracker.rb.erb
286
288
  - lib/llm_cost_tracker/generators/llm_cost_tracker/templates/add_provider_response_id_to_llm_api_calls.rb.erb
287
289
  - lib/llm_cost_tracker/generators/llm_cost_tracker/templates/add_streaming_to_llm_api_calls.rb.erb
288
290
  - lib/llm_cost_tracker/generators/llm_cost_tracker/templates/create_llm_api_calls.rb.erb
@@ -295,6 +297,7 @@ files:
295
297
  - lib/llm_cost_tracker/llm_api_call.rb
296
298
  - lib/llm_cost_tracker/logging.rb
297
299
  - lib/llm_cost_tracker/middleware/faraday.rb
300
+ - lib/llm_cost_tracker/monthly_total.rb
298
301
  - lib/llm_cost_tracker/parameter_hash.rb
299
302
  - lib/llm_cost_tracker/parsed_usage.rb
300
303
  - lib/llm_cost_tracker/parsers/anthropic.rb
@@ -338,7 +341,6 @@ files:
338
341
  - lib/llm_cost_tracker/value_helpers.rb
339
342
  - lib/llm_cost_tracker/version.rb
340
343
  - lib/tasks/llm_cost_tracker.rake
341
- - llm_cost_tracker.gemspec
342
344
  homepage: https://github.com/sergey-homenko/llm_cost_tracker
343
345
  licenses:
344
346
  - MIT
@@ -363,7 +365,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
363
365
  - !ruby/object:Gem::Version
364
366
  version: '0'
365
367
  requirements: []
366
- rubygems_version: 3.5.9
368
+ rubygems_version: 3.5.22
367
369
  signing_key:
368
370
  specification_version: 4
369
371
  summary: Self-hosted LLM usage and cost tracking for Ruby and Rails
@@ -1,50 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "lib/llm_cost_tracker/version"
4
-
5
- Gem::Specification.new do |spec|
6
- spec.name = "llm_cost_tracker"
7
- spec.version = LlmCostTracker::VERSION
8
- spec.authors = ["Sergii Khomenko"]
9
- spec.email = ["sergey@mm.st"]
10
-
11
- spec.summary = "Self-hosted LLM usage and cost tracking for Ruby and Rails"
12
- spec.description = "Tracks token usage, latency, and estimated costs for OpenAI, Anthropic, " \
13
- "Google Gemini, OpenRouter, DeepSeek, and OpenAI-compatible APIs. " \
14
- "Works through Faraday middleware or explicit track/track_stream helpers, " \
15
- "with ActiveRecord storage, tag-based attribution, price sync tasks, " \
16
- "and budget guardrails."
17
- spec.homepage = "https://github.com/sergey-homenko/llm_cost_tracker"
18
- spec.license = "MIT"
19
-
20
- spec.required_ruby_version = ">= 3.3.0"
21
-
22
- spec.metadata["bug_tracker_uri"] = "#{spec.homepage}/issues"
23
- spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
24
- spec.metadata["source_code_uri"] = spec.homepage
25
- spec.metadata["documentation_uri"] = "#{spec.homepage}#readme"
26
- spec.metadata["rubygems_mfa_required"] = "true"
27
-
28
- spec.files = Dir.chdir(__dir__) do
29
- `git ls-files -z`.split("\x0").reject do |f|
30
- (File.expand_path(f) == __FILE__) ||
31
- f.start_with?("bin/", "docs/", "test/", "spec/", ".git", ".github", "gemfiles/", ".rubocop", "Gemfile")
32
- end
33
- end
34
-
35
- spec.require_paths = ["lib"]
36
-
37
- spec.add_dependency "activesupport", ">= 7.1", "< 9.0"
38
- spec.add_dependency "csv", "~> 3.0"
39
- spec.add_dependency "faraday", ">= 2.0", "< 3.0"
40
-
41
- spec.add_development_dependency "activerecord", ">= 7.1", "< 9.0"
42
- spec.add_development_dependency "railties", ">= 7.1", "< 9.0"
43
- spec.add_development_dependency "rake", "~> 13.0"
44
- spec.add_development_dependency "rspec", "~> 3.0"
45
- spec.add_development_dependency "rubocop", "~> 1.0"
46
- spec.add_development_dependency "simplecov", "~> 0.22"
47
- spec.add_development_dependency "simplecov-lcov", "~> 0.8"
48
- spec.add_development_dependency "sqlite3", ">= 1.4", "< 3.0"
49
- spec.add_development_dependency "webmock", "~> 3.0"
50
- end