sublayer 0.2.3 → 0.2.5

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/bin/sublayer +5 -0
  3. data/lib/sublayer/cli/commands/action.rb +65 -0
  4. data/lib/sublayer/cli/commands/agent.rb +79 -0
  5. data/lib/sublayer/cli/commands/generator.rb +68 -0
  6. data/lib/sublayer/cli/commands/generators/example_action_api_call.rb +23 -0
  7. data/lib/sublayer/cli/commands/generators/example_action_file_manipulation.rb +12 -0
  8. data/lib/sublayer/cli/commands/generators/example_agent.rb +33 -0
  9. data/lib/sublayer/cli/commands/generators/example_generator.rb +26 -0
  10. data/lib/sublayer/cli/commands/generators/sublayer_action_generator.rb +55 -0
  11. data/lib/sublayer/cli/commands/generators/sublayer_agent_generator.rb +61 -0
  12. data/lib/sublayer/cli/commands/generators/sublayer_generator_generator.rb +96 -0
  13. data/lib/sublayer/cli/commands/new_project.rb +116 -0
  14. data/lib/sublayer/cli/commands/subcommand_base.rb +13 -0
  15. data/lib/sublayer/cli/templates/cli/%project_name%.gemspec.tt +35 -0
  16. data/lib/sublayer/cli/templates/cli/.gitignore +14 -0
  17. data/lib/sublayer/cli/templates/cli/Gemfile +7 -0
  18. data/lib/sublayer/cli/templates/cli/README.md.tt +22 -0
  19. data/lib/sublayer/cli/templates/cli/bin/%project_name%.tt +5 -0
  20. data/lib/sublayer/cli/templates/cli/lib/%project_name%/actions/example_action.rb.tt +15 -0
  21. data/lib/sublayer/cli/templates/cli/lib/%project_name%/agents/example_agent.rb.tt +21 -0
  22. data/lib/sublayer/cli/templates/cli/lib/%project_name%/cli.rb.tt +13 -0
  23. data/lib/sublayer/cli/templates/cli/lib/%project_name%/commands/base_command.rb.tt +21 -0
  24. data/lib/sublayer/cli/templates/cli/lib/%project_name%/commands/example_command.rb.tt +13 -0
  25. data/lib/sublayer/cli/templates/cli/lib/%project_name%/config/.keep +0 -0
  26. data/lib/sublayer/cli/templates/cli/lib/%project_name%/config.rb.tt +19 -0
  27. data/lib/sublayer/cli/templates/cli/lib/%project_name%/generators/example_generator.rb.tt +25 -0
  28. data/lib/sublayer/cli/templates/cli/lib/%project_name%/version.rb.tt +5 -0
  29. data/lib/sublayer/cli/templates/cli/lib/%project_name%.rb.tt +21 -0
  30. data/lib/sublayer/cli/templates/cli/spec/.keep +0 -0
  31. data/lib/sublayer/cli/templates/quick_script/%project_name%.rb +7 -0
  32. data/lib/sublayer/cli/templates/quick_script/README.md.tt +16 -0
  33. data/lib/sublayer/cli/templates/quick_script/actions/example_action.rb +11 -0
  34. data/lib/sublayer/cli/templates/quick_script/agents/example_agent.rb +17 -0
  35. data/lib/sublayer/cli/templates/quick_script/generators/example_generator.rb +21 -0
  36. data/lib/sublayer/cli.rb +59 -0
  37. data/lib/sublayer/components/output_adapters/formattable.rb +1 -0
  38. data/lib/sublayer/components/output_adapters/list_of_named_strings.rb +48 -0
  39. data/lib/sublayer/components/output_adapters/named_strings.rb +3 -1
  40. data/lib/sublayer/components/output_adapters/single_integer.rb +25 -0
  41. data/lib/sublayer/providers/gemini.rb +10 -22
  42. data/lib/sublayer/version.rb +1 -1
  43. data/lib/sublayer.rb +1 -1
  44. data/sublayer.gemspec +3 -3
  45. metadata +46 -36
