lex-llm-mlx 0.1.0 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c512e2029e604ccf9d22750bdfb8227a0d0218588bdaa73b272f2a228418abe8
4
- data.tar.gz: 7e3dfc48bf564bbe514b2825c922dede26b6e426dd4a093101b3efee6827639f
3
+ metadata.gz: dbaecce67d7aabb1cf22242f37299766175bcf457c20705d19c063a4193a2ac5
4
+ data.tar.gz: 228d1dfc49a0e4c224924ae04a019ff318aac6796e7112e5a967fbf10a5be4a1
5
5
  SHA512:
6
- metadata.gz: 9288eb954e18c6d6a27ba7496a49545b4c95768fcc96db3e9fc242e57f2f28c05bacf62166505870ad6011a253c35afcab076e6a4899d37d35730ad64559a5fc
7
- data.tar.gz: f4d94b47cfb71cf108f6904f5232bd23315b83a3cee866535ec0e02cad7a35d580ccd4bd5b11748d74f929fd54650609d48c41be7addff3f82fe4a618addd7ef
6
+ metadata.gz: b77edb64a1aafdb185e6f6443e0e22ba3aeea26776c54ed75fc77e6a9f3a9664e44620bccee93c6bd730ebde9ff7723e717dac66e30906db3e434f3175049c42
7
+ data.tar.gz: 855d76ac2387ecbbfa3e01bded13e1cc23be634106a37d39d24467dfdc8b51d49b06ede73e91d57cc4288322cddbc2465ab70129b5929a581a891de5f71325b2
data/.gitignore CHANGED
@@ -2,6 +2,7 @@
2
2
  /coverage/
3
3
  /pkg/
4
4
  /tmp/
5
+ Gemfile.lock
5
6
 
6
7
  .rspec_status*
7
8
  .env
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.1 - 2026-04-27
4
+
5
+ - Add a local MLX OpenAI-compatible provider class with chat, streaming, model listing, embeddings, and health endpoint helpers.
6
+ - Move provider defaults to shared `lex-llm` settings construction and add shared Legion runtime dependencies.
7
+ - Remove the tracked Bundler lockfile from the provider gem.
8
+
3
9
  ## 0.1.0 - 2026-04-26
4
10
 
5
11
  - Initial Legion LLM Mlx provider extension scaffold.
data/Gemfile CHANGED
@@ -2,11 +2,13 @@
2
2
 
3
3
  source 'https://rubygems.org'
4
4
 
5
- if ENV['LEX_LLM_PATH'] && File.directory?(ENV.fetch('LEX_LLM_PATH'))
6
- gem 'lex-llm', path: ENV.fetch('LEX_LLM_PATH')
7
- else
8
- gem 'lex-llm', git: 'https://github.com/LegionIO/lex-llm',
9
- branch: ENV.fetch('LEX_LLM_BRANCH', 'lex-llm-routing-base-20260425')
5
+ group :test do
6
+ if ENV['LEX_LLM_PATH'] && File.directory?(ENV.fetch('LEX_LLM_PATH'))
7
+ gem 'lex-llm', path: ENV.fetch('LEX_LLM_PATH')
8
+ else
9
+ gem 'lex-llm', git: 'https://github.com/LegionIO/lex-llm',
10
+ branch: ENV.fetch('LEX_LLM_BRANCH', 'main')
11
+ end
10
12
  end
11
13
 
12
14
  gemspec
data/README.md CHANGED
@@ -1,5 +1,40 @@
1
1
  # lex-llm-mlx
2
2
 
3
- LegionIO LLM provider extension for Mlx.
3
+ LegionIO LLM provider extension for MLX-backed OpenAI-compatible servers on Apple Silicon.
4
4
 
5
5
  This gem lives under `Legion::Extensions::Llm::Mlx` and depends on `lex-llm` for shared provider-neutral routing, fleet, and schema primitives.
