legion-llm 0.14.4 → 0.14.8

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: 433b9bf3832cc3c6fcb25ab53d135306475b4147a242af49a3df5fe52eab9946
4
- data.tar.gz: 94873f17f47ef2c08cab12e8ad6de884af7bb108bdb7967cf30df5b32c21234e
3
+ metadata.gz: b12fc37520233aba6bc8b063e0334e5e1abb74970c5ba6a2efd9094bbd3414cf
4
+ data.tar.gz: f52cdb8f4cc7c7c2e67e0a6c8caa55fb842f736418955c4ec20d89a0b677212a
5
5
  SHA512:
6
- metadata.gz: 8c96394a77c183d9441d3b56461c0d1303dc93823e2ca87bb68e8e21e6903a484fc36e7242ef297ea9312e4de9d36ec5abfcbd36a8b7966566d98d123ed85085
7
- data.tar.gz: b3bb6181d62ef27e6c812d34f3c9efc6b540e041025324e92fe7d199cfe28f3a619baa20a4dd5bde1eea7153afa0420d98332bcf96c941923e0d15afe563c722
6
+ metadata.gz: 598310ad96bad4d54ac02802a6d981294d5fb1faec31390fa6375e378f936f47f2336027255e0ca5c49f6db199b606528c8e96b70112325e41ccbb346245db11
7
+ data.tar.gz: 68c89f512cea238a856fdc5930155a7018cb095573f0de5de50bce8f03bb662c2b67abdd0f653a7f201139e3412806beef631f1f46dd80f065c18c0a496c489b
data/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # Legion LLM Changelog
2
2
 
3
+ ## [0.14.8] - 2026-06-25
4
+
5
+ ### Fixed
6
+
7
+ - Responses translator (`format_response`) no longer emits a message item with empty `output_text` when the response is pure tool_use (function_call items present). The empty text triggered Codex's `[Your previous response had no visible output]` injection on every tool turn.
8
+
9
+ ## [0.14.7] - 2026-06-25
10
+
11
+ ### Fixed
12
+
13
+ - `step_context_store` stores `typed_msg.text` (extracted string) instead of `typed_msg.content` (raw Array), preventing ContentBlock objects from being serialized as `#inspect` strings in conversation history.
14
+ - `Types::Message#text_from_block` recognizes `output_text`/`input_text` content block types so token estimation and text extraction work for Responses API content.
15
+ - `lex_llm_adapter.rb` `text_part_content` Data struct branch handles `output_text`/`input_text` types (previously only matched `type == 'text'`).
16
+ - `context_window.rb` `compact_to_fit` loops halving until messages fit the target token budget instead of a single halve that leaves payloads 2x over threshold for very large conversations.
17
+ - `emit_non_pipeline_metering` reads tokens from `response.usage.input_tokens` (correct) instead of `response.input_tokens` (non-existent on Canonical::Response), fixing zero-token metering for internal extension LLM calls (lex-apollo, legion-gaia, lex-knowledge, etc.).
18
+ - `emit_non_pipeline_metering` reads `response.metadata` instead of `response.meta`, passes `messages` and `response_content` to metering events.
19
+
20
+ ## [0.14.5] - 2026-06-24
21
+
22
+ ### Fixed
23
+
24
+ - Non-pipeline metering (`emit_non_pipeline_metering`) now correctly reads token counts from `response.usage` instead of the non-existent top-level `input_tokens`/`output_tokens` on `Canonical::Response`. Previously all internal LLM calls (via `Legion::LLM.structured`, `chat_single_native`) emitted zero-token metering events.
25
+ - Non-pipeline metering now includes `messages` and `response_content` fields, fixing null request/response JSON columns in the ledger for internal extension calls (lex-apollo, lex-agentic-self, legion-gaia, etc.).
26
+ - Non-pipeline metering reads `response.metadata` (correct field) instead of `response.meta` (does not exist on `Canonical::Response`) for latency/timing extraction.
27
+
3
28
  ## [0.14.4] - 2026-06-23
4
29
 
5
30
  ### Fixed
data/legion-llm.gemspec CHANGED
@@ -34,7 +34,7 @@ Gem::Specification.new do |spec|
34
34
  spec.add_dependency 'legion-settings', '>= 1.4.0'
35
35
  spec.add_dependency 'legion-transport', '>= 1.4.14'
36
36
  spec.add_dependency 'lex-knowledge'
37
- spec.add_dependency 'lex-llm', '>= 0.6.0'
37
+ spec.add_dependency 'lex-llm', '>= 0.6.3'
38
38
  spec.add_dependency 'pdf-reader'
39
39
  spec.add_dependency 'sinatra-contrib', '>= 2.0'
40
40
  spec.add_dependency 'tzinfo', '>= 2.0'
@@ -151,18 +151,16 @@ module Legion
151
151
  server_tool_items = build_output_server_tool_items(pipeline_response)
152
152
  reasoning = build_output_reasoning(pipeline_response)
153
153
 
