activeagent 1.7.0 → 1.7.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4aee77fd4f67a50824b7153b66f31aa5f19293bad4397e032ec0a9e674a759d0
4
- data.tar.gz: e261e8ca9d221edbc849791ed28c67bc491ccc31e1e18f5c4cbf7a9a96a806c5
3
+ metadata.gz: 881ca4e27488bd719dda01c80e6241bf6d17082876039033c22f9265d1e559aa
4
+ data.tar.gz: 62526259e8a48d5243a480cf3ac6f795c1672b20e8569f7804fcf13af4d25ec4
5
5
  SHA512:
6
- metadata.gz: f5779f6207e07b1aec0ef14f72a5b010075b953c9025a12f6231ba164522c8b785cd9ca1bb297361abedc42aa535c468ef642978ec9fb439c79c9c0fe71ebd79
7
- data.tar.gz: 1d2720a47e5e8c2a6c17a74cff893f1da2e89dd430b295bdcda4b9bdfb966afa8e75fbf95c0f063f5bca0bc8406d782e08b99fa6bed99b20230fb793ac3e518b
6
+ metadata.gz: 0d135cc35623442cdf38df2d588e323fec82f7e16c7d9c8fcf90d2add91bbb55476e07fe548c70ddb2b22cc709e684ab8ccc53dd337c0650800f9d6dd268f547
7
+ data.tar.gz: 76a0b25ef5cf125e99399ff61cef131d2fe6ae6dade18173601f73f0ec7ecedde6bfc0738af99ac490444b1a067cd136dc2caac34cc1f1cd6c01996cbdddf841
data/CHANGELOG.md CHANGED
@@ -7,6 +7,104 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.7.2] - 2026-09-29
11
+
12
+ Releases `activeagent` and `actionagent` 1.7.2 from one tag. A patch on 1.7.1:
13
+ the RubyLLM provider runs on ruby_llm 1.16 and 2.x, where 1.7.1 required 1.x.
14
+ Apps on the RubyLLM provider need ruby_llm 1.16 or later; earlier 1.x
15
+ releases lack the APIs the provider calls. No migrations.
16
+
17
+ ### Changed
18
+
19
+ - **The RubyLLM provider supports ruby_llm 1.16 and 2.x** (`activeagent`).
20
+ The adapter handles 2.x's tool interface, token limits, embedding model
21
+ objects, usage and finish reasons while keeping the 1.16 API working.
22
+ Requiring `>= 1.16, < 3` prevents Bundler from selecting an older 1.x
23
+ release without the APIs the adapter calls. Rails main can now resolve
24
+ RubyLLM 2.x alongside Active Storage's Marcel 2 dependency. CI also runs
25
+ the full suite with RubyLLM 1.16 to retain coverage of that version.
26
+ Unsupported-version errors name the loaded version and both bounds (#508).
27
+
28
+ ## [1.7.1] - 2026-09-29
29
+
30
+ Releases `activeagent` and `actionagent` 1.7.1 from one tag. A patch on 1.7.0:
31
+ the Evaluations page picks the judge and compared models from the provider
32
+ catalogs, `GET /api/provider_models` lists the host's RubyLLM registry, and
33
+ the RubyLLM provider requires ruby_llm 1.x and sends tool calls and structured
34
+ output in the shape ruby_llm reads. No migrations.
35
+
36
+ ### Added
37
+
38
+ - **Model pickers on the Evaluations page** (`actionagent`). The judge model
39
+ and the models to compare, on the new-evaluation form and on a scenario
40
+ suite, are chosen from type-ahead suggestions, and a model the suggestions
41
+ lack can still be typed. The compare fields hold one removable chip per
42
+ model and still submit the same comma-separated list.
43
+ - With scenarios, the suggestions are the catalogs the agent builder uses,
44
+ for the providers the owner's runs have credentials for. A model whose
45
+ name alone would run elsewhere is offered with its provider in front, as
46
+ `ModelSpec.parse` reads it: `ollama/llama3.2`, or
47
+ `openrouter/anthropic/claude-sonnet-4.5` for OpenRouter's copy of an
48
+ Anthropic model.
49
+ - Without scenarios, a compared model selects the generations recorded
50
+ under its name, usually the provider's dated id, so the suggestions are
51
+ the names the agent's generations were recorded under, from the new
52
+ `GET /api/agents/:id/recorded_models`. Adding or clearing the scenarios
53
+ renames the catalog models already chosen to match.
54
+ - The judge field suggests the models of the provider the judge runs on
55
+ and says which provider that is, or that the credentials deciding it
56
+ could not be read.
57
+
58
+ `GET /api/evaluations` reports that provider as `judge_provider` and the
59
+ providers runs can use as `model_providers`
60
+ (`AgentExecutionService.available_providers`). Runs and their judge use
61
+ the evaluated agent's owner's credentials, so both fields describe that
62
+ owner's when `agent_id` scopes the list, and the signed-in owner's
63
+ otherwise. `judge_provider` is null when no provider has credentials, and
64
+ also when reading them raised, which `judge_provider_error: true` marks.
65
+ `model_providers` leaves out a provider whose credentials cannot be read.
66
+ Either way the list still loads.
67
+ - **`GET /api/provider_models` lists the host's RubyLLM registry**
68
+ (`actionagent`). When the host app loads RubyLLM, the chat models its
69
+ registry lists for the provider that take and return text (RubyLLM's
70
+ bundled catalog, or the host's own model table) follow the live or curated
71
+ list, each once, so the builder's preselected default is unchanged. That
72
+ leaves out the speech, transcription, moderation and completion-only
73
+ models RubyLLM counts as chat models, which its registry lists with no
74
+ modalities or as taking audio, and with them the few chat models it lists
75
+ with no modalities, which can still be typed. A registry that raises is
76
+ logged and leaves the list as it was.
77
+
78
+ ### Changed
79
+
80
+ - **The RubyLLM provider requires ruby_llm 1.x** (`activeagent`). ruby_llm
81
+ 2.0 renamed the APIs `RubyLLMProvider` calls, and the open `>= 1.0`
82
+ requirement let `bundle update` install it. The provider now requires
83
+ `~> 1.0` until it supports 2.0 (#502). Loading it with an unsupported
84
+ version names the supported range and the loaded version, instead of
85
+ asking for a gem that is already in the Gemfile. Pin
86
+ `gem "ruby_llm", "~> 1.0"` if your bundle resolved 2.0.
87
+
88
+ ### Fixed
89
+
90
+ - **Tool calls sent back through the RubyLLM provider** (`activeagent`).
91
+ After a tool ran, the follow-up request repeated the model's tool call
92
+ with its arguments as a JSON string where ruby_llm expects a Hash: OpenAI
93
+ received them JSON-encoded twice, and Anthropic received a string for
94
+ `tool_use.input`, which its API requires to be an object. The same
95
+ happened when a stored conversation containing a tool call was replayed.
96
+ The provider now hands ruby_llm the parsed arguments (#501).
97
+ - **Structured output through the RubyLLM provider** (`activeagent`). A
98
+ `json_schema` response_format reached ruby_llm unchanged, but ruby_llm
99
+ reads `{ name:, schema:, strict: }`, so OpenAI received a schema with a
100
+ null name and body, and the Anthropic request raised inside ruby_llm
101
+ before it was sent. The provider now converts it, naming the schema
102
+ `response` and making it strict unless the format says otherwise, as
103
+ ruby_llm's own `with_schema` does. A `text` format asks for plain text;
104
+ `json_object`, which ruby_llm has no mode for, and a `json_schema`
105
+ without a schema now raise `ArgumentError` instead of sending a request
106
+ the API rejects (#501).
107
+
10
108
  ## [1.7.0] - 2026-09-24
11
109
 
12
110
  Releases `activeagent` and `actionagent` 1.7.0 from one tag. A minor release:
data/README.md CHANGED
@@ -54,6 +54,9 @@ bundle add openai
54
54
  # OpenRouter (uses OpenAI-compatible API)
55
55
  bundle add openai
56
56
 
57
+ # Requesty (uses OpenAI-compatible API)
58
+ bundle add openai
59
+
57
60
  # RubyLLM (unified API for 15+ providers)
58
61
  bundle add ruby_llm
59
62
  ```
@@ -20,6 +20,10 @@ module ActiveAgent
20
20
  class ModelSpec
21
21
  DEFAULT_PROVIDERS = %w[openai anthropic ollama openrouter].freeze
22
22
 
23
+ # The dashboard's model pickers resolve names with a JavaScript copy of
24
+ # these rules and of .parse (actionagent/frontend/utils/modelOptions.mjs).
25
+ # actionagent/test/fixtures/model_spec_cases.json holds the cases both
26
+ # are tested against, so a change here needs the same change there.
23
27
  DEFAULT_INFERENCE_RULES = [
24
28
  [ /\Aclaude/i, "anthropic" ],
25
29
  [ /\A(gpt-|o\d|chatgpt|text-embedding)/i, "openai" ],
@@ -16,10 +16,10 @@ module ActiveAgent
16
16
  # faults in `refine_faults` (up to `judge_limit` calls per run).
17
17
  #
18
18
  # Runner.new(
19
- # scenarios: suite.scenarios(groups: %w[blame]),
19
+ # scenarios: suite.scenarios(groups: %w[history]),
20
20
  # models: ModelSpec.parse_all(%w[gpt-5-mini claude-haiku-4-5], default_provider: "openai"),
21
21
  # criteria: [{ "key" => "response_present", "type" => "response_present" }],
22
- # available_tools: { "find_records" => "Look up records" },
22
+ # available_tools: { "lookup_order" => "Find an order by number" },
23
23
  # instructions: agent.instructions,
24
24
  # judge: Judge.new(label: "claude-opus-5") { |instructions:, prompt:| ... },
25
25
  # replay: ->(scenario, spec) { ... },
@@ -7,11 +7,11 @@ module ActiveAgent
7
7
  # answer is expected to do.
8
8
  #
9
9
  # @!attribute key
10
- # @return [String] stable within a suite ("blame_3"), so results line up across runs
10
+ # @return [String] stable within a suite ("history_3"), so results line up across runs
11
11
  # @!attribute group
12
- # @return [String, nil] the group key ("blame")
12
+ # @return [String, nil] the group key ("history")
13
13
  # @!attribute group_name
14
- # @return [String, nil] the group's display name ("Blame / audit")
14
+ # @return [String, nil] the group's display name ("History / audit")
15
15
  # @!attribute expected_tools
16
16
  # @return [Array<String>] tool names a passing answer calls (any one of them)
17
17
  # @!attribute expected_patterns
@@ -9,8 +9,8 @@ module ActiveAgent
9
9
  # - `# Heading` or `**Heading**` lines, which start a group; so does a
10
10
  # short unmarked line ending in a colon (`Find records:`)
11
11
  # - a message in backticks at the start of the line, followed by notes,
12
- # as in an issue's question catalog:
13
- # `` 3. `Show me all providers with no license on file` — 1,060 locally ``
12
+ # as in a list copied from an issue:
13
+ # `` 3. `Show me all tickets with no assignee` — 12 in the sample data ``
14
14
  # - trailing ` | tools: a, b | contains: x | not_contains: y | key: k`
15
15
  # options on a line
16
16
  # - a JSON array of strings, or of objects with `prompt` (or `message`),
@@ -19,7 +19,7 @@ module ActiveAgent
19
19
  # group names, stable keys, notes and production-only flags
20
20
  #
21
21
  # Every scenario gets a key unique within the paste, derived from its group
22
- # and position ("blame_3"), unless the line names one. The result is an
22
+ # and position ("history_3"), unless the line names one. The result is an
23
23
  # array of string-keyed hashes; `Scenario.from_hash` builds the structs.
24
24
  class ScenarioParser
25
25
  class ParseError < ArgumentError; end
@@ -8,17 +8,17 @@ module ActiveAgent
8
8
  # suite. That is how an app keeps a shared suite and lets a deployment add or
9
9
  # reword questions.
10
10
  #
11
- # suite: assistant_dashboard
12
- # description: The V1 question catalog
11
+ # suite: support_desk
12
+ # description: Questions the support team asks every week
13
13
  # groups:
14
- # - key: find_records
15
- # name: Find record(s)
14
+ # - key: open_tickets
15
+ # name: Open tickets
16
16
  # scenarios:
17
- # - key: find_records_1
18
- # prompt: Which medical search terms are under client control?
17
+ # - key: open_tickets_1
18
+ # prompt: Which open tickets mention a refund?
19
19
  # expect:
20
- # tools: [find_records, count_records]
21
- # notes: Uniform locally.
20
+ # tools: [find_tickets, count_tickets]
21
+ # notes: The sample data has three.
22
22
  # production_only: false
23
23
  class Suite
24
24
  class NotFound < StandardError; end
@@ -50,7 +50,7 @@ require_relative "evals/publisher"
50
50
  # report = ActiveAgent::Evals::Runner.new(
51
51
  # scenarios: scenarios,
52
52
  # models: models,
53
- # available_tools: { "find_records" => "Look up records by filters" },
53
+ # available_tools: { "lookup_order" => "Find an order by number" },
54
54
  # replay: ->(scenario, spec) { my_agent.run(scenario.prompt, model: spec.model, provider: spec.provider) }
55
55
  # ).call
56
56
  #
@@ -10,7 +10,8 @@ require_relative "concerns/tool_choice_clearing"
10
10
  GEM_LOADERS = {
11
11
  anthropic: [ "anthropic", "~> 1.12", "anthropic" ],
12
12
  openai: [ "openai", "~> 0.34", "openai" ],
13
- ruby_llm: [ "ruby_llm", ">= 1.0", "ruby_llm" ]
13
+ # Keep a tested floor: 1.2 lacks the provider APIs this adapter uses.
14
+ ruby_llm: [ "ruby_llm", [ ">= 1.16", "< 3" ], "ruby_llm" ]
14
15
  }
15
16
 
16
17
  # Requires a provider's gem dependency.
@@ -18,15 +19,23 @@ GEM_LOADERS = {
18
19
  # @param type [Symbol] provider type (:anthropic, :openai)
19
20
  # @param file_name [String] for error context
20
21
  # @return [void]
21
- # @raise [LoadError] when required gem is not installed
22
+ # @raise [LoadError] when the gem is not installed, or when the loaded
23
+ # version is outside the supported range
22
24
  def require_gem!(type, file_name)
23
25
  gem_name, requirement, package_name = GEM_LOADERS.fetch(type)
26
+ requirements = Array(requirement)
24
27
  provider_name = file_name.split("/").last.delete_suffix(".rb").camelize
25
28
 
26
29
  begin
27
- gem(gem_name, requirement)
30
+ gem(gem_name, *requirements)
28
31
  require(package_name)
29
32
  rescue LoadError
33
+ loaded = Gem.loaded_specs[gem_name]
34
+ if loaded && !Gem::Requirement.new(requirement).satisfied_by?(loaded.version)
35
+ raise LoadError, "#{provider_name} supports the '#{gem_name}' gem #{requirements.join(', ')}, but #{loaded.version} is loaded. " \
36
+ "Add `gem \"#{gem_name}\", #{requirements.map(&:inspect).join(', ')}` to your Gemfile and run `bundle update #{gem_name}`."
37
+ end
38
+
30
39
  raise LoadError, "The '#{gem_name}' gem is required for #{provider_name}. Please add it to your Gemfile and run `bundle install`."
31
40
  end
32
41
  end
@@ -27,6 +27,11 @@ module ActiveAgent
27
27
  {}
28
28
  end
29
29
 
30
+ # RubyLLM 2 names for the same tool interface.
31
+ alias_method :parameters_schema, :params_schema
32
+ alias_method :declared_parameters, :parameters
33
+ alias_method :provider_options, :provider_params
34
+
30
35
  private
31
36
 
32
37
  def deep_stringify(obj)
@@ -2,7 +2,7 @@
2
2
 
3
3
  require_relative "_base_provider"
4
4
 
5
- require_gem!(:ruby_llm, __FILE__) unless defined?(::RubyLLM)
5
+ require_gem!(:ruby_llm, __FILE__)
6
6
 
7
7
  require_relative "ruby_llm/_types"
8
8
  require_relative "ruby_llm/tool_proxy"
@@ -59,16 +59,22 @@ module ActiveAgent
59
59
  tools: tools || {},
60
60
  temperature: parameters[:temperature]
61
61
  }
62
- kwargs[:schema] = parameters[:response_format] if parameters[:response_format]
62
+ schema = ruby_llm_schema(parameters[:response_format])
63
+ kwargs[:schema] = schema if schema
63
64
 
64
- # Pass extra params (max_tokens, etc.) via RubyLLM's params: deep-merge
65
+ # RubyLLM 2 renamed params: and exposes a provider-neutral token limit.
65
66
  max_tokens = parameters[:max_tokens] || options.max_tokens
66
67
  if max_tokens
67
- kwargs[:params] = { max_tokens: max_tokens }
68
+ if ruby_llm_v2?
69
+ kwargs[:max_output_tokens] = max_tokens
70
+ else
71
+ kwargs[:params] = { max_tokens: max_tokens }
72
+ end
68
73
  end
69
74
 
70
75
  if parameters[:stream]
71
76
  stream_proc = parameters[:stream]
77
+ @stream_tool_calls = {}
72
78
 
73
79
  # For streaming, pass a block that forwards chunks
74
80
  @ruby_llm_provider.complete(messages, **kwargs) do |chunk|
@@ -94,7 +100,8 @@ module ActiveAgent
94
100
  inputs = input.is_a?(Array) ? input : [ input ]
95
101
 
96
102
  data = inputs.map.with_index do |text, index|
97
- embedding = @ruby_llm_provider.embed(text, model: model_id, dimensions: parameters[:dimensions])
103
+ embedding_model = ruby_llm_v2? ? @ruby_llm_model : model_id
104
+ embedding = @ruby_llm_provider.embed(text, model: embedding_model, dimensions: parameters[:dimensions])
98
105
 
99
106
  {
100
107
  object: "embedding",
@@ -137,12 +144,15 @@ module ActiveAgent
137
144
  # Handle tool calls in chunk
138
145
  if chunk.tool_calls&.any?
139
146
  message[:tool_calls] ||= []
140
- chunk.tool_calls.each do |_id, tool_call|
141
- existing = message[:tool_calls].find { |tc| tc[:id] == tool_call.id }
147
+ chunk.tool_calls.each do |key, tool_call|
148
+ # RubyLLM 2 keys OpenAI deltas by index; only the first delta
149
+ # includes the call ID and name. Keep each index tied to its call.
150
+ existing = message[:tool_calls].find { |tc| tool_call.id && tc[:id] == tool_call.id }
151
+ existing ||= @stream_tool_calls[key]
142
152
  if existing
143
153
  existing[:function][:arguments] += tool_call.arguments.to_s if tool_call.arguments
144
154
  else
145
- message[:tool_calls] << {
155
+ existing = {
146
156
  id: tool_call.id,
147
157
  type: "function",
148
158
  function: {
@@ -150,7 +160,9 @@ module ActiveAgent
150
160
  arguments: tool_call.arguments.to_s
151
161
  }
152
162
  }
163
+ message[:tool_calls] << existing
153
164
  end
165
+ @stream_tool_calls[key] = existing
154
166
  end
155
167
  end
156
168
 
@@ -253,6 +265,10 @@ module ActiveAgent
253
265
 
254
266
  private
255
267
 
268
+ def ruby_llm_v2?
269
+ Gem.loaded_specs.fetch("ruby_llm").version.segments.first >= 2
270
+ end
271
+
256
272
  # Resolves and caches the RubyLLM provider for the given model.
257
273
  #
258
274
  # Reuses the cached provider if the model hasn't changed (e.g., during
@@ -345,12 +361,61 @@ module ActiveAgent
345
361
  call = ::RubyLLM::ToolCall.new(
346
362
  id: id,
347
363
  name: tc.dig(:function, :name) || tc[:name],
348
- arguments: tc.dig(:function, :arguments) || tc[:input]&.to_json || "{}"
364
+ arguments: ruby_llm_tool_arguments(tc.dig(:function, :arguments) || tc[:input])
349
365
  )
350
366
  hash[id] = call
351
367
  end
352
368
  end
353
369
 
370
+ # ActiveAgent keeps a tool call's arguments as the JSON string the
371
+ # model sent; RubyLLM takes them as a Hash and renders them itself
372
+ # (JSON-encoded for OpenAI, as the input object for Anthropic).
373
+ #
374
+ # A string that is not valid JSON, such as arguments a model cut off
375
+ # mid-way in a stored conversation, is passed through unchanged.
376
+ #
377
+ # @param arguments [String, Hash, nil]
378
+ # @return [Hash, String]
379
+ def ruby_llm_tool_arguments(arguments)
380
+ case arguments
381
+ when Hash
382
+ arguments.deep_stringify_keys
383
+ when String
384
+ arguments.blank? ? {} : JSON.parse(arguments)
385
+ else
386
+ {}
387
+ end
388
+ rescue JSON::ParserError
389
+ arguments
390
+ end
391
+
392
+ # Converts ActiveAgent's response_format to the schema RubyLLM's
393
+ # complete takes, { name:, schema:, strict: }, filling the name and
394
+ # strict flag in the way RubyLLM::Chat#with_schema does.
395
+ #
396
+ # @param response_format [Hash, Symbol, String, nil] ActiveAgent common format
397
+ # @return [Hash, nil] nil when the response is plain text
398
+ # @raise [ArgumentError] for json_schema without a schema, and for any
399
+ # other type, including json_object, which RubyLLM has no mode for
400
+ def ruby_llm_schema(response_format)
401
+ return nil if response_format.nil?
402
+
403
+ format = response_format.is_a?(Hash) ? response_format : { type: response_format.to_s }
404
+
405
+ case format[:type].to_s
406
+ when "text"
407
+ nil
408
+ when "json_schema"
409
+ json_schema = format[:json_schema] || {}
410
+ raise ArgumentError, "RubyLLMProvider needs a schema for a json_schema response_format" unless json_schema[:schema]
411
+
412
+ { name: json_schema[:name] || "response", schema: json_schema[:schema], strict: json_schema[:strict] != false }
413
+ else
414
+ raise ArgumentError, "RubyLLMProvider supports a json_schema or text response_format, not #{format[:type].inspect}; " \
415
+ "ruby_llm has no JSON object mode, so give json_schema a schema instead"
416
+ end
417
+ end
418
+
354
419
  # Converts ActiveAgent tool definitions to RubyLLM ToolProxy objects.
355
420
  #
356
421
  # @param tools [Array<Hash>, nil] ActiveAgent tool definitions
@@ -397,6 +462,8 @@ module ActiveAgent
397
462
  # Add stop_reason if available
398
463
  if response.respond_to?(:stop_reason) && response.stop_reason
399
464
  hash[:stop_reason] = response.stop_reason
465
+ elsif response.respond_to?(:finish_reason) && response.finish_reason
466
+ hash[:stop_reason] = { stop: "end_turn", tool_calls: "tool_use" }.fetch(response.finish_reason, response.finish_reason.to_s)
400
467
  elsif response.tool_calls&.any?
401
468
  hash[:stop_reason] = "tool_use"
402
469
  else
@@ -404,7 +471,10 @@ module ActiveAgent
404
471
  end
405
472
 
406
473
  # Add usage info if available
407
- if response.respond_to?(:input_tokens) && response.input_tokens
474
+ if response.respond_to?(:tokens)
475
+ tokens = response.tokens
476
+ hash[:usage] = { input_tokens: tokens.input, output_tokens: tokens.output } if tokens.input
477
+ elsif response.respond_to?(:input_tokens) && response.input_tokens
408
478
  hash[:usage] = {
409
479
  input_tokens: response.input_tokens,
410
480
  output_tokens: response.output_tokens
@@ -1,3 +1,3 @@
1
1
  module ActiveAgent
2
- VERSION = "1.7.0"
2
+ VERSION = "1.7.2"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activeagent
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.0
4
+ version: 1.7.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Bowen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-09-24 00:00:00.000000000 Z
11
+ date: 2026-09-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionpack
@@ -206,14 +206,20 @@ dependencies:
206
206
  requirements:
207
207
  - - ">="
208
208
  - !ruby/object:Gem::Version
209
- version: '1.0'
209
+ version: '1.16'
210
+ - - "<"
211
+ - !ruby/object:Gem::Version
212
+ version: '3'
210
213
  type: :development
211
214
  prerelease: false
212
215
  version_requirements: !ruby/object:Gem::Requirement
213
216
  requirements:
214
217
  - - ">="
215
218
  - !ruby/object:Gem::Version
216
- version: '1.0'
219
+ version: '1.16'
220
+ - - "<"
221
+ - !ruby/object:Gem::Version
222
+ version: '3'
217
223
  - !ruby/object:Gem::Dependency
218
224
  name: capybara
219
225
  requirement: !ruby/object:Gem::Requirement