@@ -0,0 +1,13 @@
1
+ module Sublayer
2
+ module Commands
3
+ class SubCommandBase < Thor
4
+ def self.banner(command, namespace = nil, subcommand = false)
5
+ "#{basename} #{subcommand_prefix} #{command.usage}"
6
+ end
7
+
8
+ def self.subcommand_prefix
9
+ self.name.gsub(%r{.*::}, '').gsub(%r{^[A-Z]}) { |match| match[0].downcase }.gsub(%r{[A-Z]}) { |match| "-#{match[0].downcase}" }
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,35 @@
1
+ require_relative "lib/<%= project_name.gsub("-", "_") %>/version"
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "<%= project_name %>"
5
+ spec.version = <%= project_name.camelize %>::VERSION
6
+ spec.authors = ["Your Name"]
7
+ spec.email = ["your.email@example.com"]
8
+
9
+ spec.summary = "Summary of your project"
10
+ spec.description = "Longer description of your project"
11
+ spec.homepage = "https://github.com/yourusername/<%= project_name %>"
12
+ spec.license = "MIT"
13
+ spec.required_ruby_version = ">= 2.6.0"
14
+
15
+ spec.metadata["allowed_push_host"] = "TODO: Set to your gem server 'https://example.com'"
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = "https://github.com/yourusername/<%= project_name %>"
19
+ spec.metadata["changelog_uri"] = "https://github.com/yourusername/<%= project_name %>/blob/master/CHANGELOG.md"
20
+
21
+ # Specify which files should be added to the gem when it is released.
22
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
24
+ `git ls-files -z`.split("\x0").reject do |f|
25
+ (f == __FILE__) || f.match(%r{\A(?:(?:test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
26
+ end
27
+ end
28
+ spec.bindir = "bin"
29
+ spec.executables = spec.files.grep(%r{\Abin/}) { |f| File.basename(f) }
30
+ spec.require_paths = ["lib"]
31
+
32
+ # Add dependencies here
33
+ spec.add_dependency "sublayer", "~> <%= sublayer_version %>"
34
+ spec.add_dependency "thor", "~> 1.2"
35
+ end
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ /log/
10
+
11
+ .rspec_status
12
+
13
+ .DS_Store
14
+ .DS_Store?
@@ -0,0 +1,7 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ group :development, :test do
6
+ gem "rspec", "~> 3.10"
7
+ end
@@ -0,0 +1,22 @@
1
+ # <%= project_name %>
2
+
3
+ Welcome to your new Sublayer CLI project!
4
+
5
+ ## Installation
6
+
7
+ Execute:
8
+
9
+ $ bundle install
10
+
11
+ ## Usage
12
+
13
+ To run your CLI application:
14
+
15
+ ```
16
+ $ bin/<%= project_name %>
17
+ ```
18
+
19
+ Available commands:
20
+ - `example`: Run the example generator
21
+ - `help`: Display the help message
22
+
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative "../lib/<%= project_name %>"
4
+
5
+ <%= project_name.camelize %>::CLI.start(ARGV)
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module <%= project_name.camelize %>
4
+ module Actions
5
+ class ExampleAction < Sublayer::Actions::Base
6
+ def initialize(input:)
7
+ @input = input
8
+ end
9
+
10
+ def call
11
+ puts "Performing action with input: #{@input}"
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module <%= project_name.camelize %>
4
+ module Agents
5
+ class ExampleAgent < Sublayer::Agents::Base
6
+ trigger_on_files_changed { ["example_file.txt"] }
7
+
8
+ goal_condition { @goal_reached }
9
+
10
+ check_status do
11
+ @status_checked = true
12
+ end
13
+
14
+ step do
15
+ @step_taken = true
16
+ @goal_reached = true
17
+ puts "Example agent step executed"
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module <%= project_name.camelize %>
4
+ class CLI < Thor
5
+ <%= project_name.camelize %>::Commands.constants.reject{ |command_class| command_class == :BaseCommand }.each do |command_class|
6
+ command = <%= project_name.camelize %>::Commands.const_get(command_class)
7
+ desc command.command_name, command.description
8
+ define_method(command.command_name) do |*args|
9
+ command.new(options).execute(*args)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,21 @@
1
+ module <%= project_name.camelize %>
2
+ module Commands
3
+ class BaseCommand
4
+ def self.command_name
5
+ name.split("::").last.gsub(/Command$/, '').downcase
6
+ end
7
+
8
+ def self.description
9
+ "Description for #{command_name}"
10
+ end
11
+
12
+ def initialize(options)
13
+ @options = options
14
+ end
15
+
16
+ def execute(*args)
17
+ raise NotImplementedError, "#{self.class} must implement #execute"
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,13 @@
1
+ module <%= project_name.camelize %>
2
+ module Commands
3
+ class ExampleCommand < BaseCommand
4
+ def self.description
5
+ "An example command that generates a story based on the command line arguments."
6
+ end
7
+
8
+ def execute(*args)
9
+ puts <%= project_name.camelize %>::Generators::ExampleGenerator.new(input: args.join(" ")).generate
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,19 @@
1
+ module <%= project_name.camelize %>
2
+ module Config
3
+ def self.load
4
+ config_path = File.join(File.dirname(__FILE__), "config", "sublayer.yml")
5
+
6
+ if File.exist?(config_path)
7
+ config = YAML.load_file(config_path)
8
+
9
+ Sublayer.configure do |c|
10
+ c.ai_provider = Object.const_get("Sublayer::Providers::#{config[:ai_provider]}")
11
+ c.ai_model = config[:ai_model]
12
+ c.logger = Sublayer::Logging::JsonLogger.new(File.join(Dir.pwd, 'log', 'sublayer.log'))
13
+ end
14
+ else
15
+ puts "Warning: config/sublayer.yml not found. Using default configuration."
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module <%= project_name.camelize %>
4
+ module Generators
5
+ class ExampleGenerator < Sublayer::Generators::Base
6
+ llm_output_adapter type: :single_string,
7
+ name: "generated_text",
8
+ description: "A simple generated text"
9
+
10
+ def initialize(input:)
11
+ @input = input
12
+ end
13
+
14
+ def generate
15
+ super
16
+ end
17
+
18
+ def prompt
19
+ <<-PROMPT
20
+ Generate a simple story based on this input: #{@input}
21
+ PROMPT
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module <%= project_name.camelize %>
4
+ VERSION = "0.0.1"
5
+ end
@@ -0,0 +1,21 @@
1
+ require "yaml"
2
+ require "thor"
3
+ require "sublayer"
4
+ require_relative "<%= project_name.gsub("-", "_") %>/version"
5
+ require_relative "<%= project_name.gsub("-", "_") %>/config"
6
+
7
+ Dir[File.join(__dir__, "<%= project_name.gsub("-", "_") %>", "commands", "*.rb")].each { |file| require file }
8
+ Dir[File.join(__dir__, "<%= project_name.gsub("-", "_") %>", "generators", "*.rb")].each { |file| require file }
9
+ Dir[File.join(__dir__, "<%= project_name.gsub("-", "_") %>", "actions", "*.rb")].each { |file| require file }
10
+ Dir[File.join(__dir__, "<%= project_name.gsub("-", "_") %>", "agents", "*.rb")].each { |file| require file }
11
+
12
+ require_relative "<%= project_name.gsub("-", "_") %>/cli"
13
+
14
+ module <%= project_name.camelize %>
15
+ class Error < StandardError; end
16
+ Config.load
17
+
18
+ def self.root
19
+ File.dirname __dir__
20
+ end
21
+ end
File without changes
@@ -0,0 +1,7 @@
1
+ require "yaml"
2
+ require "sublayer"
3
+
4
+ # Load any Actions, Generators, and Agents
5
+ Dir[File.join(__dir__, "actions", "*.rb")].each { |file| require file }
6
+ Dir[File.join(__dir__, "generators", "*.rb")].each { |file| require file }
7
+ Dir[File.join(__dir__, "agents", "*.rb")].each { |file| require file }
@@ -0,0 +1,16 @@
1
+ # <%= project_name.camelize %>
2
+
3
+ Welcome to your new Sublayer quick script project!
4
+
5
+ There are example Agents, Generators, and Actions in the respective folders.
6
+
7
+ ## Usage
8
+
9
+ Create your own custom agents, generators, and actions and use them in
10
+ `<%= project_name %>.rb`
11
+
12
+ Run your script:
13
+
14
+ ```
15
+ $ ruby <%= project_name %>.rb
16
+ ```
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ExampleAction < Sublayer::Actions::Base
4
+ def initialize(input:)
5
+ @input = input
6
+ end
7
+
8
+ def call
9
+ puts "Performing action with input: #{@input}"
10
+ end
11
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ExampleAgent < Sublayer::Agents::Base
4
+ trigger_on_files_changed { ["example_file.txt"] }
5
+
6
+ goal_condition { @goal_reached }
7
+
8
+ check_status do
9
+ @status_checked = true
10
+ end
11
+
12
+ step do
13
+ @step_taken = true
14
+ @goal_reached = true
15
+ puts "Example agent step executed"
16
+ end
17
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ExampleGenerator < Sublayer::Generators::Base
4
+ llm_output_adapter type: :single_string,
5
+ name: "generated_text",
6
+ description: "A simple generated text"
7
+
8
+ def initialize(input:)
9
+ @input = input
10
+ end
11
+
12
+ def generate
13
+ super
14
+ end
15
+
16
+ def prompt
17
+ <<-PROMPT
18
+ Generate a simple story based on this input: #{@input}
19
+ PROMPT
20
+ end
21
+ end
@@ -0,0 +1,59 @@
1
+ require "thor"
2
+
3
+ require "sublayer"
4
+ require "sublayer/version"
5
+ require "yaml"
6
+ require "fileutils"
7
+ require "active_support/inflector"
8
+
9
+ require_relative "cli/commands/subcommand_base"
10
+ require_relative "cli/commands/new_project"
11
+ require_relative "cli/commands/generator"
12
+ require_relative "cli/commands/agent"
13
+ require_relative "cli/commands/action"
14
+
15
+ module Sublayer
16
+ class CLI < Thor
17
+
18
+ register(Sublayer::Commands::NewProject, "new", "new PROJECT_NAME", "Creates a new Sublayer project")
19
+
20
+ register(Sublayer::Commands::Generator, "generate:generator", "generate:generator", "Generates a new Sublayer::Generator subclass for your project")
21
+ register(Sublayer::Commands::Agent, "generate:agent", "generate:agent", "Generates a new Sublayer::Agent subclass for your project")
22
+ register(Sublayer::Commands::Action, "generate:action", "generate:action", "Generates a new Sublayer::Action subclass for your project")
23
+
24
+ desc "version", "Prints the Sublayer version"
25
+ def version
26
+ puts Sublayer::VERSION
27
+ end
28
+
29
+ desc "help [COMMAND]", "Describe available commands or one specific command"
30
+ def help(command = nil, subcommand = false)
31
+ if command.nil?
32
+ puts "Sublayer CLI"
33
+ puts
34
+ puts "Usage:"
35
+ puts " sublayer COMMAND [OPTIONS]"
36
+ puts
37
+ puts "Commands:"
38
+ print_commands(self.class.commands.reject { |name, _| name == "help" || name == "version" })
39
+ puts
40
+ print_commands(self.class.commands.select { |name, _| name == "help" })
41
+ print_commands(self.class.commands.select { |name, _| name == "version" })
42
+ puts
43
+ puts "Run 'sublayer COMMAND --help' for more information on a command."
44
+ else
45
+ super
46
+ end
47
+ end
48
+
49
+ default_command :help
50
+
51
+ private
52
+
53
+ def print_commands(commands)
54
+ commands.each do |name, command|
55
+ puts " #{name.ljust(15)} # #{command.description}"
56
+ end
57
+ end
58
+ end
59
+ end
@@ -33,6 +33,7 @@ module Sublayer
33
33
  result[:items] = property.items.is_a?(OpenStruct) ? format_property(property.items) : property.items
34
34
  when 'object'
35
35
  result[:properties] = build_json_schema(property.properties) if property.properties
36
+ result[:required] = property.properties.select(&:required).map(&:name) if property.properties
36
37
  end
37
38
 
38
39
  result
@@ -0,0 +1,48 @@
1
+ module Sublayer
2
+ module Components
3
+ module OutputAdapters
4
+ class ListOfNamedStrings
5
+ attr_reader :name, :description, :attributes, :item_name
6
+
7
+ def initialize(options)
8
+ @name = options[:name]
9
+ @item_name = options[:item_name]
10
+ @description = options[:description]
11
+ @attributes = options[:attributes]
12
+ end
13
+
14
+ def properties
15
+ [
16
+ OpenStruct.new(
17
+ name: @name,
18
+ type: "array",
19
+ description: @description,
20
+ required: true,
21
+ items: OpenStruct.new(
22
+ type: "object",
23
+ description: "a single #{@item_name}",
24
+ name: @item_name,
25
+ properties: @attributes.map do |attribute|
26
+ OpenStruct.new(
27
+ type: "string",
28
+ name: attribute[:name],
29
+ description: attribute[:description],
30
+ required: true
31
+ )
32
+ end
33
+ )
34
+ )
35
+ ]
36
+ end
37
+
38
+ def materialize_result(raw_results)
39
+ raw_results.map do |raw_result|
40
+ OpenStruct.new(
41
+ @attributes.map { |attribute| [attribute[:name], raw_result[attribute[:name]]] }.to_h
42
+ )
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -17,7 +17,9 @@ module Sublayer
17
17
  type: "object",
18
18
  description: @description,
19
19
  required: true,
20
- properties: @attributes.map { |attribute| OpenStruct.new(type: "string", description: attribute[:description], required: true, name: attribute[:name]) }
20
+ properties: @attributes.map { |attribute|
21
+ OpenStruct.new(type: "string", description: attribute[:description], required: true, name: attribute[:name])
22
+ }
21
23
  )
22
24
  ]
23
25
  end
@@ -0,0 +1,25 @@
1
+ module Sublayer
2
+ module Components
3
+ module OutputAdapters
4
+ class SingleInteger
5
+ attr_reader :name, :description
6
+
7
+ def initialize(options)
8
+ @name = options[:name]
9
+ @description = options[:description]
10
+ end
11
+
12
+ def properties
13
+ [
14
+ OpenStruct.new(
15
+ name: @name,
16
+ type: 'integer',
17
+ description: @description,
18
+ required: true
19
+ )
20
+ ]
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -1,8 +1,5 @@
1
- # *UNSTABLE* Gemini function calling API is in beta.
2
- # Provider is not recommended until API update.
3
-
4
1
  # Sublayer.configuration.ai_provider = Sublayer::Providers::Gemini
5
- # Sublayer.configuration.ai_model = "gemini-1.5-pro"
2
+ # Sublayer.configuration.ai_model = "gemini-1.5-flash-latest"
6
3
 
7
4
  module Sublayer
8
5
  module Providers
@@ -26,23 +23,12 @@ module Sublayer
26
23
  text: "#{prompt}"
27
24
  },
28
25
  },