6
+
7
+ ## What It Provides
8
+
9
+ - `Legion::Extensions::Llm::Mlx::Provider`, registered as `:mlx`.
10
+ - OpenAI-compatible chat, streaming, model listing, and embeddings endpoint wrappers.
11
+ - Local-first defaults for MLX servers running on MacBook, Mac Studio, or local Apple Silicon hosts.
12
+ - Shared Legion settings, JSON, and logging dependencies.
13
+
14
+ ## Default Settings
15
+
16
+ ```ruby
17
+ Legion::Extensions::Llm::Mlx.default_settings
18
+ ```
19
+
20
+ Defaults target `http://localhost:8000`, mark the provider as `:local`, and allow one concurrent local request. Fleet participation stays disabled unless the host opts in through `Legion::Settings`.
21
+
22
+ ## Configuration
23
+
24
+ ```ruby
25
+ LexLLM.configure do |config|
26
+ config.mlx_api_base = 'http://localhost:8000'
27
+ config.mlx_api_key = ENV['MLX_API_KEY']
28
+ end
29
+ ```
30
+
31
+ `mlx_api_key` is optional because most local MLX servers run without authentication. Set it when a proxy or hosted MLX gateway requires bearer authentication.
32
+
33
+ ## Endpoint Helpers
34
+
35
+ - `completion_url` and `stream_url`: `/v1/chat/completions`
36
+ - `models_url`: `/v1/models`
37
+ - `embedding_url`: `/v1/embeddings`
38
+ - `health_url`: `/health`
39
+
40
+ The provider uses the shared `LexLLM::Provider::OpenAICompatible` adapter so Legion routing can treat MLX, vLLM, OpenAI, and other compatible servers consistently while preserving provider-specific settings and health behavior.
data/lex-llm-mlx.gemspec CHANGED
@@ -24,5 +24,8 @@ Gem::Specification.new do |spec|
24
24
  spec.require_paths = ['lib']
25
25
 
26
26
  spec.add_dependency 'faraday', '>= 2.0'
27
- spec.add_dependency 'lex-llm', '>= 0.1.0'
27
+ spec.add_dependency 'legion-json', '>= 1.0'
28
+ spec.add_dependency 'legion-logging', '>= 1.0'
29
+ spec.add_dependency 'legion-settings', '>= 1.3.14'
30
+ spec.add_dependency 'lex-llm', '>= 0.1.2'
28
31
  end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'lex_llm'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module Llm
8
+ module Mlx
9
+ # MLX provider implementation for local OpenAI-compatible servers.
10
+ class Provider < LexLLM::Provider
11
+ include LexLLM::Provider::OpenAICompatible
12
+
13
+ class << self
14
+ def slug = 'mlx'
15
+ def local? = true
16
+ def configuration_options = %i[mlx_api_base mlx_api_key]
17
+ def configuration_requirements = []
18
+ def capabilities = Capabilities
19
+ end
20
+
21
+ # Conservative capability predicates for local MLX OpenAI-compatible servers.
22
+ module Capabilities
23
+ module_function
24
+
25
+ def chat?(model) = !embeddings?(model)
26
+ def streaming?(model) = chat?(model)
27
+ def vision?(model) = model_id(model).match?(/vlm|vision|llava|pixtral|qwen.*vl/i)
28
+ def functions?(_model) = true
29
+ def embeddings?(model) = model_id(model).match?(/embed|bge|e5|nomic/i)
30
+
31
+ def model_id(model)
32
+ model.respond_to?(:id) ? model.id.to_s : model.to_s
33
+ end
34
+ end
35
+
36
+ def api_base
37
+ config.mlx_api_base || 'http://localhost:8000'
38
+ end
39
+
40
+ def headers
41
+ token = config.mlx_api_key
42
+ return {} if token.nil? || token.to_s.empty?
43
+
44
+ { 'Authorization' => "Bearer #{token}" }
45
+ end
46
+
47
+ def health_url = '/health'
48
+
49
+ def health
50
+ connection.get(health_url).body
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -4,7 +4,7 @@ module Legion
4
4
  module Extensions
5
5
  module Llm
6
6
  module Mlx
7
- VERSION = '0.1.0'
7
+ VERSION = '0.1.1'
8
8
  end
9
9
  end
10
10
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'legion/extensions/llm'
4
- require 'legion/extensions/llm/mlx/provider_settings'
4
+ require 'legion/extensions/llm/mlx/provider'
5
5
  require 'legion/extensions/llm/mlx/version'
6
6
 
7
7
  module Legion
@@ -14,18 +14,25 @@ module Legion
14
14
  PROVIDER_FAMILY = :mlx
15
15
 
16
16
  def self.default_settings
