lex-metering 0.1.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 7c946a94465b617a2dd09a892a954f82e6877d69ca5e95deab2e12323c4baeb5
4
+ data.tar.gz: 6045e0987b95775a42f940ab9dfbe51a8c3c331bb2ccd41dcb1470d8b5dd6022
5
+ SHA512:
6
+ metadata.gz: a2b33f3404050a72676eba080f1bc94bd1a1680ef3bd869385c1d3a47c94eaa36453bd1426603018f2a4bb495eadf0e8f454198dfebb4de34f4621366ff39fdc
7
+ data.tar.gz: 36c8fb718ab7a60ebd81ab210344832bd9981fd68ded369890a9abf68e9045dab4eea7220cc23b6cfb497c4fefb4351e47e22bd9e33976b0c6f7ab59c786156f
@@ -0,0 +1,16 @@
1
+ name: CI
2
+ on:
3
+ push:
4
+ branches: [main]
5
+ pull_request:
6
+
7
+ jobs:
8
+ ci:
9
+ uses: LegionIO/.github/.github/workflows/ci.yml@main
10
+
11
+ release:
12
+ needs: ci
13
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
14
+ uses: LegionIO/.github/.github/workflows/release.yml@main
15
+ secrets:
16
+ rubygems-api-key: ${{ secrets.RUBYGEMS_API_KEY }}
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ .rspec_status
5
+ pkg/
6
+ coverage/
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --require spec_helper
2
+ --format documentation
3
+ --color
data/.rubocop.yml ADDED
@@ -0,0 +1,59 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.4
3
+ NewCops: enable
4
+ SuggestExtensions: false
5
+
6
+ Layout/LineLength:
7
+ Max: 160
8
+
9
+ Layout/SpaceAroundEqualsInParameterDefault:
10
+ EnforcedStyle: space
11
+
12
+ Layout/HashAlignment:
13
+ EnforcedHashRocketStyle: table
14
+ EnforcedColonStyle: table
15
+
16
+ Metrics/MethodLength:
17
+ Max: 50
18
+
19
+ Metrics/ClassLength:
20
+ Max: 1500
21
+
22
+ Metrics/ModuleLength:
23
+ Max: 1500
24
+
25
+ Metrics/BlockLength:
26
+ Max: 40
27
+ Exclude:
28
+ - 'spec/**/*'
29
+
30
+ Metrics/AbcSize:
31
+ Max: 60
32
+
33
+ Metrics/CyclomaticComplexity:
34
+ Max: 15
35
+
36
+ Metrics/PerceivedComplexity:
37
+ Max: 17
38
+
39
+ Style/Documentation:
40
+ Enabled: false
41
+
42
+ Style/SymbolArray:
43
+ Enabled: true
44
+
45
+ Style/FrozenStringLiteralComment:
46
+ Enabled: true
47
+ EnforcedStyle: always
48
+
49
+ Naming/FileName:
50
+ Enabled: false
51
+
52
+ Naming/PredicateMethod:
53
+ Enabled: false
54
+
55
+ Gemspec/DevelopmentDependencies:
56
+ Enabled: false
57
+
58
+ Metrics/ParameterLists:
59
+ Enabled: false
data/CHANGELOG.md ADDED
@@ -0,0 +1,17 @@
1
+ # Changelog
2
+
3
+ ## [0.1.1] - 2026-03-15
4
+
5
+ ### Added
6
+ - `cleanup_old_records` runner method with configurable retention (default 90 days)
7
+ - `Cleanup` periodic actor (runs daily) to prune old metering records
8
+
9
+ ## [0.1.0] - 2026-03-13
10
+
11
+ ### Added
12
+ - `record` method for capturing LLM token usage metrics
13
+ - `worker_costs` aggregation by worker ID and time period
14
+ - `team_costs` aggregation across team members
15
+ - `routing_stats` breakdown by routing reason, provider, and model
16
+ - Database migration for `metering_records` table
17
+ - Full RSpec test coverage for all runner methods
data/CLAUDE.md ADDED
@@ -0,0 +1,75 @@
1
+ # lex-metering: LLM Cost Metering for LegionIO
2
+
3
+ **Repository Level 3 Documentation**
4
+ - **Parent**: `/Users/miverso2/rubymine/legion/extensions-core/CLAUDE.md`
5
+ - **Grandparent**: `/Users/miverso2/rubymine/legion/CLAUDE.md`
6
+
7
+ ## Purpose
8
+
9
+ Captures LLM token usage metrics per task for cost attribution and intelligent routing. Records input/output/thinking tokens, latency, wall-clock time, CPU time, external API call counts, and routing reason per metered call. Supports per-worker, per-team, and aggregate routing statistics queries.
10
+
11
+ ## Gem Info
12
+
13
+ - **Gem name**: `lex-metering`
14
+ - **Version**: `0.1.0`
15
+ - **Module**: `Legion::Extensions::Metering`
16
+ - **Ruby**: `>= 3.4`
17
+ - **License**: MIT
18
+ - **GitHub**: https://github.com/LegionIO/lex-metering
19
+
20
+ ## File Structure
21
+
22
+ ```
23
+ lib/legion/extensions/metering/
24
+ version.rb
25
+ data/
26
+ migrations/
27
+ 001_add_metering_records.rb # Creates metering_records table
28
+ runners/
29
+ metering.rb # record, worker_costs, team_costs, routing_stats
30
+ ```
31
+
32
+ ## Database Schema (`metering_records`)
33
+
34
+ | Column | Type | Description |
35
+ |--------|------|-------------|
36
+ | `worker_id` | String(36) | Digital worker ID (nullable for non-worker tasks) |
37
+ | `task_id` | Integer | Legion task ID |
38
+ | `provider` | String(100) | LLM provider (e.g., 'anthropic', 'openai', 'bedrock') |
39
+ | `model_id` | String(255) | Model identifier |
40
+ | `input_tokens` | Integer | Prompt tokens |
41
+ | `output_tokens` | Integer | Completion tokens |
42
+ | `thinking_tokens` | Integer | Thinking/reasoning tokens (Anthropic extended thinking) |
43
+ | `total_tokens` | Integer | Sum of all token types |
44
+ | `input_context_bytes` | Integer | Raw context size in bytes |
45
+ | `latency_ms` | Integer | LLM API round-trip time |
46
+ | `wall_clock_ms` | Integer | Total wall-clock time for the task |
47
+ | `cpu_time_ms` | Integer | CPU time consumed |
48
+ | `external_api_calls` | Integer | Non-LLM external API calls made |
49
+ | `routing_reason` | String(255) | Why this model/provider was chosen |
50
+ | `recorded_at` | DateTime | Timestamp (indexed) |
51
+
52
+ ## Runner Methods
53
+
54
+ | Method | Parameters | Returns |
55
+ |--------|-----------|---------|
56
+ | `record` | All schema fields as kwargs | Record hash (also inserted to DB) |
57
+ | `worker_costs` | `worker_id:`, `period: 'daily'` | Aggregated token/call/latency metrics |
58
+ | `team_costs` | `team:`, `period: 'daily'` | Team-wide aggregation across all team workers |
59
+ | `routing_stats` | `worker_id: nil` | Breakdowns by routing_reason, provider, model, avg latency |
60
+
61
+ `period` values: `'daily'`, `'weekly'`, `'monthly'`
62
+
63
+ ## Integration Points
64
+
65
+ - **legion-data**: `data_required? true` — will not load if DB unavailable. Accesses `metering_records` as a raw Sequel dataset (no Sequel::Model subclass).
66
+ - **LegionIO MCP**: `legion.routing_stats` MCP tool calls `routing_stats` runner
67
+ - **REST API**: `GET /api/tasks/:id` includes a `:metering` block when lex-metering data exists for the task
68
+ - **Digital Workers**: `legion worker costs` CLI command delegates to `worker_costs` runner
69
+
70
+ ## Development Notes
71
+
72
+ - Extension has `data_required? true` (both at module level and instance level) — will skip loading if `legion-data` is not connected
73
+ - No explicit actors — gets auto-generated subscription actors from the framework
74
+ - `routing_stats` uses `select_append { avg(latency_ms).as(avg_latency) }` — Sequel virtual row syntax
75
+ - Time interval filtering uses `Sequel.lit("CURRENT_TIMESTAMP - INTERVAL '...'")` which is PostgreSQL syntax; SQLite uses different interval syntax (known limitation)
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Matthew Iverson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,59 @@
1
+ # lex-metering
2
+
3
+ Captures LLM token usage, latency, and routing metrics per task for cost attribution and intelligent routing decisions.
4
+
5
+ **Ruby >= 3.4** | **License**: MIT | **Author**: [@Esity](https://github.com/Esity)
6
+
7
+ ## Purpose
8
+
9
+ `lex-metering` records every LLM call made through a Legion digital worker — tokens consumed, latency, wall-clock time, CPU time, external API calls, and routing reason. Data is persisted to the `metering_records` table and queried for cost attribution and routing statistics.
10
+
11
+ ## Installation
12
+
13
+ Included with the LegionIO framework. No separate installation needed.
14
+
15
+ ## Usage
16
+
17
+ ```ruby
18
+ # Record an LLM call
19
+ Legion::Extensions::Metering::Runners::Metering.record(
20
+ worker_id: 'my-worker',
21
+ task_id: 42,
22
+ provider: 'anthropic',
23
+ model_id: 'claude-opus-4-6',
24
+ input_tokens: 1000,
25
+ output_tokens: 500,
26
+ latency_ms: 1200,
27
+ routing_reason: 'cost_optimization'
28
+ )
29
+
30
+ # Query worker costs
31
+ costs = Legion::Extensions::Metering::Runners::Metering.worker_costs(
32
+ worker_id: 'my-worker',
33
+ period: 'daily'
34
+ )
35
+
36
+ # Query routing statistics
37
+ stats = Legion::Extensions::Metering::Runners::Metering.routing_stats
38
+ ```
39
+
40
+ ## Database
41
+
42
+ Requires `legion-data`. Creates the `metering_records` table via Sequel migration.
43
+
44
+ ## Record Retention
45
+
46
+ Metering records are pruned automatically by the `Cleanup` actor, which runs once per day. The default retention period is 90 days. Records with a `recorded_at` older than the cutoff are permanently deleted.
47
+
48
+ To trigger cleanup manually:
49
+
50
+ ```ruby
51
+ Legion::Extensions::Metering::Runners::Metering.cleanup_old_records(retention_days: 90)
52
+ # => { purged: 1234, retention_days: 90, cutoff: 2025-12-15 00:00:00 UTC }
53
+ ```
54
+
55
+ ## Related
56
+
57
+ - [LegionIO](https://github.com/LegionIO/LegionIO) — Framework
58
+ - [legion-data](https://github.com/LegionIO/legion-data) — Persistence layer
59
+ - [Digital Worker Platform](../../../docs/spec-digital-worker-integration.md) — Cost governance
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'legion/extensions/metering/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'lex-metering'
9
+ spec.version = Legion::Extensions::Metering::VERSION
10
+ spec.authors = ['Esity']
11
+ spec.email = ['matthewdiverson@gmail.com']
12
+
13
+ spec.summary = 'Legion::Extensions::Metering'
14
+ spec.description = 'Captures LLM token usage metrics per task for cost attribution and intelligent routing'
15
+ spec.homepage = 'https://github.com/LegionIO/lex-metering'
16
+ spec.license = 'MIT'
17
+ spec.required_ruby_version = '>= 3.4'
18
+
19
+ spec.metadata['homepage_uri'] = spec.homepage
20
+ spec.metadata['source_code_uri'] = 'https://github.com/LegionIO/lex-metering'
21
+ spec.metadata['changelog_uri'] = 'https://github.com/LegionIO/lex-metering/blob/main/CHANGELOG.md'
22
+ spec.metadata['documentation_uri'] = 'https://github.com/LegionIO/lex-metering'
23
+ spec.metadata['bug_tracker_uri'] = 'https://github.com/LegionIO/lex-metering/issues'
24
+ spec.metadata['rubygems_mfa_required'] = 'true'
25
+
26
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
27
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
28
+ end
29
+ spec.require_paths = ['lib']
30
+
31
+ spec.add_dependency 'legionio'
32
+
33
+ spec.add_development_dependency 'rake'
34
+ spec.add_development_dependency 'rspec'
35
+ spec.add_development_dependency 'rubocop'
36
+ spec.add_development_dependency 'rubocop-rspec'
37
+ spec.add_development_dependency 'simplecov'
38
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Metering
6
+ module Actors
7
+ class Cleanup < Legion::Extensions::Actors::Every
8
+ def runner_class
9
+ 'Legion::Extensions::Metering::Runners::Metering'
10
+ end
11
+
12
+ def runner_function
13
+ 'cleanup_old_records'
14
+ end
15
+
16
+ def time
17
+ 86_400 # once per day
18
+ end
19
+
20
+ def run_now?
21
+ false
22
+ end
23
+
24
+ def use_runner?
25
+ false
26
+ end
27
+
28
+ def check_subtask?
29
+ false
30
+ end
31
+
32
+ def generate_task?
33
+ false
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ Sequel.migration do
4
+ up do
5
+ create_table(:metering_records) do
6
+ primary_key :id
7
+ String :worker_id, null: true, size: 36, index: true
8
+ Integer :task_id, null: true, index: true
9
+ String :provider, null: true, size: 100, index: true
10
+ String :model_id, null: true, size: 255
11
+ Integer :input_tokens, null: false, default: 0
12
+ Integer :output_tokens, null: false, default: 0
13
+ Integer :thinking_tokens, null: false, default: 0
14
+ Integer :total_tokens, null: false, default: 0
15
+ Integer :input_context_bytes, null: false, default: 0
16
+ Integer :latency_ms, null: false, default: 0
17
+ Integer :wall_clock_ms, null: false, default: 0
18
+ Integer :cpu_time_ms, null: false, default: 0
19
+ Integer :external_api_calls, null: false, default: 0
20
+ String :routing_reason, null: true, size: 255
21
+ DateTime :recorded_at, null: false, default: Sequel::CURRENT_TIMESTAMP, index: true
22
+ end
23
+ end
24
+
25
+ down do
26
+ drop_table :metering_records
27
+ end
28
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Metering
6
+ module Runners
7
+ module Metering
8
+ def record(worker_id: nil, task_id: nil, provider: nil, model_id: nil,
9
+ input_tokens: 0, output_tokens: 0, thinking_tokens: 0,
10
+ input_context_bytes: 0, latency_ms: 0, routing_reason: nil,
11
+ wall_clock_ms: 0, cpu_time_ms: 0, external_api_calls: 0, **)
12
+ record = {
13
+ worker_id: worker_id,
14
+ task_id: task_id,
15
+ provider: provider,
16
+ model_id: model_id,
17
+ input_tokens: input_tokens,
18
+ output_tokens: output_tokens,
19
+ thinking_tokens: thinking_tokens,
20
+ total_tokens: input_tokens + output_tokens + thinking_tokens,
21
+ input_context_bytes: input_context_bytes,
22
+ latency_ms: latency_ms,
23
+ wall_clock_ms: wall_clock_ms,
24
+ cpu_time_ms: cpu_time_ms,
25
+ external_api_calls: external_api_calls,
26
+ routing_reason: routing_reason,
27
+ recorded_at: Time.now.utc
28
+ }
29
+
30
+ Legion::Data.connection[:metering_records].insert(record) if defined?(Legion::Data) && Legion::Data.connection
31
+ Legion::Logging.debug "[metering] recorded: provider=#{provider} model=#{model_id} " \
32
+ "tokens=#{record[:total_tokens]} latency=#{latency_ms}ms wall_clock=#{wall_clock_ms}ms"
33
+ record
34
+ end
35
+
36
+ def worker_costs(worker_id:, period: 'daily', **)
37
+ ds = Legion::Data.connection[:metering_records].where(worker_id: worker_id)
38
+
39
+ case period
40
+ when 'daily'
41
+ ds = ds.where { recorded_at >= Sequel.lit("CURRENT_TIMESTAMP - INTERVAL '1 day'") }
42
+ when 'weekly'
43
+ ds = ds.where { recorded_at >= Sequel.lit("CURRENT_TIMESTAMP - INTERVAL '7 days'") }
44
+ when 'monthly'
45
+ ds = ds.where { recorded_at >= Sequel.lit("CURRENT_TIMESTAMP - INTERVAL '30 days'") }
46
+ end
47
+
48
+ {
49
+ worker_id: worker_id,
50
+ period: period,
51
+ total_tokens: ds.sum(:total_tokens) || 0,
52
+ input_tokens: ds.sum(:input_tokens) || 0,
53
+ output_tokens: ds.sum(:output_tokens) || 0,
54
+ thinking_tokens: ds.sum(:thinking_tokens) || 0,
55
+ total_calls: ds.count,
56
+ avg_latency_ms: ds.avg(:latency_ms)&.round(1) || 0,
57
+ by_provider: ds.group_and_count(:provider).all,
58
+ by_model: ds.group_and_count(:model_id).all
59
+ }
60
+ end
61
+
62
+ def team_costs(team:, period: 'daily', **)
63
+ workers = Legion::Data::Model::DigitalWorker.where(team: team).select_map(:worker_id)
64
+ ds = Legion::Data.connection[:metering_records].where(worker_id: workers)
65
+
66
+ {
67
+ team: team,
68
+ period: period,
69
+ worker_count: workers.size,
70
+ total_tokens: ds.sum(:total_tokens) || 0,
71
+ total_calls: ds.count,
72
+ by_worker: ds.group_and_count(:worker_id).all
73
+ }
74
+ end
75
+
76
+ def routing_stats(worker_id: nil, **)
77
+ ds = Legion::Data.connection[:metering_records]
78
+ ds = ds.where(worker_id: worker_id) if worker_id
79
+
80
+ {
81
+ by_routing_reason: ds.group_and_count(:routing_reason).all,
82
+ by_provider: ds.group_and_count(:provider).all,
83
+ by_model: ds.group_and_count(:model_id).all,
84
+ avg_latency_by_provider: ds.group(:provider).select_append { avg(latency_ms).as(avg_latency) }.all
85
+ }
86
+ end
87
+
88
+ def cleanup_old_records(retention_days: 90, **)
89
+ return { purged: 0, retention_days: retention_days, cutoff: nil } unless defined?(Legion::Data) && Legion::Data.connection
90
+
91
+ cutoff = Time.now.utc - (retention_days * 86_400)
92
+ count = Legion::Data.connection[:metering_records].where { recorded_at < cutoff }.delete
93
+ Legion::Logging.info "[metering] cleanup: purged=#{count} retention_days=#{retention_days} cutoff=#{cutoff}"
94
+ { purged: count, retention_days: retention_days, cutoff: cutoff }
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Metering
6
+ VERSION = '0.1.1'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/metering/version'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module Metering
8
+ extend Legion::Extensions::Core if Legion::Extensions.const_defined? :Core
9
+
10
+ def self.data_required?
11
+ true
12
+ end
13
+
14
+ def data_required?
15
+ true
16
+ end
17
+ end
18
+ end
19
+ end
metadata ADDED
@@ -0,0 +1,146 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lex-metering
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Esity
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: legionio
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: rake
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: rspec
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: rubocop
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: rubocop-rspec
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ - !ruby/object:Gem::Dependency
83
+ name: simplecov
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ description: Captures LLM token usage metrics per task for cost attribution and intelligent
97
+ routing
98
+ email:
99
+ - matthewdiverson@gmail.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".github/workflows/ci.yml"
105
+ - ".gitignore"
106
+ - ".rspec"
107
+ - ".rubocop.yml"
108
+ - CHANGELOG.md
109
+ - CLAUDE.md
110
+ - Gemfile
111
+ - LICENSE
112
+ - README.md
113
+ - lex-metering.gemspec
114
+ - lib/legion/extensions/metering.rb
115
+ - lib/legion/extensions/metering/actors/cleanup.rb
116
+ - lib/legion/extensions/metering/data/migrations/001_add_metering_records.rb
117
+ - lib/legion/extensions/metering/runners/metering.rb
118
+ - lib/legion/extensions/metering/version.rb
119
+ homepage: https://github.com/LegionIO/lex-metering
120
+ licenses:
121
+ - MIT
122
+ metadata:
123
+ homepage_uri: https://github.com/LegionIO/lex-metering
124
+ source_code_uri: https://github.com/LegionIO/lex-metering
125
+ changelog_uri: https://github.com/LegionIO/lex-metering/blob/main/CHANGELOG.md
126
+ documentation_uri: https://github.com/LegionIO/lex-metering
127
+ bug_tracker_uri: https://github.com/LegionIO/lex-metering/issues
128
+ rubygems_mfa_required: 'true'
129
+ rdoc_options: []
130
+ require_paths:
131
+ - lib
132
+ required_ruby_version: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - ">="
135
+ - !ruby/object:Gem::Version
136
+ version: '3.4'
137
+ required_rubygems_version: !ruby/object:Gem::Requirement
138
+ requirements:
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ requirements: []
143
+ rubygems_version: 3.6.9
144
+ specification_version: 4
145
+ summary: Legion::Extensions::Metering
146
+ test_files: []