rails-agent-stack 0.1.0

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 (35) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +16 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.md +463 -0
  5. data/lib/generators/rails_agents/install_generator.rb +29 -0
  6. data/lib/generators/rails_agents/templates/initializer.rb +6 -0
  7. data/lib/rails/agent/stack.rb +4 -0
  8. data/lib/rails_agents/agent.rb +128 -0
  9. data/lib/rails_agents/configuration.rb +35 -0
  10. data/lib/rails_agents/error.rb +7 -0
  11. data/lib/rails_agents/generated_file.rb +15 -0
  12. data/lib/rails_agents/message.rb +27 -0
  13. data/lib/rails_agents/providers/anthropic/files.rb +51 -0
  14. data/lib/rails_agents/providers/anthropic.rb +111 -0
  15. data/lib/rails_agents/providers/base.rb +23 -0
  16. data/lib/rails_agents/providers/grok.rb +15 -0
  17. data/lib/rails_agents/providers/open_router.rb +19 -0
  18. data/lib/rails_agents/providers/openai.rb +15 -0
  19. data/lib/rails_agents/providers/openai_compatible.rb +78 -0
  20. data/lib/rails_agents/railtie.rb +25 -0
  21. data/lib/rails_agents/result.rb +13 -0
  22. data/lib/rails_agents/runner.rb +146 -0
  23. data/lib/rails_agents/skill.rb +24 -0
  24. data/lib/rails_agents/skill_declaration.rb +8 -0
  25. data/lib/rails_agents/skill_set.rb +139 -0
  26. data/lib/rails_agents/skills/anthropic_content.rb +29 -0
  27. data/lib/rails_agents/skills/portable/web_fetch.rb +29 -0
  28. data/lib/rails_agents/skills/portable/web_search.rb +33 -0
  29. data/lib/rails_agents/skills/registry.rb +67 -0
  30. data/lib/rails_agents/tool.rb +48 -0
  31. data/lib/rails_agents/tool_set.rb +46 -0
  32. data/lib/rails_agents/version.rb +5 -0
  33. data/lib/rails_agents.rb +44 -0
  34. data/rails-agent-stack.gemspec +49 -0
  35. metadata +150 -0
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsAgents
4
+ class SkillSet
5
+ FILES_BETA = "files-api-2025-04-14"
6
+
7
+ attr_reader :provider, :entries, :custom_skills, :declarations
8
+
9
+ def initialize(declarations:, provider:)
10
+ @provider = provider.to_sym
11
+ @declarations = declarations
12
+ @custom_skills = []
13
+ @options_by_name = declarations.to_h { |declaration|
14
+ key = declaration.custom? ? declaration.key.to_s : declaration.name.to_s
15
+ [key, declaration.options]
16
+ }
17
+ @entries = expand(resolve)
18
+ end
19
+
20
+ def server_tool_names
21
+ entries.select(&:server?).filter_map { |entry| entry.anthropic_tool&.dig(:name) }
22
+ end
23
+
24
+ def portable_tool_classes
25
+ return [] unless use_portable?
26
+
27
+ entries.filter_map(&:portable_tool)
28
+ end
29
+
30
+ def anthropic_document_skills?
31
+ entries.any?(&:anthropic_document?) || custom_skills.any?
32
+ end
33
+
34
+ def anthropic_request
35
+ return {} unless provider == :anthropic
36
+
37
+ tools = entries.filter_map { |entry| anthropic_tool_for(entry) }
38
+ skills = entries.filter_map { |entry| anthropic_skill_for(entry) } + custom_skills
39
+ headers = anthropic_beta_headers(tools:, skills:)
40
+
41
+ {
42
+ server_tools: tools.uniq { |tool| tool[:name] },
43
+ container: (skills.empty? ? nil : {skills: skills}),
44
+ beta_headers: headers
45
+ }
46
+ end
47
+
48
+ def validate!
49
+ if custom_skills.any? && provider != :anthropic
50
+ raise ConfigurationError, "Custom Anthropic skills require provider :anthropic"
51
+ end
52
+
53
+ declarations.each do |declaration|
54
+ next if declaration.custom?
55
+ next if Skills::Registry.fetch(declaration.name)
56
+
57
+ raise ConfigurationError, "Unknown skill: #{declaration.key}. See RailsAgents::Skills::Registry::ENTRIES"
58
+ end
59
+
60
+ entries.each do |entry|
61
+ next if provider == :anthropic
62
+ next if entry.portable_tool
63
+
64
+ raise ConfigurationError,
65
+ "Skill :#{entry.name} requires provider :anthropic. " \
66
+ "Portable skills: #{Skills::Registry::PORTABLE_SKILL_IDS.join(', ')}"
67
+ end
68
+ end
69
+
70
+ private
71
+
72
+ def use_portable?
73
+ provider != :anthropic
74
+ end
75
+
76
+ def resolve
77
+ declarations.filter_map do |declaration|
78
+ if declaration.custom?
79
+ @custom_skills << anthropic_custom_skill(declaration)
80
+ next
81
+ end
82
+
83
+ Skills::Registry.fetch(declaration.name)
84
+ end
85
+ end
86
+
87
+ def expand(resolved)
88
+ expanded = resolved.dup
89
+ resolved.each do |entry|
90
+ entry.requires.each do |required|
91
+ dependency = Skills::Registry.fetch(required)
92
+ expanded << dependency unless expanded.any? { |existing| existing.name == dependency.name }
93
+ end
94
+ end
95
+ expanded.uniq { |entry| entry.name }
96
+ end
97
+
98
+ def anthropic_tool_for(entry)
99
+ return unless entry.anthropic_tool
100
+
101
+ tool = entry.anthropic_tool.dup
102
+ options = @options_by_name.fetch(entry.name.to_s, {})
103
+ merge_tool_options!(tool, options)
104
+ tool
105
+ end
106
+
107
+ def anthropic_skill_for(entry)
108
+ return unless entry.anthropic_skill
109
+
110
+ skill = entry.anthropic_skill.dup
111
+ options = @options_by_name.fetch(entry.name.to_s, {})
112
+ skill[:version] = options.fetch(:version, skill[:version] || "latest")
113
+ skill
114
+ end
115
+
116
+ def anthropic_custom_skill(declaration)
117
+ {
118
+ type: "custom",
119
+ skill_id: declaration.key.to_s,
120
+ version: declaration.options.fetch(:version, "latest")
121
+ }
122
+ end
123
+
124
+ def merge_tool_options!(tool, options)
125
+ allowed = %i[max_uses allowed_domains blocked_domains user_location response_inclusion]
126
+ options.each do |key, value|
127
+ tool[key] = value if allowed.include?(key.to_sym)
128
+ end
129
+ end
130
+
131
+ def anthropic_beta_headers(tools:, skills:)
132
+ headers = []
133
+ headers << "code-execution-2025-08-25" if skills.any? || tools.any? { |tool| tool[:name] == "code_execution" }
134
+ headers << "skills-2025-10-02" if skills.any?
135
+ headers << FILES_BETA if skills.any? || tools.any? { |tool| tool[:name] == "code_execution" }
136
+ headers
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsAgents
4
+ module Skills
5
+ module AnthropicContent
6
+ module_function
7
+
8
+ def extract_text(blocks)
9
+ Array(blocks).select { |block| block["type"] == "text" }.map { |block| block["text"] }.join("\n").strip
10
+ end
11
+
12
+ def extract_file_ids(blocks)
13
+ ids = []
14
+ walk(blocks) { |node| ids << node["file_id"] if node.is_a?(Hash) && !node["file_id"].to_s.empty? }
15
+ ids.uniq
16
+ end
17
+
18
+ def walk(object)
19
+ case object
20
+ when Array
21
+ object.each { |item| walk(item) { |node| yield node } }
22
+ when Hash
23
+ yield object
24
+ object.each_value { |value| walk(value) { |node| yield node } }
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "faraday"
4
+
5
+ module RailsAgents
6
+ module Skills
7
+ module Portable
8
+ class WebFetch < Tool
9
+ description "Fetch and read the text content of a web page"
10
+ param :url, :string, description: "URL to fetch"
11
+
12
+ def call(url:)
13
+ response = Faraday.get(url, nil, {"User-Agent" => "RailsAgents/1.0"})
14
+ return {error: "Fetch failed", url: url, status: response.status} unless response.success?
15
+
16
+ text = response.body.gsub(/<script.*?>.*?<\/script>/m, "")
17
+ .gsub(/<style.*?>.*?<\/style>/m, "")
18
+ .gsub(/<[^>]+>/, " ")
19
+ .gsub(/\s+/, " ")
20
+ .strip
21
+
22
+ {url: url, content: text[0, 8_000]}
23
+ rescue StandardError => e
24
+ {error: e.message, url: url}
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "faraday"
4
+
5
+ module RailsAgents
6
+ module Skills
7
+ module Portable
8
+ class WebSearch < Tool
9
+ SEARCH_URL = "https://html.duckduckgo.com/html/"
10
+
11
+ description "Search the web for current information"
12
+ param :query, :string, description: "Search query"
13
+
14
+ def call(query:)
15
+ response = Faraday.get(SEARCH_URL, {q: query}, {"User-Agent" => "RailsAgents/1.0"})
16
+ return {error: "Search failed", query: query} unless response.success?
17
+
18
+ titles = response.body.scan(/class="result__a"[^>]*>(.*?)<\/a>/).first(5).map { |match|
19
+ match.first.gsub(/<[^>]+>/, "").strip
20
+ }.reject(&:empty?)
21
+
22
+ urls = response.body.scan(/class="result__url"[^>]*>(.*?)<\/a>/).first(5).map { |match|
23
+ match.first.gsub(/<[^>]+>/, "").strip
24
+ }
25
+
26
+ {query: query, results: titles.zip(urls).map { |title, url| {title:, url:} }}
27
+ rescue StandardError => e
28
+ {error: e.message, query: query}
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsAgents
4
+ module Skills
5
+ class Registry
6
+ ENTRIES = {
7
+ web_search: Skill.new(
8
+ name: :web_search,
9
+ kind: Skill::SERVER,
10
+ anthropic_tool: {type: "web_search_20260209", name: "web_search"},
11
+ portable_tool: Portable::WebSearch
12
+ ),
13
+ web_fetch: Skill.new(
14
+ name: :web_fetch,
15
+ kind: Skill::SERVER,
16
+ anthropic_tool: {type: "web_fetch_20260309", name: "web_fetch"},
17
+ portable_tool: Portable::WebFetch
18
+ ),
19
+ code_execution: Skill.new(
20
+ name: :code_execution,
21
+ kind: Skill::SERVER,
22
+ anthropic_tool: {type: "code_execution_20250825", name: "code_execution"}
23
+ ),
24
+ memory: Skill.new(
25
+ name: :memory,
26
+ kind: Skill::SERVER,
27
+ anthropic_tool: {type: "memory_20250818", name: "memory"}
28
+ ),
29
+ pptx: Skill.new(
30
+ name: :pptx,
31
+ kind: Skill::ANTHROPIC_DOCUMENT,
32
+ anthropic_skill: {type: "anthropic", skill_id: "pptx", version: "latest"},
33
+ requires: [:code_execution]
34
+ ),
35
+ xlsx: Skill.new(
36
+ name: :xlsx,
37
+ kind: Skill::ANTHROPIC_DOCUMENT,
38
+ anthropic_skill: {type: "anthropic", skill_id: "xlsx", version: "latest"},
39
+ requires: [:code_execution]
40
+ ),
41
+ docx: Skill.new(
42
+ name: :docx,
43
+ kind: Skill::ANTHROPIC_DOCUMENT,
44
+ anthropic_skill: {type: "anthropic", skill_id: "docx", version: "latest"},
45
+ requires: [:code_execution]
46
+ ),
47
+ pdf: Skill.new(
48
+ name: :pdf,
49
+ kind: Skill::ANTHROPIC_DOCUMENT,
50
+ anthropic_skill: {type: "anthropic", skill_id: "pdf", version: "latest"},
51
+ requires: [:code_execution]
52
+ )
53
+ }.freeze
54
+
55
+ ANTHROPIC_SKILL_IDS = %i[pptx xlsx docx pdf].freeze
56
+ PORTABLE_SKILL_IDS = %i[web_search web_fetch].freeze
57
+
58
+ def self.fetch(name)
59
+ ENTRIES[name.to_sym]
60
+ end
61
+
62
+ def self.known?(name)
63
+ name.to_s.start_with?("skill_") || ENTRIES.key?(name.to_sym)
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/string/inflections"
4
+
5
+ module RailsAgents
6
+ class Tool
7
+ class << self
8
+ attr_reader :description_text, :parameters
9
+
10
+ def inherited(subclass)
11
+ super
12
+ subclass.instance_variable_set(:@parameters, (parameters || {}).dup)
13
+ end
14
+
15
+ def description(text = nil)
16
+ return @description_text if text.nil?
17
+ @description_text = text
18
+ end
19
+
20
+ def param(name, type, required: true, description: nil)
21
+ @parameters ||= {}
22
+ @parameters[name.to_sym] = {type: type.to_sym, required:, description:}
23
+ end
24
+
25
+ def tool_name
26
+ name.split("::").last.gsub(/Tool$/, "").underscore
27
+ end
28
+
29
+ def definition
30
+ {
31
+ name: tool_name,
32
+ description: description_text,
33
+ parameters: {
34
+ type: "object",
35
+ properties: parameters.transform_values { |p|
36
+ {type: p[:type].to_s, description: p[:description]}.compact
37
+ },
38
+ required: parameters.select { |_, p| p[:required] }.keys.map(&:to_s)
39
+ }
40
+ }
41
+ end
42
+ end
43
+
44
+ def call(**kwargs)
45
+ raise NotImplementedError
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsAgents
4
+ class ToolSet
5
+ def initialize(*tools)
6
+ @tools = tools.flatten.compact.map { |t| resolve(t) }
7
+ end
8
+
9
+ def self.from_directory(path = nil)
10
+ path ||= (defined?(::Rails) && ::Rails.respond_to?(:root) ? ::Rails.root.join("app/agents/tools") : nil)
11
+ return new unless path && Dir.exist?(path)
12
+
13
+ classes = Dir.glob("#{path}/**/*.rb").filter_map do |file|
14
+ require_dependency file if defined?(Rails)
15
+ const_name = file.delete_prefix("#{path}/").delete_suffix(".rb").camelize
16
+ const_name.safe_constantize
17
+ end
18
+ new(*classes)
19
+ end
20
+
21
+ def self.use(*tools) = new(*tools)
22
+
23
+ def resolve(klass)
24
+ case klass
25
+ when Class
26
+ klass < Tool ? klass : klass
27
+ when String, Symbol
28
+ resolved = klass.to_s.camelize.constantize
29
+ raise Error, "#{klass} is not a RailsAgents::Tool" unless resolved < Tool
30
+ resolved
31
+ else
32
+ klass
33
+ end
34
+ end
35
+
36
+ def definitions = @tools.map(&:definition)
37
+ def find(name) = @tools.find { |t| t.tool_name == name.to_s }
38
+ def execute(name, arguments)
39
+ tool_class = find(name)
40
+ raise Error, "Unknown tool: #{name}" unless tool_class
41
+ tool_class.new.call(**arguments.transform_keys(&:to_sym))
42
+ end
43
+
44
+ def +(other) = self.class.new(*@tools, *other.instance_variable_get(:@tools))
45
+ end
46
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsAgents
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "zeitwerk"
4
+ require "json"
5
+
6
+ loader = Zeitwerk::Loader.for_gem
7
+ loader.ignore("#{__dir__}/generators")
8
+ # Bundler auto-requires gem "rails-agent-stack" as rails/agent/stack —
9
+ # keep that shim off Zeitwerk so it does not define a stub Rails module.
10
+ loader.ignore("#{__dir__}/rails")
11
+ loader.inflector.inflect(
12
+ "openai" => "OpenAI",
13
+ "openai_compatible" => "OpenAICompatible",
14
+ "anthropic" => "Anthropic",
15
+ "open_router" => "OpenRouter"
16
+ )
17
+ loader.setup
18
+
19
+ require_relative "rails_agents/message"
20
+ require_relative "rails_agents/result"
21
+ require_relative "rails_agents/error"
22
+ require_relative "rails_agents/skill_declaration"
23
+ require_relative "rails_agents/generated_file"
24
+ require_relative "rails_agents/providers/openai_compatible"
25
+
26
+ module RailsAgents
27
+ class << self
28
+ attr_writer :config
29
+
30
+ def config
31
+ @config ||= Configuration.new
32
+ end
33
+
34
+ def configure
35
+ yield config
36
+ end
37
+
38
+ def tools(*classes)
39
+ ToolSet.use(*classes)
40
+ end
41
+ end
42
+ end
43
+
44
+ require_relative "rails_agents/version"
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/rails_agents/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "rails-agent-stack"
7
+ spec.version = RailsAgents::VERSION
8
+ spec.authors = ["Tiny Bubble Company"]
9
+ spec.email = ["hello@tinybubble.company"]
10
+
11
+ spec.summary = "Dead-simple AI agents for Rails — speed to production, not framework noise"
12
+ spec.description = <<~DESC.gsub(/\s+/, " ").strip
13
+ Rails Agents (gem: rails-agent-stack) is the simplest way to build AI agents
14
+ in Ruby on Rails. Define an LLM agent as a plain Ruby class, attach your app
15
+ code as tools, add skills like web search or spreadsheets, and call .run —
16
+ minutes to production, not days of framework setup.
17
+
18
+ Built for developers searching for Rails AI agents, Ruby LLM agents,
19
+ OpenAI / Anthropic / Claude / GPT tool-calling agents, OpenRouter and
20
+ Grok (xAI) integrations, agentic workflows, RAG helpers, and a lighter
21
+ alternative to RubyLLM, LangChain, or rolling your own multi-turn tool loop.
22
+
23
+ One mental model: RailsAgents::Agent. No dashboards, no cloud lock-in,
24
+ no agent lifecycle UI — just provider, model, description, tools, and skills.
25
+ DESC
26
+
27
+ spec.homepage = "https://tiny-bubble-company.github.io/rails-agents/"
28
+ spec.license = "MIT"
29
+ spec.required_ruby_version = ">= 3.2"
30
+
31
+ spec.metadata = {
32
+ "homepage_uri" => spec.homepage,
33
+ "source_code_uri" => "https://github.com/Tiny-Bubble-Company/rails-agents",
34
+ "changelog_uri" => "https://github.com/Tiny-Bubble-Company/rails-agents/blob/main/CHANGELOG.md",
35
+ "documentation_uri" => "https://tiny-bubble-company.github.io/rails-agents/",
36
+ "bug_tracker_uri" => "https://github.com/Tiny-Bubble-Company/rails-agents/issues",
37
+ "rubygems_mfa_required" => "true",
38
+ }
39
+
40
+ spec.files = Dir.chdir(__dir__) do
41
+ Dir["{app,lib}/**/*", "README.md", "CHANGELOG.md", "MIT-LICENSE", "rails-agent-stack.gemspec"]
42
+ end
43
+
44
+ spec.require_paths = ["lib"]
45
+
46
+ spec.add_dependency "faraday", ">= 2.9", "< 3"
47
+ spec.add_dependency "rails", ">= 7.1", "< 9"
48
+ spec.add_dependency "zeitwerk", ">= 2.6", "< 3"
49
+ end
metadata ADDED
@@ -0,0 +1,150 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rails-agent-stack
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Tiny Bubble Company
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-07-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: faraday
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '2.9'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '3'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '2.9'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '3'
33
+ - !ruby/object:Gem::Dependency
34
+ name: rails
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '7.1'
40
+ - - "<"
41
+ - !ruby/object:Gem::Version
42
+ version: '9'
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '7.1'
50
+ - - "<"
51
+ - !ruby/object:Gem::Version
52
+ version: '9'
53
+ - !ruby/object:Gem::Dependency
54
+ name: zeitwerk
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: '2.6'
60
+ - - "<"
61
+ - !ruby/object:Gem::Version
62
+ version: '3'
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '2.6'
70
+ - - "<"
71
+ - !ruby/object:Gem::Version
72
+ version: '3'
73
+ description: 'Rails Agents (gem: rails-agent-stack) is the simplest way to build AI
74
+ agents in Ruby on Rails. Define an LLM agent as a plain Ruby class, attach your
75
+ app code as tools, add skills like web search or spreadsheets, and call .run — minutes
76
+ to production, not days of framework setup. Built for developers searching for Rails
77
+ AI agents, Ruby LLM agents, OpenAI / Anthropic / Claude / GPT tool-calling agents,
78
+ OpenRouter and Grok (xAI) integrations, agentic workflows, RAG helpers, and a lighter
79
+ alternative to RubyLLM, LangChain, or rolling your own multi-turn tool loop. One
80
+ mental model: RailsAgents::Agent. No dashboards, no cloud lock-in, no agent lifecycle
81
+ UI — just provider, model, description, tools, and skills.'
82
+ email:
83
+ - hello@tinybubble.company
84
+ executables: []
85
+ extensions: []
86
+ extra_rdoc_files: []
87
+ files:
88
+ - CHANGELOG.md
89
+ - MIT-LICENSE
90
+ - README.md
91
+ - lib/generators/rails_agents/install_generator.rb
92
+ - lib/generators/rails_agents/templates/initializer.rb
93
+ - lib/rails/agent/stack.rb
94
+ - lib/rails_agents.rb
95
+ - lib/rails_agents/agent.rb
96
+ - lib/rails_agents/configuration.rb
97
+ - lib/rails_agents/error.rb
98
+ - lib/rails_agents/generated_file.rb
99
+ - lib/rails_agents/message.rb
100
+ - lib/rails_agents/providers/anthropic.rb
101
+ - lib/rails_agents/providers/anthropic/files.rb
102
+ - lib/rails_agents/providers/base.rb
103
+ - lib/rails_agents/providers/grok.rb
104
+ - lib/rails_agents/providers/open_router.rb
105
+ - lib/rails_agents/providers/openai.rb
106
+ - lib/rails_agents/providers/openai_compatible.rb
107
+ - lib/rails_agents/railtie.rb
108
+ - lib/rails_agents/result.rb
109
+ - lib/rails_agents/runner.rb
110
+ - lib/rails_agents/skill.rb
111
+ - lib/rails_agents/skill_declaration.rb
112
+ - lib/rails_agents/skill_set.rb
113
+ - lib/rails_agents/skills/anthropic_content.rb
114
+ - lib/rails_agents/skills/portable/web_fetch.rb
115
+ - lib/rails_agents/skills/portable/web_search.rb
116
+ - lib/rails_agents/skills/registry.rb
117
+ - lib/rails_agents/tool.rb
118
+ - lib/rails_agents/tool_set.rb
119
+ - lib/rails_agents/version.rb
120
+ - rails-agent-stack.gemspec
121
+ homepage: https://tiny-bubble-company.github.io/rails-agents/
122
+ licenses:
123
+ - MIT
124
+ metadata:
125
+ homepage_uri: https://tiny-bubble-company.github.io/rails-agents/
126
+ source_code_uri: https://github.com/Tiny-Bubble-Company/rails-agents
127
+ changelog_uri: https://github.com/Tiny-Bubble-Company/rails-agents/blob/main/CHANGELOG.md
128
+ documentation_uri: https://tiny-bubble-company.github.io/rails-agents/
129
+ bug_tracker_uri: https://github.com/Tiny-Bubble-Company/rails-agents/issues
130
+ rubygems_mfa_required: 'true'
131
+ post_install_message:
132
+ rdoc_options: []
133
+ require_paths:
134
+ - lib
135
+ required_ruby_version: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: '3.2'
140
+ required_rubygems_version: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ requirements: []
146
+ rubygems_version: 3.4.1
147
+ signing_key:
148
+ specification_version: 4
149
+ summary: Dead-simple AI agents for Rails — speed to production, not framework noise
150
+ test_files: []