summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Gemfile.lock2
-rw-r--r--components/storage.rb24
-rw-r--r--controllers/cartridges.rb28
-rw-r--r--controllers/instance.rb7
-rw-r--r--logic/cartridge/parser.rb56
-rw-r--r--nano-bots.gemspec1
-rw-r--r--ports/dsl/nano-bots.rb3
-rw-r--r--ports/dsl/nano-bots/cartridges.rb15
-rw-r--r--spec/data/cartridges/markdown.md37
-rw-r--r--spec/logic/cartridge/parser_spec.rb31
10 files changed, 178 insertions, 26 deletions
diff --git a/Gemfile.lock b/Gemfile.lock
index 6b3cc60..fc0da2e 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -13,6 +13,7 @@ PATH
pry (~> 0.14.2)
rainbow (~> 3.1, >= 3.1.1)
rbnacl (~> 7.1, >= 7.1.1)
+ redcarpet (~> 3.6)
ruby-openai (~> 6.3, >= 6.3.1)
sweet-moon (~> 0.0.7)
@@ -82,6 +83,7 @@ GEM
rainbow (3.1.1)
rbnacl (7.1.1)
ffi
+ redcarpet (3.6.0)
regexp_parser (2.8.3)
rexml (3.2.6)
rspec (3.12.0)
diff --git a/components/storage.rb b/components/storage.rb
index 6a3fe13..839cd02 100644
--- a/components/storage.rb
+++ b/components/storage.rb
@@ -8,6 +8,8 @@ require_relative 'crypto'
module NanoBot
module Components
class Storage
+ EXTENSIONS = %w[yml yaml markdown mdown mkdn md].freeze
+
def self.end_user(cartridge, environment)
user = ENV.fetch('NANO_BOTS_END_USER', nil)
@@ -74,11 +76,11 @@ module NanoBot
def self.cartridge_path(path)
partial = File.join(File.dirname(path), File.basename(path, File.extname(path)))
- candidates = [
- path,
- "#{partial}.yml",
- "#{partial}.yaml"
- ]
+ candidates = [path]
+
+ EXTENSIONS.each do |extension|
+ candidates << "#{partial}.#{extension}"
+ end
unless ENV.fetch('NANO_BOTS_CARTRIDGES_DIRECTORY', nil).nil?
directory = ENV.fetch('NANO_BOTS_CARTRIDGES_DIRECTORY').sub(%r{/$}, '')
@@ -88,8 +90,10 @@ module NanoBot
partial = partial.sub(%r{^\.?/}, '')
candidates << "#{directory}/#{partial}"
- candidates << "#{directory}/#{partial}.yml"
- candidates << "#{directory}/#{partial}.yaml"
+
+ EXTENSIONS.each do |extension|
+ candidates << "#{directory}/#{partial}.#{extension}"
+ end
end
directory = "#{user_home!.sub(%r{/$}, '')}/.local/share/nano-bots/cartridges"
@@ -99,8 +103,10 @@ module NanoBot
partial = partial.sub(%r{^\.?/}, '')
candidates << "#{directory}/#{partial}"
- candidates << "#{directory}/#{partial}.yml"
- candidates << "#{directory}/#{partial}.yaml"
+
+ EXTENSIONS.each do |extension|
+ candidates << "#{directory}/#{partial}.#{extension}"
+ end
candidates = candidates.uniq
diff --git a/controllers/cartridges.rb b/controllers/cartridges.rb
index df474a9..7215a99 100644
--- a/controllers/cartridges.rb
+++ b/controllers/cartridges.rb
@@ -3,16 +3,21 @@
require_relative '../components/storage'
require_relative '../logic/helpers/hash'
require_relative '../logic/cartridge/default'
+require_relative '../logic/cartridge/parser'
module NanoBot
module Controllers
class Cartridges
+ def self.load(path)
+ Logic::Cartridge::Parser.parse(File.read(path), format: File.extname(path))
+ end
+
def self.all
files = {}
path = Components::Storage.cartridges_path
- Dir.glob("#{path}/**/*.{yml,yaml}").each do |file|
+ Dir.glob("#{path}/**/*.{yml,yaml,markdown,mdown,mkdn,md}").each do |file|
files[Pathname.new(file).realpath] = {
base: path,
path: Pathname.new(file).realpath
@@ -22,16 +27,17 @@ module NanoBot
cartridges = []
files.values.uniq.map do |file|
- cartridge = Logic::Helpers::Hash.symbolize_keys(
- YAML.safe_load_file(file[:path], permitted_classes: [Symbol])
- ).merge({
- system: {
- id: file[:path].to_s.sub(/^#{Regexp.escape(file[:base])}/, '').sub(%r{^/}, '').sub(/\.[^.]+\z/,
- ''),
- path: file[:path],
- base: file[:base]
- }
- })
+ cartridge = load_cartridge(file[:path]).merge(
+ {
+ system: {
+ id: file[:path].to_s.sub(
+ /^#{Regexp.escape(file[:base])}/, ''
+ ).sub(%r{^/}, '').sub(/\.[^.]+\z/, ''),
+ path: file[:path],
+ base: file[:base]
+ }
+ }
+ )
next if cartridge[:meta][:name].nil?
diff --git a/controllers/instance.rb b/controllers/instance.rb
index 259a548..85b97ec 100644
--- a/controllers/instance.rb
+++ b/controllers/instance.rb
@@ -1,11 +1,10 @@
# frozen_string_literal: true
-require 'yaml'
-
require_relative '../logic/helpers/hash'
require_relative '../components/provider'
require_relative '../components/storage'
require_relative '../components/stream'
+require_relative 'cartridges'
require_relative 'interfaces/repl'
require_relative 'interfaces/eval'
require_relative 'session'
@@ -83,13 +82,11 @@ module NanoBot
raise StandardError, "Cartridge file not found: \"#{path}\""
end
- @cartridge = YAML.safe_load_file(elected_path, permitted_classes: [Symbol])
+ @cartridge = Cartridges.load(elected_path)
end
@safe_cartridge = Marshal.load(Marshal.dump(@cartridge))
- @cartridge = Logic::Helpers::Hash.symbolize_keys(@cartridge)
-
inject_environment_variables!(@cartridge)
end
diff --git a/logic/cartridge/parser.rb b/logic/cartridge/parser.rb
new file mode 100644
index 0000000..440c929
--- /dev/null
+++ b/logic/cartridge/parser.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+require 'singleton'
+
+require 'redcarpet'
+require 'redcarpet/render_strip'
+
+module NanoBot
+ module Logic
+ module Cartridge
+ module Parser
+ def self.parse(raw, format:)
+ normalized = format.to_s.downcase.gsub('.', '')
+
+ if %w[yml yaml].include?(normalized)
+ yaml(raw)
+ elsif %w[markdown mdown mkdn md].include?(normalized)
+ markdown(raw)
+ else
+ raise "Unknown cartridge format: '#{format}'"
+ end
+ end
+
+ def self.markdown(raw)
+ yaml(Markdown.instance.render(raw))
+ end
+
+ def self.yaml(raw)
+ Logic::Helpers::Hash.symbolize_keys(
+ YAML.safe_load(raw, permitted_classes: [Symbol])
+ )
+ end
+
+ class Renderer < Redcarpet::Render::Base
+ def block_code(code, _language)
+ "\n#{code}\n"
+ end
+ end
+
+ class Markdown
+ include Singleton
+
+ attr_reader :markdown
+
+ def initialize
+ @markdown = Redcarpet::Markdown.new(Renderer, fenced_code_blocks: true)
+ end
+
+ def render(raw)
+ @markdown.render(raw)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/nano-bots.gemspec b/nano-bots.gemspec
index f843f37..87c4f48 100644
--- a/nano-bots.gemspec
+++ b/nano-bots.gemspec
@@ -37,6 +37,7 @@ Gem::Specification.new do |spec|
spec.add_dependency 'pry', '~> 0.14.2'
spec.add_dependency 'rainbow', '~> 3.1', '>= 3.1.1'
spec.add_dependency 'rbnacl', '~> 7.1', '>= 7.1.1'
+ spec.add_dependency 'redcarpet', '~> 3.6'
spec.add_dependency 'sweet-moon', '~> 0.0.7'
spec.add_dependency 'cohere-ai', '~> 1.0', '>= 1.0.1'
diff --git a/ports/dsl/nano-bots.rb b/ports/dsl/nano-bots.rb
index e01b2c4..20d8f14 100644
--- a/ports/dsl/nano-bots.rb
+++ b/ports/dsl/nano-bots.rb
@@ -8,6 +8,7 @@ require_relative '../../controllers/instance'
require_relative '../../controllers/security'
require_relative '../../controllers/interfaces/cli'
require_relative '../../components/stream'
+require_relative 'nano-bots/cartridges'
module NanoBot
def self.new(cartridge: '-', state: '-', environment: {})
@@ -24,7 +25,7 @@ module NanoBot
end
def self.cartridges
- Controllers::Cartridges.all
+ Cartridges
end
def self.cli
diff --git a/ports/dsl/nano-bots/cartridges.rb b/ports/dsl/nano-bots/cartridges.rb
new file mode 100644
index 0000000..7c0f05b
--- /dev/null
+++ b/ports/dsl/nano-bots/cartridges.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require_relative '../../controllers/cartridges'
+
+module NanoBot
+ module Cartridges
+ def self.all
+ Controllers::Cartridges.all
+ end
+
+ def self.load(path)
+ Controllers::Cartridges.load(path)
+ end
+ end
+end
diff --git a/spec/data/cartridges/markdown.md b/spec/data/cartridges/markdown.md
new file mode 100644
index 0000000..cd50b7b
--- /dev/null
+++ b/spec/data/cartridges/markdown.md
@@ -0,0 +1,37 @@
+A cartridge is a YAML file with human-readable data that outlines the bot's goals, expected behaviors, and settings for authentication and provider utilization.
+
+We begin with the meta section, which provides information about what this cartridge is designed for:
+
+```yaml
+meta:
+ symbol: 🤖
+ name: ChatGPT 4 Turbo
+ author: icebaker
+ version: 0.0.1
+ license: CC0-1.0
+ description: A helpful assistant.
+```
+
+It includes details like versioning and license.
+
+Next, we add a behavior section that will provide the bot with a directive on how it should behave:
+
+```yaml
+behaviors:
+ interaction:
+ directive: You are a helpful assistant.
+```
+
+Now, we need to provide instructions on how this Nano Bot should connect with a provider, which credentials to use, and what specific configurations for the LLM are required:
+
+```yaml
+provider:
+ id: openai
+ credentials:
+ access-token: ENV/OPENAI_API_KEY
+ settings:
+ user: ENV/NANO_BOTS_END_USER
+ model: gpt-4-1106-preview
+```
+
+In my API, I have set the environment variables `OPENAI_API_KEY` and `NANO_BOTS_END_USER`, which is where the values for these will come from.
diff --git a/spec/logic/cartridge/parser_spec.rb b/spec/logic/cartridge/parser_spec.rb
new file mode 100644
index 0000000..f8d1302
--- /dev/null
+++ b/spec/logic/cartridge/parser_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require_relative '../../../logic/cartridge/parser'
+
+RSpec.describe NanoBot::Logic::Cartridge::Parser do
+ context 'markdown' do
+ let(:raw) { File.read('spec/data/cartridges/markdown.md') }
+
+ it 'parses markdown cartridge' do
+ expect(described_class.parse(raw, format: 'md')).to eq(
+ { meta: {
+ symbol: '🤖',
+ name: 'ChatGPT 4 Turbo',
+ author: 'icebaker',
+ version: '0.0.1',
+ license: 'CC0-1.0',
+ description: 'A helpful assistant.'
+ },
+ behaviors: { interaction: { directive: 'You are a helpful assistant.' } },
+ provider: {
+ id: 'openai',
+ credentials: { 'access-token': 'ENV/OPENAI_API_KEY' },
+ settings: {
+ user: 'ENV/NANO_BOTS_END_USER',
+ model: 'gpt-4-1106-preview'
+ }
+ } }
+ )
+ end
+ end
+end