154
- output = [
155
- *reasoning,
156
- *server_tool_items,
157
- *actionable_tool_calls,
158
- {
154
+ output = [*reasoning, *server_tool_items, *actionable_tool_calls]
155
+ unless content.to_s.strip.empty? && (actionable_tool_calls.any? || server_tool_items.any?)
156
+ output << {
159
157
  type: 'message',
160
158
  id: "msg_#{SecureRandom.hex(12)}",
161
159
  role: 'assistant',
162
160
  content: [{ type: 'output_text', text: content }],
163
161
  status: 'completed'
164
162
  }
165
- ]
163
+ end
166
164
 
167
165
  # Responses protocol: a turn is always status `completed`. Both
168
166
  # client-callable calls (actionable_tool_calls) and LegionIO-run
@@ -753,7 +753,7 @@ module Legion
753
753
  if part.respond_to?(:type) || part.respond_to?(:text)
754
754
  type = (part.respond_to?(:type) ? part.type.to_s : '')
755
755
  text = part.respond_to?(:text) ? part.text : nil
756
- return text.to_s if type == 'text' || (type.empty? && !text.nil?)
756
+ return text.to_s if %w[text output_text input_text].include?(type) || (type.empty? && !text.nil?)
757
757
 
758
758
  return nil
759
759
  end
@@ -83,8 +83,8 @@ module Legion
83
83
 
84
84
  return messages if estimate_message_tokens(messages) <= target_tokens
85
85
 
86
- half = messages.size / 2
87
- messages.last(half)
86
+ messages = messages.last(messages.size / 2) while messages.size > 2 && estimate_message_tokens(messages) > target_tokens
87
+ messages
88
88
  end
89
89
 
90
90
  def resolved_context_window
@@ -947,7 +947,7 @@ module Legion
947
947
 
948
948
  attrs = {
949
949
  role: typed_msg.role,
950
- content: typed_msg.content,
950
+ content: typed_msg.text,
951
951
  conversation_id: conv_id,
952
952
  task_id: typed_msg.task_id
953
953
  }
@@ -605,7 +605,7 @@ module Legion
605
605
  Call::Dispatch.call(provider: provider, instance: instance, capability: :chat, model: model,
606
606
  messages: messages, **)
607
607
  end
608
- emit_non_pipeline_metering(result, model: model, provider: provider, caller: caller)
608
+ emit_non_pipeline_metering(result, model: model, provider: provider, caller: caller, messages: messages)
609
609
  result
610
610
  end
611
611
 
@@ -890,14 +890,13 @@ module Legion
890
890
  esc[:quality_threshold] || 50
891
891
  end
892
892
 
893
- def emit_non_pipeline_metering(response, model:, provider:, caller: nil)
893
+ def emit_non_pipeline_metering(response, model:, provider:, caller: nil, messages: nil)
894
894
  return unless response
895
895
 
896
- input = response.respond_to?(:input_tokens) ? response.input_tokens.to_i : 0
897
- output = response.respond_to?(:output_tokens) ? response.output_tokens.to_i : 0
898
- usage = response.respond_to?(:usage) ? response.usage : {}
899
- usage_hash = usage.is_a?(Hash) ? usage : {}
900
- thinking = (usage_hash[:thinking_tokens] || usage_hash[:thinking] || 0).to_i
896
+ usage = response.respond_to?(:usage) ? response.usage : nil
897
+ input = usage.respond_to?(:input_tokens) ? usage.input_tokens.to_i : 0
898
+ output = usage.respond_to?(:output_tokens) ? usage.output_tokens.to_i : 0
899
+ thinking = usage.respond_to?(:thinking_tokens) ? usage.thinking_tokens.to_i : 0
901
900
 
902
901
  finish = nil
903
902
  if response.respond_to?(:stop_reason)
@@ -906,11 +905,17 @@ module Legion
906
905
  finish = response.stop[:reason]&.to_s
907
906
  end
908
907
 
909
- meta = response.respond_to?(:meta) ? response.meta : {}
908
+ meta = response.respond_to?(:metadata) ? response.metadata : {}
910
909
  meta_hash = meta.is_a?(Hash) ? meta : {}
911
910
  latency = (meta_hash[:latency_ms] || meta_hash.dig(:timing, :latency_ms) || 0).to_i
912
911
  wall_clock = (meta_hash[:wall_clock_ms] || meta_hash.dig(:timing, :wall_clock_ms) || 0).to_i
913
912
 
913
+ response_content = if response.respond_to?(:text)
914
+ response.text
915
+ elsif response.respond_to?(:content)
916
+ response.content
917
+ end
918
+
914
919
  Legion::LLM::Metering.emit(
915
920
  provider: provider,
916
921
  model_id: model,
@@ -926,7 +931,9 @@ module Legion
926
931
  wall_clock_ms: wall_clock,
927
932
  caller: caller,
928
933
  event_type: 'llm_completion',
929
- status: 'success'
934
+ status: 'success',
935
+ messages: messages,
936
+ response_content: response_content
930
937
  )
931
938
  rescue StandardError => e
932
939
  handle_exception(e, level: :warn, operation: 'llm.inference.non_pipeline_metering')
@@ -93,7 +93,7 @@ module Legion
93
93
  return nil unless block.is_a?(Hash)
94
94
 
95
95
  type = block[:type] || block['type']
96
- return nil unless type.nil? || type.to_s == 'text'
96
+ return nil unless type.nil? || %w[text output_text input_text].include?(type.to_s)
97
97
 
98
98
  block[:text] || block['text'] || block[:content] || block['content']
99
99
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Legion
4
4
  module LLM
5
- VERSION = '0.14.4'
5
+ VERSION = '0.14.8'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: legion-llm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.14.4
4
+ version: 0.14.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -141,14 +141,14 @@ dependencies:
141
141
  requirements:
142
142
  - - ">="
143
143
  - !ruby/object:Gem::Version
144
- version: 0.6.0
144
+ version: 0.6.3
145
145
  type: :runtime
146
146
  prerelease: false
147
147
  version_requirements: !ruby/object:Gem::Requirement
148
148
  requirements:
149
149
  - - ">="
150
150
  - !ruby/object:Gem::Version
151
- version: 0.6.0
151
+ version: 0.6.3
152
152
  - !ruby/object:Gem::Dependency
153
153
  name: pdf-reader
154
154
  requirement: !ruby/object:Gem::Requirement