lex-llm-ledger 0.1.2

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.
Files changed (31) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +30 -0
  3. data/Gemfile +6 -0
  4. data/LICENSE +201 -0
  5. data/README.md +62 -0
  6. data/Rakefile +7 -0
  7. data/lex-llm-ledger.gemspec +43 -0
  8. data/lib/legion/extensions/llm/ledger/actors/metering_writer.rb +25 -0
  9. data/lib/legion/extensions/llm/ledger/actors/prompt_writer.rb +25 -0
  10. data/lib/legion/extensions/llm/ledger/actors/spool_flush.rb +42 -0
  11. data/lib/legion/extensions/llm/ledger/actors/tool_writer.rb +25 -0
  12. data/lib/legion/extensions/llm/ledger/helpers/decryption.rb +50 -0
  13. data/lib/legion/extensions/llm/ledger/helpers/queries.rb +39 -0
  14. data/lib/legion/extensions/llm/ledger/helpers/retention.rb +75 -0
  15. data/lib/legion/extensions/llm/ledger/migrations/001_create_metering_records.rb +48 -0
  16. data/lib/legion/extensions/llm/ledger/migrations/002_create_prompt_records.rb +54 -0
  17. data/lib/legion/extensions/llm/ledger/migrations/003_create_tool_records.rb +47 -0
  18. data/lib/legion/extensions/llm/ledger/runners/metering.rb +65 -0
  19. data/lib/legion/extensions/llm/ledger/runners/prompts.rb +88 -0
  20. data/lib/legion/extensions/llm/ledger/runners/provider_stats.rb +63 -0
  21. data/lib/legion/extensions/llm/ledger/runners/tools.rb +79 -0
  22. data/lib/legion/extensions/llm/ledger/runners/usage_reporter.rb +82 -0
  23. data/lib/legion/extensions/llm/ledger/transport/exchanges/audit.rb +27 -0
  24. data/lib/legion/extensions/llm/ledger/transport/exchanges/metering.rb +27 -0
  25. data/lib/legion/extensions/llm/ledger/transport/queues/audit_prompts.rb +23 -0
  26. data/lib/legion/extensions/llm/ledger/transport/queues/audit_tools.rb +23 -0
  27. data/lib/legion/extensions/llm/ledger/transport/queues/metering_write.rb +23 -0
  28. data/lib/legion/extensions/llm/ledger/transport/transport.rb +39 -0
  29. data/lib/legion/extensions/llm/ledger/version.rb +11 -0
  30. data/lib/lex-llm-ledger.rb +42 -0
  31. metadata +229 -0
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ Sequel.migration do # rubocop:disable Metrics/BlockLength
4
+ change do
5
+ create_table(:metering_records) do
6
+ primary_key :id
7
+
8
+ String :message_id, null: false, unique: true
9
+ String :correlation_id, null: false
10
+ String :conversation_id, null: false
11
+ String :message_id_ctx, null: false
12
+ String :parent_message_id
13
+ Integer :message_seq
14
+ String :request_id, null: false
15
+ String :exchange_id
16
+ String :request_type, null: false
17
+ String :tier, null: false
18
+ String :provider, null: false
19
+ String :model_id, null: false
20
+ String :node_id, null: false
21
+ String :worker_id
22
+ String :agent_id
23
+ String :task_id
24
+ Integer :input_tokens, null: false, default: 0
25
+ Integer :output_tokens, null: false, default: 0
26
+ Integer :thinking_tokens, null: false, default: 0
27
+ Integer :total_tokens, null: false, default: 0
28
+ Integer :latency_ms, null: false, default: 0
29
+ Integer :wall_clock_ms, null: false, default: 0
30
+ Float :cost_usd, null: false, default: 0.0
31
+ String :routing_reason
32
+ String :cost_center
33
+ String :budget_id
34
+ String :recorded_at, null: false
35
+ DateTime :inserted_at, null: false, default: Sequel::CURRENT_TIMESTAMP
36
+
37
+ index [:conversation_id]
38
+ index [:request_id]
39
+ index [:message_id_ctx]
40
+ index [:correlation_id]
41
+ index %i[provider model_id]
42
+ index [:node_id]
43
+ index [:worker_id]
44
+ index [:recorded_at]
45
+ index %i[cost_center recorded_at]
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ Sequel.migration do # rubocop:disable Metrics/BlockLength
4
+ change do # rubocop:disable Metrics/BlockLength
5
+ create_table(:prompt_records) do # rubocop:disable Metrics/BlockLength
6
+ primary_key :id
7
+
8
+ String :message_id, null: false, unique: true
9
+ String :correlation_id, null: false
10
+ String :conversation_id, null: false
11
+ String :message_id_ctx, null: false
12
+ String :parent_message_id
13
+ Integer :message_seq
14
+ String :request_id, null: false
15
+ String :exchange_id
16
+ String :response_message_id
17
+ String :provider, null: false
18
+ String :model_id, null: false
19
+ String :tier
20
+ String :request_type
21
+ String :request_json, null: false, text: true
22
+ String :response_json, null: false, text: true
23
+ Integer :input_tokens, default: 0
24
+ Integer :output_tokens, default: 0
25
+ Integer :total_tokens, default: 0
26
+ Float :cost_usd, default: 0.0
27
+ String :caller_identity
28
+ String :caller_type
29
+ String :agent_id
30
+ String :task_id
31
+ String :classification_level
32
+ TrueClass :contains_phi, null: false, default: false
33
+ TrueClass :contains_pii, null: false, default: false
34
+ String :jurisdictions
35
+ Integer :quality_score
36
+ String :quality_band
37
+ String :retention_policy, null: false, default: 'default'
38
+ DateTime :expires_at
39
+ String :recorded_at, null: false
40
+ DateTime :inserted_at, null: false, default: Sequel::CURRENT_TIMESTAMP
41
+
42
+ index [:conversation_id]
43
+ index [:request_id]
44
+ index [:message_id_ctx]
45
+ index [:correlation_id]
46
+ index [:response_message_id]
47
+ index [:caller_identity]
48
+ index %i[provider model_id]
49
+ index [:contains_phi]
50
+ index [:expires_at]
51
+ index [:inserted_at]
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ Sequel.migration do # rubocop:disable Metrics/BlockLength
4
+ change do
5
+ create_table(:tool_records) do
6
+ primary_key :id
7
+
8
+ String :message_id, null: false, unique: true
9
+ String :correlation_id, null: false
10
+ String :conversation_id, null: false
11
+ String :message_id_ctx, null: false
12
+ String :parent_message_id
13
+ Integer :message_seq
14
+ String :request_id, null: false
15
+ String :exchange_id
16
+ String :tool_call_id, null: false
17
+ String :tool_name, null: false
18
+ String :tool_source_type
19
+ String :tool_source_server
20
+ String :tool_status, null: false
21
+ Integer :tool_duration_ms, default: 0
22
+ String :arguments_json, text: true
23
+ String :result_json, text: true
24
+ String :error_json, text: true
25
+ String :caller_identity
26
+ String :agent_id
27
+ String :classification_level
28
+ TrueClass :contains_phi, null: false, default: false
29
+ String :retention_policy, null: false, default: 'default'
30
+ DateTime :expires_at
31
+ String :tool_start_at
32
+ String :tool_end_at
33
+ DateTime :inserted_at, null: false, default: Sequel::CURRENT_TIMESTAMP
34
+
35
+ index [:conversation_id]
36
+ index [:request_id]
37
+ index [:message_id_ctx]
38
+ index [:correlation_id]
39
+ index [:tool_name]
40
+ index %i[tool_source_server tool_name]
41
+ index [:tool_status]
42
+ index [:contains_phi]
43
+ index [:expires_at]
44
+ index [:inserted_at]
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module LLM
6
+ module Ledger
7
+ module Runners
8
+ module Metering
9
+ extend self
10
+
11
+ def write_metering_record(payload, metadata = {})
12
+ ctx = payload[:message_context] || {}
13
+ props = metadata[:properties] || {}
14
+
15
+ record = build_metering_record(payload, ctx, props)
16
+ ::Legion::Data::DB[:metering_records].insert(record)
17
+ { result: :ok }
18
+ rescue Sequel::UniqueConstraintViolation => _e
19
+ { result: :duplicate }
20
+ rescue StandardError => e
21
+ Legion::Logging.error("[lex-llm-ledger] write_metering_record failed: #{e.message}") # rubocop:disable Legion/HelperMigration/DirectLogging
22
+ { result: :error, error: e.message }
23
+ end
24
+
25
+ private
26
+
27
+ def build_metering_record(payload, ctx, props)
28
+ billing = payload[:billing] || {}
29
+ {
30
+ message_id: props[:message_id],
31
+ correlation_id: props[:correlation_id],
32
+ conversation_id: ctx[:conversation_id],
33
+ message_id_ctx: ctx[:message_id],
34
+ parent_message_id: ctx[:parent_message_id],
35
+ message_seq: ctx[:message_seq],
36
+ request_id: ctx[:request_id],
37
+ exchange_id: ctx[:exchange_id],
38
+ request_type: payload[:request_type],
39
+ tier: payload[:tier],
40
+ provider: payload[:provider],
41
+ model_id: payload[:model_id],
42
+ node_id: payload[:node_id],
43
+ worker_id: payload[:worker_id],
44
+ agent_id: payload[:agent_id],
45
+ task_id: payload[:task_id],
46
+ input_tokens: payload[:input_tokens].to_i,
47
+ output_tokens: payload[:output_tokens].to_i,
48
+ thinking_tokens: payload[:thinking_tokens].to_i,
49
+ total_tokens: payload[:total_tokens].to_i,
50
+ latency_ms: payload[:latency_ms].to_i,
51
+ wall_clock_ms: payload[:wall_clock_ms].to_i,
52
+ cost_usd: payload[:cost_usd].to_f,
53
+ routing_reason: payload[:routing_reason],
54
+ cost_center: billing[:cost_center],
55
+ budget_id: billing[:budget_id],
56
+ recorded_at: payload[:recorded_at],
57
+ inserted_at: Time.now.utc
58
+ }
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module LLM
6
+ module Ledger
7
+ module Runners
8
+ module Prompts
9
+ extend self
10
+
11
+ def write_prompt_record(payload, metadata = {})
12
+ headers = metadata[:headers] || {}
13
+ props = metadata[:properties] || {}
14
+
15
+ body = Helpers::Decryption.decrypt_if_needed(payload, metadata)
16
+ ctx = body[:message_context] || {}
17
+
18
+ expires_at = Helpers::Retention.resolve(
19
+ retention: headers['x-legion-retention'],
20
+ contains_phi: headers['x-legion-contains-phi'] == 'true'
21
+ )
22
+
23
+ record = build_prompt_record(body, ctx, props, headers, expires_at)
24
+ ::Legion::Data::DB[:prompt_records].insert(record)
25
+ { result: :ok }
26
+ rescue Sequel::UniqueConstraintViolation => _e
27
+ { result: :duplicate }
28
+ rescue Helpers::DecryptionUnavailable => _e
29
+ raise
30
+ rescue StandardError => e
31
+ Legion::Logging.error("[lex-llm-ledger] write_prompt_record failed: #{e.message}") # rubocop:disable Legion/HelperMigration/DirectLogging
32
+ { result: :error, error: e.message }
33
+ end
34
+
35
+ private
36
+
37
+ def build_prompt_record(body, ctx, props, headers, expires_at)
38
+ routing = body[:routing] || {}
39
+ tokens = body[:tokens] || {}
40
+ cost = body[:cost] || {}
41
+ caller = body.dig(:caller, :requested_by) || {}
42
+ agent = body[:agent] || {}
43
+ cls = body[:classification] || {}
44
+ quality = body[:quality] || {}
45
+ ts = body[:timestamps] || {}
46
+
47
+ {
48
+ message_id: props[:message_id],
49
+ correlation_id: props[:correlation_id],
50
+ conversation_id: ctx[:conversation_id],
51
+ message_id_ctx: ctx[:message_id],
52
+ parent_message_id: ctx[:parent_message_id],
53
+ message_seq: ctx[:message_seq],
54
+ request_id: ctx[:request_id],
55
+ exchange_id: ctx[:exchange_id],
56
+ response_message_id: body[:response_message_id],
57
+ provider: routing[:provider],
58
+ model_id: routing[:model],
59
+ tier: routing[:tier],
60
+ request_type: headers['x-legion-llm-request-type'],
61
+ request_json: Legion::JSON.dump(body[:request] || {}), # rubocop:disable Legion/HelperMigration/DirectJson
62
+ response_json: Legion::JSON.dump(body[:response] || {}), # rubocop:disable Legion/HelperMigration/DirectJson
63
+ input_tokens: tokens[:input].to_i,
64
+ output_tokens: tokens[:output].to_i,
65
+ total_tokens: tokens[:total].to_i,
66
+ cost_usd: cost[:estimated_usd].to_f,
67
+ caller_identity: caller[:identity],
68
+ caller_type: caller[:type],
69
+ agent_id: agent[:id],
70
+ task_id: agent[:task_id],
71
+ classification_level: cls[:level] || headers['x-legion-classification'],
72
+ contains_phi: Helpers::Queries.phi_flag?(cls, headers),
73
+ contains_pii: cls[:contains_pii] ? true : false,
74
+ jurisdictions: Array(cls[:jurisdictions]).join(','),
75
+ quality_score: quality[:score],
76
+ quality_band: quality[:band],
77
+ retention_policy: headers['x-legion-retention'] || 'default',
78
+ expires_at: expires_at,
79
+ recorded_at: ts[:returned] || ts[:provider_end],
80
+ inserted_at: Time.now.utc
81
+ }
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module LLM
6
+ module Ledger
7
+ module Runners
8
+ module ProviderStats
9
+ extend self # rubocop:disable Style/ModuleFunction
10
+
11
+ def health_report
12
+ ds = ::Legion::Data::DB[:metering_records]
13
+ .where { inserted_at >= Time.now.utc - 86_400 }
14
+ .select(
15
+ :provider,
16
+ Sequel.function(:COUNT, Sequel.lit('*')).as(:request_count),
17
+ Sequel.function(:SUM, :total_tokens).as(:total_tokens),
18
+ Sequel.function(:AVG, :latency_ms).as(:avg_latency_ms),
19
+ Sequel.function(:MAX, :latency_ms).as(:max_latency_ms)
20
+ )
21
+ .group(:provider)
22
+ .all
23
+
24
+ ds.map { |row| row.merge(status: Helpers::Queries.latency_status(row[:avg_latency_ms])) }
25
+ end
26
+
27
+ def circuit_summary(period: 'hour')
28
+ since = Helpers::Queries.period_start(period)
29
+ ::Legion::Data::DB[:metering_records]
30
+ .where { inserted_at >= since }
31
+ .select(
32
+ :provider, :tier,
33
+ Sequel.function(:COUNT, Sequel.lit('*')).as(:request_count),
34
+ Sequel.function(:AVG, :latency_ms).as(:avg_latency_ms),
35
+ Sequel.function(:SUM, :cost_usd).as(:cost_usd)
36
+ )
37
+ .group(:provider, :tier)
38
+ .order(Sequel.desc(:request_count))
39
+ .all
40
+ end
41
+
42
+ def provider_detail(provider:, period: 'day')
43
+ since = Helpers::Queries.period_start(period)
44
+ ::Legion::Data::DB[:metering_records]
45
+ .where(provider: provider)
46
+ .where { inserted_at >= since }
47
+ .select(
48
+ :model_id, :request_type,
49
+ Sequel.function(:COUNT, Sequel.lit('*')).as(:count),
50
+ Sequel.function(:SUM, :total_tokens).as(:total_tokens),
51
+ Sequel.function(:AVG, :latency_ms).as(:avg_latency_ms),
52
+ Sequel.function(:SUM, :cost_usd).as(:cost_usd)
53
+ )
54
+ .group(:model_id, :request_type)
55
+ .order(Sequel.desc(:count))
56
+ .all
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module LLM
6
+ module Ledger
7
+ module Runners
8
+ module Tools
9
+ extend self
10
+
11
+ def write_tool_record(payload, metadata = {})
12
+ headers = metadata[:headers] || {}
13
+ props = metadata[:properties] || {}
14
+
15
+ body = Helpers::Decryption.decrypt_if_needed(payload, metadata)
16
+ ctx = body[:message_context] || {}
17
+ tool = body[:tool_call] || {}
18
+
19
+ expires_at = Helpers::Retention.resolve(
20
+ retention: headers['x-legion-retention'],
21
+ contains_phi: headers['x-legion-contains-phi'] == 'true'
22
+ )
23
+
24
+ record = build_tool_record(body, ctx, tool, props, headers, expires_at)
25
+ ::Legion::Data::DB[:tool_records].insert(record)
26
+ { result: :ok }
27
+ rescue Sequel::UniqueConstraintViolation => _e
28
+ { result: :duplicate }
29
+ rescue Helpers::DecryptionUnavailable => _e
30
+ raise
31
+ rescue StandardError => e
32
+ Legion::Logging.error("[lex-llm-ledger] write_tool_record failed: #{e.message}") # rubocop:disable Legion/HelperMigration/DirectLogging
33
+ { result: :error, error: e.message }
34
+ end
35
+
36
+ private
37
+
38
+ def build_tool_record(body, ctx, tool, props, headers, expires_at)
39
+ src = tool[:source] || {}
40
+ cls = body[:classification] || {}
41
+ ts = body[:timestamps] || {}
42
+ caller = body.dig(:caller, :requested_by) || {}
43
+ agent = body[:agent] || {}
44
+
45
+ {
46
+ message_id: props[:message_id],
47
+ correlation_id: props[:correlation_id],
48
+ conversation_id: ctx[:conversation_id],
49
+ message_id_ctx: ctx[:message_id],
50
+ parent_message_id: ctx[:parent_message_id],
51
+ message_seq: ctx[:message_seq],
52
+ request_id: ctx[:request_id],
53
+ exchange_id: ctx[:exchange_id],
54
+ tool_call_id: tool[:id],
55
+ tool_name: tool[:name] || headers['x-legion-tool-name'],
56
+ tool_source_type: src[:type] || headers['x-legion-tool-source-type'],
57
+ tool_source_server: src[:server] || headers['x-legion-tool-source-server'],
58
+ tool_status: tool[:status] || headers['x-legion-tool-status'],
59
+ tool_duration_ms: tool[:duration_ms].to_i,
60
+ arguments_json: Legion::JSON.dump(tool[:arguments] || {}), # rubocop:disable Legion/HelperMigration/DirectJson
61
+ result_json: Legion::JSON.dump(tool[:result]), # rubocop:disable Legion/HelperMigration/DirectJson
62
+ error_json: Legion::JSON.dump(tool[:error]), # rubocop:disable Legion/HelperMigration/DirectJson
63
+ caller_identity: caller[:identity],
64
+ agent_id: agent[:id],
65
+ classification_level: cls[:level] || headers['x-legion-classification'],
66
+ contains_phi: Helpers::Queries.phi_flag?(cls, headers),
67
+ retention_policy: headers['x-legion-retention'] || 'default',
68
+ expires_at: expires_at,
69
+ tool_start_at: ts[:tool_start],
70
+ tool_end_at: ts[:tool_end],
71
+ inserted_at: Time.now.utc
72
+ }
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module LLM
6
+ module Ledger
7
+ module Runners
8
+ module UsageReporter
9
+ extend self
10
+
11
+ def summary(since: nil, until_: nil, period: nil, group_by: nil)
12
+ dataset = ::Legion::Data::DB[:metering_records]
13
+ dataset = apply_time_window(dataset, since, until_, period)
14
+ dataset = dataset.group_and_count(group_by.to_sym) if group_by
15
+ dataset.select_append(
16
+ Sequel.function(:SUM, :input_tokens).as(:total_input_tokens),
17
+ Sequel.function(:SUM, :output_tokens).as(:total_output_tokens),
18
+ Sequel.function(:SUM, :total_tokens).as(:grand_total_tokens),
19
+ Sequel.function(:SUM, :cost_usd).as(:total_cost_usd),
20
+ Sequel.function(:AVG, :latency_ms).as(:avg_latency_ms),
21
+ Sequel.function(:COUNT, Sequel.lit('*')).as(:request_count)
22
+ ).all
23
+ end
24
+
25
+ def worker_usage(worker_id:, since: nil, until_: nil, period: nil)
26
+ dataset = ::Legion::Data::DB[:metering_records].where(worker_id: worker_id)
27
+ dataset = apply_time_window(dataset, since, until_, period)
28
+ dataset.select(
29
+ :provider, :model_id, :request_type,
30
+ Sequel.function(:SUM, :total_tokens).as(:total_tokens),
31
+ Sequel.function(:SUM, :cost_usd).as(:cost_usd),
32
+ Sequel.function(:COUNT, Sequel.lit('*')).as(:count)
33
+ ).group(:provider, :model_id, :request_type).all
34
+ end
35
+
36
+ def budget_check(budget_id:, budget_usd:, threshold: 0.8, period: 'month')
37
+ dataset = ::Legion::Data::DB[:metering_records].where(budget_id: budget_id)
38
+ dataset = apply_time_window(dataset, nil, nil, period)
39
+ spent = dataset.sum(:cost_usd).to_f
40
+
41
+ {
42
+ budget_id: budget_id,
43
+ budget_usd: budget_usd.to_f,
44
+ spent_usd: spent,
45
+ remaining_usd: [budget_usd.to_f - spent, 0.0].max,
46
+ exceeded: spent > budget_usd.to_f,
47
+ threshold_reached: spent >= (budget_usd.to_f * threshold.to_f)
48
+ }
49
+ end
50
+
51
+ def top_consumers(limit: 10, group_by: 'node_id', since: nil, until_: nil, period: 'day')
52
+ col = group_by.to_sym
53
+ dataset = ::Legion::Data::DB[:metering_records]
54
+ dataset = apply_time_window(dataset, since, until_, period)
55
+ dataset.select(
56
+ col,
57
+ Sequel.function(:SUM, :total_tokens).as(:total_tokens),
58
+ Sequel.function(:SUM, :cost_usd).as(:cost_usd),
59
+ Sequel.function(:COUNT, Sequel.lit('*')).as(:request_count)
60
+ ).group(col)
61
+ .order(Sequel.desc(:cost_usd))
62
+ .limit(limit)
63
+ .all
64
+ end
65
+
66
+ private
67
+
68
+ def apply_time_window(dataset, since, until_, period)
69
+ if period
70
+ since = Helpers::Queries.period_start(period)
71
+ until_ = Time.now.utc
72
+ end
73
+ dataset = dataset.where { inserted_at >= since } if since
74
+ dataset = dataset.where { inserted_at <= until_ } if until_
75
+ dataset
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module LLM
6
+ module Ledger
7
+ module Transport
8
+ module Exchanges
9
+ class Audit < Legion::Transport::Exchange
10
+ def exchange_name
11
+ 'llm.audit'
12
+ end
13
+
14
+ def default_type
15
+ 'topic'
16
+ end
17
+
18
+ def passive?
19
+ true
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module LLM
6
+ module Ledger
7
+ module Transport
8
+ module Exchanges
9
+ class Metering < Legion::Transport::Exchange
10
+ def exchange_name
11
+ 'llm.metering'
12
+ end
13
+
14
+ def default_type
15
+ 'topic'
16
+ end
17
+
18
+ def passive?
19
+ true
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module LLM
6
+ module Ledger
7
+ module Transport
8
+ module Queues
9
+ class AuditPrompts < Legion::Transport::Queue
10
+ def queue_name
11
+ 'llm.audit.prompts'
12
+ end
13
+
14
+ def queue_options
15
+ { durable: true }
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module LLM
6
+ module Ledger
7
+ module Transport
8
+ module Queues
9
+ class AuditTools < Legion::Transport::Queue
10
+ def queue_name
11
+ 'llm.audit.tools'
12
+ end
13
+
14
+ def queue_options
15
+ { durable: true }
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module LLM
6
+ module Ledger
7
+ module Transport
8
+ module Queues
9
+ class MeteringWrite < Legion::Transport::Queue
10
+ def queue_name
11
+ 'llm.metering.write'
12
+ end
13
+
14
+ def queue_options
15
+ { durable: true }
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end