29
- tools: [{
30
- function_declarations: [
31
- {
32
- name: output_adapter.name,
33
- description: output_adapter.description,
34
- parameters: {
35
- type: "OBJECT",
36
- properties: output_adapter.format_properties,
37
- required: output_adapter.format_required
38
- }
39
- }
40
- ]
41
- }],
42
- tool_config: {
43
- function_calling_config: {
44
- mode: "ANY",
45
- allowed_function_names: [output_adapter.name]
26
+ generationConfig: {
27
+ responseMimeType: "application/json",
28
+ responseSchema: {
29
+ type: "OBJECT",
30
+ properties: output_adapter.format_properties,
31
+ required: output_adapter.format_required
46
32
  }
47
33
  }
48
34
  }.to_json,
@@ -66,7 +52,9 @@ module Sublayer
66
52
 
67
53
  raise "Error generating with Gemini, error: #{response.body}" unless response.success?
68
54
 
69
- argument = response.dig("candidates", 0, "content", "parts", 0, "functionCall", "args", output_adapter.name)
55
+ output = response.dig("candidates", 0, "content", "parts", 0, "text")
56
+
57
+ parsed_output = JSON.parse(output)[output_adapter.name]
70
58
  end
71
59
  end
72
60
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sublayer
4
- VERSION = "0.2.3"
4
+ VERSION = "0.2.5"
5
5
  end
data/lib/sublayer.rb CHANGED
@@ -7,7 +7,6 @@ require 'active_support/inflector'
7
7
  require 'ostruct'
8
8
  require "httparty"
9
9
  require "openai"
10
- require "nokogiri"
11
10
  require "listen"
12
11
  require "securerandom"
13
12
  require "time"
@@ -16,6 +15,7 @@ require_relative "sublayer/version"
16
15
 
17
16
  loader = Zeitwerk::Loader.for_gem
18
17
  loader.inflector.inflect('open_ai' => 'OpenAI')
18
+ loader.inflector.inflect("cli" => "CLI")
19
19
  loader.setup
20
20
 
21
21
  module Sublayer
data/sublayer.gemspec CHANGED
@@ -29,18 +29,18 @@ Gem::Specification.new do |spec|
29
29
  end
30
30
 
31
31
  spec.require_paths = ["lib"]
32
+ spec.bindir = "bin"
33
+ spec.executables = ["sublayer"]
32
34
 
33
35
  spec.add_dependency "ruby-openai"
34
- spec.add_dependency "colorize"
35
36
  spec.add_dependency "activesupport"
36
37
  spec.add_dependency "zeitwerk"
37
- spec.add_dependency "nokogiri", "~> 1.16.5"
38
38
  spec.add_dependency "httparty"
39
39
  spec.add_dependency "listen"
40
+ spec.add_dependency "thor"
40
41
 
41
42
  spec.add_development_dependency "rspec", "~> 3.12"
42
43
  spec.add_development_dependency "pry", "~> 0.14"
43
44
  spec.add_development_dependency "vcr", "~> 6.0"
44
45
  spec.add_development_dependency "webmock", "~> 3"
45
- spec.add_development_dependency "clag"
46
46
  end