17
- ProviderSettings.build(
17
+ ::Legion::Extensions::Llm.provider_settings(
18
18
  family: PROVIDER_FAMILY,
19
19
  instance: {
20
- endpoint: nil,
20
+ endpoint: 'http://localhost:8000',
21
21
  tier: :local,
22
22
  transport: :local,
23
- usage: { inference: true, embedding: false },
23
+ usage: { inference: true, embedding: true },
24
24
  limits: { concurrency: 1 }
25
25
  }
26
26
  )
27
27
  end
28
+
29
+ def self.provider_class
30
+ Provider
31
+ end
28
32
  end
29
33
  end
30
34
  end
31
35
  end
36
+
37
+ LexLLM::Provider.register(Legion::Extensions::Llm::Mlx::PROVIDER_FAMILY,
38
+ Legion::Extensions::Llm::Mlx::Provider)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lex-llm-mlx
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - LegionIO
@@ -23,20 +23,62 @@ dependencies:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
25
  version: '2.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: legion-json
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '1.0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '1.0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: legion-logging
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '1.0'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '1.0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: legion-settings
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: 1.3.14
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: 1.3.14
26
68
  - !ruby/object:Gem::Dependency
27
69
  name: lex-llm
28
70
  requirement: !ruby/object:Gem::Requirement
29
71
  requirements:
30
72
  - - ">="
31
73
  - !ruby/object:Gem::Version
32
- version: 0.1.0
74
+ version: 0.1.2
33
75
  type: :runtime
34
76
  prerelease: false
35
77
  version_requirements: !ruby/object:Gem::Requirement
36
78
  requirements:
37
79
  - - ">="
38
80
  - !ruby/object:Gem::Version
39
- version: 0.1.0
81
+ version: 0.1.2
40
82
  description: Mlx provider integration for the LegionIO LLM routing framework.
41
83
  email:
42
84
  - matthewdiverson@gmail.com
@@ -51,12 +93,11 @@ files:
51
93
  - ".rubocop.yml"
52
94
  - CHANGELOG.md
53
95
  - Gemfile
54
- - Gemfile.lock
55
96
  - LICENSE
56
97
  - README.md
57
98
  - lex-llm-mlx.gemspec
58
99
  - lib/legion/extensions/llm/mlx.rb
59
- - lib/legion/extensions/llm/mlx/provider_settings.rb
100
+ - lib/legion/extensions/llm/mlx/provider.rb
60
101
  - lib/legion/extensions/llm/mlx/version.rb
61
102
  - lib/lex_llm_mlx.rb
62
103
  homepage: https://github.com/LegionIO/lex-llm-mlx
data/Gemfile.lock DELETED
@@ -1,161 +0,0 @@
1
- GIT
2
- remote: https://github.com/LegionIO/lex-llm
3
- revision: 6b2901bf332806ca369399ad08f09da82825b121
4
- branch: lex-llm-routing-base-20260425
5
- specs:
6
- lex-llm (0.1.0)
7
- base64
8
- event_stream_parser (~> 1)
9
- faraday (>= 1.10.0)
10
- faraday-multipart (>= 1)
11
- faraday-net_http (>= 1)
12
- faraday-retry (>= 1)
13
- marcel (~> 1)
14
- ruby_llm-schema (~> 0)
15
- zeitwerk (~> 2)
16
-
17
- PATH
18
- remote: .
19
- specs:
20
- lex-llm-mlx (0.1.0)
21
- faraday (>= 2.0)
22
- lex-llm (>= 0.1.0)
23
-
24
- GEM
25
- remote: https://rubygems.org/
26
- specs:
27
- ast (2.4.3)
28
- base64 (0.3.0)
29
- diff-lcs (1.6.2)
30
- event_stream_parser (1.0.0)
31
- faraday (2.14.1)
32
- faraday-net_http (>= 2.0, < 3.5)
33
- json
34
- logger
35
- faraday-multipart (1.2.0)
36
- multipart-post (~> 2.0)
37
- faraday-net_http (3.4.2)
38
- net-http (~> 0.5)
39
- faraday-retry (2.4.0)
40
- faraday (~> 2.0)
41
- json (2.19.4)
42
- language_server-protocol (3.17.0.5)
43
- lint_roller (1.1.0)
44
- logger (1.7.0)
45
- marcel (1.1.0)
46
- multipart-post (2.4.1)
47
- net-http (0.9.1)
48
- uri (>= 0.11.1)
49
- parallel (2.1.0)
50
- parser (3.3.11.1)
51
- ast (~> 2.4.1)
52
- racc
53
- prism (1.9.0)
54
- racc (1.8.1)
55
- rainbow (3.1.1)
56
- rake (13.4.2)
57
- regexp_parser (2.12.0)
58
- rspec (3.13.2)
59
- rspec-core (~> 3.13.0)
60
- rspec-expectations (~> 3.13.0)
61
- rspec-mocks (~> 3.13.0)
62
- rspec-core (3.13.6)
63
- rspec-support (~> 3.13.0)
64
- rspec-expectations (3.13.5)
65
- diff-lcs (>= 1.2.0, < 2.0)
66
- rspec-support (~> 3.13.0)
67
- rspec-mocks (3.13.8)
68
- diff-lcs (>= 1.2.0, < 2.0)
69
- rspec-support (~> 3.13.0)
70
- rspec-support (3.13.7)
71
- rubocop (1.86.1)
72
- json (~> 2.3)
73
- language_server-protocol (~> 3.17.0.2)
74
- lint_roller (~> 1.1.0)
75
- parallel (>= 1.10)
76
- parser (>= 3.3.0.2)
77
- rainbow (>= 2.2.2, < 4.0)
78
- regexp_parser (>= 2.9.3, < 3.0)
79
- rubocop-ast (>= 1.49.0, < 2.0)
80
- ruby-progressbar (~> 1.7)
81
- unicode-display_width (>= 2.4.0, < 4.0)
82
- rubocop-ast (1.49.1)
83
- parser (>= 3.3.7.2)
84
- prism (~> 1.7)
85
- rubocop-performance (1.26.1)
86
- lint_roller (~> 1.1)
87
- rubocop (>= 1.75.0, < 2.0)
88
- rubocop-ast (>= 1.47.1, < 2.0)
89
- rubocop-rake (0.7.1)
90
- lint_roller (~> 1.1)
91
- rubocop (>= 1.72.1)
92
- rubocop-rspec (3.9.0)
93
- lint_roller (~> 1.1)
94
- rubocop (~> 1.81)
95
- ruby-progressbar (1.13.0)
96
- ruby_llm-schema (0.3.0)
97
- unicode-display_width (3.2.0)
98
- unicode-emoji (~> 4.1)
99
- unicode-emoji (4.2.0)
100
- uri (1.1.1)
101
- zeitwerk (2.7.5)
102
-
103
- PLATFORMS
104
- arm64-darwin-25
105
- ruby
106
-
107
- DEPENDENCIES
108
- bundler (>= 2.0)
109
- lex-llm!
110
- lex-llm-mlx!
111
- rake (>= 13.0)
112
- rspec (~> 3.12)
113
- rubocop (>= 1.0)
114
- rubocop-performance
115
- rubocop-rake (>= 0.6)
116
- rubocop-rspec
117
-
118
- CHECKSUMS
119
- ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383
120
- base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
121
- diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962
122
- event_stream_parser (1.0.0) sha256=a2683bab70126286f8184dc88f7968ffc4028f813161fb073ec90d171f7de3c8
123
- faraday (2.14.1) sha256=a43cceedc1e39d188f4d2cdd360a8aaa6a11da0c407052e426ba8d3fb42ef61c
124
- faraday-multipart (1.2.0) sha256=7d89a949693714176f612323ca13746a2ded204031a6ba528adee788694ef757
125
- faraday-net_http (3.4.2) sha256=f147758260d3526939bf57ecf911682f94926a3666502e24c69992765875906c
126
- faraday-retry (2.4.0) sha256=7b79c48fb7e56526faf247b12d94a680071ff40c9fda7cf1ec1549439ad11ebe
127
- json (2.19.4) sha256=670a7d333fb3b18ca5b29cb255eb7bef099e40d88c02c80bd42a3f30fe5239ac
128
- language_server-protocol (3.17.0.5) sha256=fd1e39a51a28bf3eec959379985a72e296e9f9acfce46f6a79d31ca8760803cc
129
- lex-llm (0.1.0)
130
- lex-llm-mlx (0.1.0)
131
- lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87
132
- logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
133
- marcel (1.1.0) sha256=fdcfcfa33cc52e93c4308d40e4090a5d4ea279e160a7f6af988260fa970e0bee
134
- multipart-post (2.4.1) sha256=9872d03a8e552020ca096adadbf5e3cb1cd1cdd6acd3c161136b8a5737cdb4a8
135
- net-http (0.9.1) sha256=25ba0b67c63e89df626ed8fac771d0ad24ad151a858af2cc8e6a716ca4336996
136
- parallel (2.1.0) sha256=b35258865c2e31134c5ecb708beaaf6772adf9d5efae28e93e99260877b09356
137
- parser (3.3.11.1) sha256=d17ace7aabe3e72c3cc94043714be27cc6f852f104d81aa284c2281aecc65d54
138
- prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85
139
- racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
140
- rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
141
- rake (13.4.2) sha256=cb825b2bd5f1f8e91ca37bddb4b9aaf345551b4731da62949be002fa89283701
142
- regexp_parser (2.12.0) sha256=35a916a1d63190ab5c9009457136ae5f3c0c7512d60291d0d1378ba18ce08ebb
143
- rspec (3.13.2) sha256=206284a08ad798e61f86d7ca3e376718d52c0bc944626b2349266f239f820587
144
- rspec-core (3.13.6) sha256=a8823c6411667b60a8bca135364351dda34cd55e44ff94c4be4633b37d828b2d
145
- rspec-expectations (3.13.5) sha256=33a4d3a1d95060aea4c94e9f237030a8f9eae5615e9bd85718fe3a09e4b58836
146
- rspec-mocks (3.13.8) sha256=086ad3d3d17533f4237643de0b5c42f04b66348c28bf6b9c2d3f4a3b01af1d47
147
- rspec-support (3.13.7) sha256=0640e5570872aafefd79867901deeeeb40b0c9875a36b983d85f54fb7381c47c
148
- rubocop (1.86.1) sha256=44415f3f01d01a21e01132248d2fd0867572475b566ca188a0a42133a08d4531
149
- rubocop-ast (1.49.1) sha256=4412f3ee70f6fe4546cc489548e0f6fcf76cafcfa80fa03af67098ffed755035
150
- rubocop-performance (1.26.1) sha256=cd19b936ff196df85829d264b522fd4f98b6c89ad271fa52744a8c11b8f71834
151
- rubocop-rake (0.7.1) sha256=3797f2b6810c3e9df7376c26d5f44f3475eda59eb1adc38e6f62ecf027cbae4d
152
- rubocop-rspec (3.9.0) sha256=8fa70a3619408237d789aeecfb9beef40576acc855173e60939d63332fdb55e2
153
- ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33
154
- ruby_llm-schema (0.3.0) sha256=a591edc5ca1b7f0304f0e2261de61ba4b3bea17be09f5cf7558153adfda3dec6
155
- unicode-display_width (3.2.0) sha256=0cdd96b5681a5949cdbc2c55e7b420facae74c4aaf9a9815eee1087cb1853c42
156
- unicode-emoji (4.2.0) sha256=519e69150f75652e40bf736106cfbc8f0f73aa3fb6a65afe62fefa7f80b0f80f
157
- uri (1.1.1) sha256=379fa58d27ffb1387eaada68c749d1426738bd0f654d812fcc07e7568f5c57c6
158
- zeitwerk (2.7.5) sha256=d8da92128c09ea6ec62c949011b00ed4a20242b255293dd66bf41545398f73dd
159
-
160
- BUNDLED WITH
161
- 4.0.6
@@ -1,55 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Legion
4
- module Extensions
5
- module Llm
6
- module Mlx
7
- # Builds provider defaults while inheriting shared lex-llm defaults.
8
- module ProviderSettings
9
- module_function
10
-
11
- def build(family:, instance:)
12
- deep_merge(
13
- base_settings,
14
- {
15
- enabled: true,
16
- provider_family: family,
17
- discovery: { enabled: true, interval_seconds: 300 },
18
- instances: {
19
- default: {
20
- enabled: true,
21
- credentials: nil,
22
- fleet: { enabled: false, consumer_priority: 0, prefetch: 1 }
23
- }.merge(instance)
24
- }
25
- }
26
- )
27
- end
28
-
29
- def base_settings
30
- return {} unless ::Legion::Extensions::Llm.respond_to?(:default_settings)
31
-
32
- deep_dup(::Legion::Extensions::Llm.default_settings)
33
- end
34
-
35
- def deep_dup(value)
36
- case value
37
- when Hash
38
- value.to_h { |key, inner_value| [key, deep_dup(inner_value)] }
39
- when Array
40
- value.map { |inner_value| deep_dup(inner_value) }
41
- else
42
- value
43
- end
44
- end
45
-
46
- def deep_merge(left, right)
47
- left.merge(right) do |_key, left_value, right_value|
48
- left_value.is_a?(Hash) && right_value.is_a?(Hash) ? deep_merge(left_value, right_value) : right_value
49
- end
50
- end
51
- end
52
- end
53
- end
54
- end
55
- end