nano-bots 0.1.1 → 1.0.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.
@@ -2,9 +2,14 @@
2
2
 
3
3
  require 'openai'
4
4
 
5
- require_relative './base'
5
+ require_relative 'base'
6
6
  require_relative '../crypto'
7
7
 
8
+ require_relative '../../logic/providers/openai/tools'
9
+ require_relative '../../controllers/interfaces/tools'
10
+
11
+ require_relative 'openai/tools'
12
+
8
13
  module NanoBot
9
14
  module Components
10
15
  module Providers
@@ -32,17 +37,16 @@ module NanoBot
32
37
  @client = ::OpenAI::Client.new(uri_base:, access_token: @credentials[:'access-token'])
33
38
  end
34
39
 
35
- def stream(input)
36
- provider = @settings.key?(:stream) ? @settings[:stream] : true
37
- interface = input[:interface].key?(:stream) ? input[:interface][:stream] : true
38
-
39
- provider && interface
40
- end
41
-
42
- def evaluate(input, &block)
40
+ def evaluate(input, streaming, cartridge, &feedback)
43
41
  messages = input[:history].map do |event|
44
- { role: event[:who] == 'user' ? 'user' : 'assistant',
45
- content: event[:message] }
42
+ if event[:message].nil? && event[:meta] && event[:meta][:tool_calls]
43
+ { role: 'assistant', content: nil, tool_calls: event[:meta][:tool_calls] }
44
+ elsif event[:who] == 'tool'
45
+ { role: event[:who], content: event[:message].to_s,
46
+ tool_call_id: event[:meta][:id], name: event[:meta][:name] }
47
+ else
48
+ { role: event[:who] == 'user' ? 'user' : 'assistant', content: event[:message] }
49
+ end
46
50
  end
47
51
 
48
52
  %i[instruction backdrop directive].each do |key|
@@ -62,26 +66,105 @@ module NanoBot
62
66
 
63
67
  payload.delete(:logit_bias) if payload.key?(:logit_bias) && payload[:logit_bias].nil?
64
68
 
65
- if stream(input)
69
+ payload[:tools] = input[:tools].map { |raw| NanoBot::Logic::OpenAI::Tools.adapt(raw) } if input[:tools]
70
+
71
+ if streaming
66
72
  content = ''
73
+ tools = []
67
74
 
68
75
  payload[:stream] = proc do |chunk, _bytesize|
69
- partial = chunk.dig('choices', 0, 'delta', 'content')
70
- if partial
71
- content += partial
72
- block.call({ who: 'AI', message: partial }, false)
76
+ partial_content = chunk.dig('choices', 0, 'delta', 'content')
77
+ partial_tools = chunk.dig('choices', 0, 'delta', 'tool_calls')
78
+
79
+ if partial_tools
80
+ partial_tools.each do |partial_tool|
81
+ tools[partial_tool['index']] = {} if tools[partial_tool['index']].nil?
82
+
83
+ partial_tool.keys.reject { |key| ['index'].include?(key) }.each do |key|
84
+ target = tools[partial_tool['index']]
85
+
86
+ if partial_tool[key].is_a?(Hash)
87
+ target[key] = {} if target[key].nil?
88
+ partial_tool[key].each_key do |sub_key|
89
+ target[key][sub_key] = '' if target[key][sub_key].nil?
90
+
91
+ target[key][sub_key] += partial_tool[key][sub_key]
92
+ end
93
+ else
94
+ target[key] = '' if target[key].nil?
95
+
96
+ target[key] += partial_tool[key]
97
+ end
98
+ end
99
+ end
73
100
  end
74
101
 
75
- block.call({ who: 'AI', message: content }, true) if chunk.dig('choices', 0, 'finish_reason')
102
+ if partial_content
103
+ content += partial_content
104
+ feedback.call(
105
+ { should_be_stored: false,
106
+ interaction: { who: 'AI', message: partial_content } }
107
+ )
108
+ end
109
+
110
+ if chunk.dig('choices', 0, 'finish_reason')
111
+ if tools&.size&.positive?
112
+ feedback.call(
113
+ { should_be_stored: true,
114
+ needs_another_round: true,
115
+ interaction: { who: 'AI', message: nil, meta: { tool_calls: tools } } }
116
+ )
117
+ Tools.apply(cartridge, input[:tools], tools, feedback).each do |interaction|
118
+ feedback.call({ should_be_stored: true, needs_another_round: true, interaction: })
119
+ end
120
+ end
121
+
122
+ feedback.call(
123
+ { should_be_stored: !(content.nil? || content == ''),
124
+ interaction: content.nil? || content == '' ? nil : { who: 'AI', message: content },
125
+ finished: true }
126
+ )
127
+ end
76
128
  end
77
129
 
78
- @client.chat(parameters: payload)
130
+ begin
131
+ @client.chat(parameters: payload)
132
+ rescue StandardError => e
133
+ raise e.class, e.response[:body] if e.response && e.response[:body]
134
+
135
+ raise e
136
+ end
79
137
  else
80
- result = @client.chat(parameters: payload)
138
+ begin
139
+ result = @client.chat(parameters: payload)
140
+ rescue StandardError => e
141
+ raise e.class, e.response[:body] if e.response && e.response[:body]
142
+
143
+ raise e
144
+ end
81
145
 
82
146
  raise StandardError, result['error'] if result['error']
83
147
 
84
- block.call({ who: 'AI', message: result.dig('choices', 0, 'message', 'content') }, true)
148
+ tools = result.dig('choices', 0, 'message', 'tool_calls')
149
+
150
+ if tools&.size&.positive?
151
+ feedback.call(
152
+ { should_be_stored: true,
153
+ needs_another_round: true,
154
+ interaction: { who: 'AI', message: nil, meta: { tool_calls: tools } } }
155
+ )
156
+ Tools.apply(cartridge, input[:tools], tools, feedback).each do |interaction|
157
+ feedback.call({ should_be_stored: true, needs_another_round: true, interaction: })
158
+ end
159
+ end
160
+
161
+ content = result.dig('choices', 0, 'message', 'content')
162
+
163
+ feedback.call(
164
+ { should_be_stored: !(content.nil? || content == ''),
165
+ interaction: content.nil? || content == '' ? nil : { who: 'AI', message: content },
166
+ finished: true }
167
+ )
85
168
  end
86
169
  end
87
170
 
@@ -3,7 +3,7 @@
3
3
  require 'babosa'
4
4
 
5
5
  require_relative '../logic/helpers/hash'
6
- require_relative './crypto'
6
+ require_relative 'crypto'
7
7
 
8
8
  module NanoBot
9
9
  module Components
data/components/stream.rb CHANGED
@@ -7,7 +7,12 @@ module NanoBot
7
7
  class Stream < StringIO
8
8
  def write(*args)
9
9
  if @callback
10
- @accumulated += args.first
10
+ begin
11
+ @accumulated += args.first
12
+ rescue StandardError => _e
13
+ @accumulated = "#{@accumulated.force_encoding('UTF-8')}#{args.first.force_encoding('UTF-8')}"
14
+ end
15
+
11
16
  @callback.call(@accumulated, args.first, false)
12
17
  end
13
18
  super
@@ -23,7 +23,7 @@ module NanoBot
23
23
 
24
24
  files.values.uniq.map do |file|
25
25
  cartridge = Logic::Helpers::Hash.symbolize_keys(
26
- YAML.safe_load(File.read(file[:path]), permitted_classes: [Symbol])
26
+ YAML.safe_load_file(file[:path], permitted_classes: [Symbol])
27
27
  ).merge({
28
28
  system: {
29
29
  id: file[:path].to_s.sub(/^#{Regexp.escape(file[:base])}/, '').sub(%r{^/}, '').sub(/\.[^.]+\z/,
@@ -6,9 +6,9 @@ require_relative '../logic/helpers/hash'
6
6
  require_relative '../components/provider'
7
7
  require_relative '../components/storage'
8
8
  require_relative '../components/stream'
9
- require_relative './interfaces/repl'
10
- require_relative './interfaces/eval'
11
- require_relative './session'
9
+ require_relative 'interfaces/repl'
10
+ require_relative 'interfaces/eval'
11
+ require_relative 'session'
12
12
 
13
13
  module NanoBot
14
14
  module Controllers
@@ -83,7 +83,7 @@ module NanoBot
83
83
  raise StandardError, "Cartridge file not found: \"#{path}\""
84
84
  end
85
85
 
86
- @cartridge = YAML.safe_load(File.read(elected_path), permitted_classes: [Symbol])
86
+ @cartridge = YAML.safe_load_file(elected_path, permitted_classes: [Symbol])
87
87
  end
88
88
 
89
89
  @safe_cartridge = Marshal.load(Marshal.dump(@cartridge))
@@ -81,7 +81,7 @@ module NanoBot
81
81
  when 'cartridge'
82
82
  puts YAML.dump(bot.cartridge)
83
83
  else
84
- raise "TODO: [#{params[:command]}]"
84
+ raise "Command not found: [#{params[:command]}]"
85
85
  end
86
86
  end
87
87
  end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rainbow'
4
+
5
+ require_relative '../../logic/cartridge/tools'
6
+ require_relative '../../logic/cartridge/safety'
7
+ require_relative '../../components/embedding'
8
+
9
+ module NanoBot
10
+ module Controllers
11
+ module Interfaces
12
+ module Tool
13
+ def self.confirming(session, cartridge, mode, feedback)
14
+ yeses = Logic::Cartridge::Safety.yeses(cartridge)
15
+ default_answer = Logic::Cartridge::Safety.default_answer(cartridge)
16
+ dispatch_feedback(session, cartridge, mode, feedback)
17
+ session.flush
18
+ answer = $stdin.gets.chomp.to_s.downcase.strip
19
+ answer = default_answer if answer == ''
20
+ session.print("\n")
21
+ yeses.include?(answer)
22
+ end
23
+
24
+ def self.adapt(feedback, adapter, cartridge)
25
+ call = {
26
+ parameters: %w[id name parameters parameters-as-json output],
27
+ values: [
28
+ feedback[:id], feedback[:name], feedback[:parameters],
29
+ feedback[:parameters].to_json,
30
+ feedback[:output]
31
+ ],
32
+ safety: { sandboxed: Logic::Cartridge::Safety.sandboxed?(cartridge) }
33
+ }
34
+
35
+ raise StandardError, 'conflicting adapters' if %i[fennel lua clojure].count { |key| !adapter[key].nil? } > 1
36
+
37
+ if adapter[:fennel]
38
+ call[:source] = adapter[:fennel]
39
+ Components::Embedding.fennel(**call)
40
+ elsif adapter[:clojure]
41
+ call[:source] = adapter[:clojure]
42
+ Components::Embedding.clojure(**call)
43
+ elsif adapter[:lua]
44
+ call[:parameters] = %w[id name parameters parameters_as_json output]
45
+ call[:source] = adapter[:lua]
46
+ Components::Embedding.lua(**call)
47
+ else
48
+ raise 'missing handler for adapter'
49
+ end
50
+ end
51
+
52
+ def self.dispatch_feedback(session, cartridge, mode, feedback)
53
+ enabled = Logic::Cartridge::Tools.feedback?(cartridge, mode.to_sym, feedback[:action].to_sym)
54
+
55
+ enabled = true if feedback[:action].to_sym == :confirming
56
+
57
+ return unless enabled
58
+
59
+ color = Logic::Cartridge::Tools.fetch_from_interface(
60
+ cartridge, mode.to_sym, feedback[:action].to_sym, [:color]
61
+ )
62
+
63
+ adapter = Tool.adapter(cartridge, mode, feedback)
64
+
65
+ if %i[fennel lua clojure].any? { |key| !adapter[key].nil? }
66
+ message = adapt(feedback, adapter, cartridge)
67
+ else
68
+ message = "#{feedback[:name]} #{feedback[:parameters].to_json}"
69
+
70
+ message += "\n#{feedback[:output]}" if feedback[:action].to_sym == :responding
71
+ end
72
+
73
+ message = "#{adapter[:prefix]}#{message}#{adapter[:suffix]}"
74
+
75
+ session.print(color.nil? ? message : Rainbow(message).send(color))
76
+ end
77
+
78
+ def self.adapter(cartridge, mode, feedback)
79
+ prefix = Logic::Cartridge::Tools.fetch_from_interface(
80
+ cartridge, mode.to_sym, feedback[:action].to_sym, [:prefix]
81
+ )
82
+
83
+ suffix = Logic::Cartridge::Tools.fetch_from_interface(
84
+ cartridge, mode.to_sym, feedback[:action].to_sym, [:suffix]
85
+ )
86
+
87
+ fennel = Logic::Cartridge::Tools.fetch_from_interface(
88
+ cartridge, mode.to_sym, feedback[:action].to_sym, %i[adapter fennel]
89
+ )
90
+
91
+ lua = Logic::Cartridge::Tools.fetch_from_interface(
92
+ cartridge, mode.to_sym, feedback[:action].to_sym, %i[adapter lua]
93
+ )
94
+
95
+ clojure = Logic::Cartridge::Tools.fetch_from_interface(
96
+ cartridge, mode.to_sym, feedback[:action].to_sym, %i[adapter clojure]
97
+ )
98
+
99
+ { prefix:, suffix:, fennel:, lua:, clojure: }
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -18,11 +18,10 @@ module NanoBot
18
18
  password = 'UNSAFE' unless password && password != ''
19
19
 
20
20
  {
21
- encryption: (
21
+ encryption:
22
22
  Components::Crypto.encrypt('SAFE') != 'SAFE' &&
23
- Components::Crypto.encrypt('SAFE') != Components::Crypto.encrypt('SAFE') &&
24
- Components::Crypto.decrypt(Components::Crypto.encrypt('SAFE')) == 'SAFE'
25
- ),
23
+ Components::Crypto.encrypt('SAFE') != Components::Crypto.encrypt('SAFE') &&
24
+ Components::Crypto.decrypt(Components::Crypto.encrypt('SAFE')) == 'SAFE',
26
25
  password: password != 'UNSAFE'
27
26
  }
28
27
  end
@@ -3,10 +3,14 @@
3
3
  require 'babosa'
4
4
 
5
5
  require 'fileutils'
6
+ require 'rainbow'
6
7
 
7
8
  require_relative '../logic/helpers/hash'
9
+ require_relative '../logic/cartridge/safety'
8
10
  require_relative '../logic/cartridge/streaming'
9
11
  require_relative '../logic/cartridge/interaction'
12
+ require_relative '../logic/cartridge/fetch'
13
+ require_relative 'interfaces/tools'
10
14
  require_relative '../components/storage'
11
15
  require_relative '../components/adapter'
12
16
  require_relative '../components/crypto'
@@ -14,6 +18,7 @@ require_relative '../components/crypto'
14
18
  module NanoBot
15
19
  module Controllers
16
20
  STREAM_TIMEOUT_IN_SECONDS = 5
21
+ INFINITE_LOOP_PREVENTION = 10
17
22
 
18
23
  class Session
19
24
  attr_accessor :stream
@@ -41,9 +46,9 @@ module NanoBot
41
46
  end
42
47
 
43
48
  def load_state
44
- @state = Logic::Helpers::Hash.symbolize_keys(JSON.parse(
45
- Components::Crypto.decrypt(File.read(@state_path))
46
- ))
49
+ @state = Logic::Helpers::Hash.symbolize_keys(
50
+ JSON.parse(Components::Crypto.decrypt(File.read(@state_path)))
51
+ )
47
52
  end
48
53
 
49
54
  def store_state!
@@ -68,7 +73,7 @@ module NanoBot
68
73
  mode: mode.to_s,
69
74
  input: message,
70
75
  message: Components::Adapter.apply(
71
- :input, Logic::Cartridge::Interaction.input(@cartridge, mode.to_sym, message)
76
+ Logic::Cartridge::Interaction.input(@cartridge, mode.to_sym, message), @cartridge
72
77
  )
73
78
  }
74
79
 
@@ -78,41 +83,88 @@ module NanoBot
78
83
  end
79
84
 
80
85
  def process(input, mode:)
86
+ interface = Logic::Helpers::Hash.fetch(@cartridge, [:interfaces, mode.to_sym]) || {}
87
+
88
+ input[:interface] = interface
89
+ input[:tools] = @cartridge[:tools]
90
+
91
+ needs_another_round = true
92
+
93
+ rounds = 0
94
+
95
+ while needs_another_round
96
+ needs_another_round = process_interaction(input, mode:)
97
+ rounds += 1
98
+ raise StandardError, 'infinite loop prevention' if rounds > INFINITE_LOOP_PREVENTION
99
+ end
100
+ end
101
+
102
+ def process_interaction(input, mode:)
81
103
  prefix = Logic::Cartridge::Affixes.get(@cartridge, mode.to_sym, :output, :prefix)
82
104
  suffix = Logic::Cartridge::Affixes.get(@cartridge, mode.to_sym, :output, :suffix)
83
105
 
84
- interface = Logic::Helpers::Hash.fetch(@cartridge, [:interfaces, mode.to_sym]) || {}
106
+ color = Logic::Cartridge::Fetch.cascate(
107
+ @cartridge, [[:interfaces, mode.to_sym, :output, :color], %i[interfaces output color]]
108
+ )
85
109
 
86
- streaming = Logic::Cartridge::Streaming.enabled?(@cartridge, mode.to_sym)
110
+ color = color.to_sym if color
87
111
 
88
- input[:interface] = interface
112
+ streaming = Logic::Cartridge::Streaming.enabled?(@cartridge, mode.to_sym)
89
113
 
90
114
  updated_at = Time.now
91
115
 
92
116
  ready = false
93
- @provider.evaluate(input) do |output, finished|
94
- updated_at = Time.now
95
-
96
- if finished
97
- event = Marshal.load(Marshal.dump(output))
98
117
 
99
- output = Logic::Cartridge::Interaction.output(
100
- @cartridge, mode.to_sym, output, streaming, finished
101
- )
118
+ needs_another_round = false
102
119
 
103
- output[:message] = Components::Adapter.apply(:output, output[:message])
120
+ @provider.evaluate(input, streaming, @cartridge) do |feedback|
121
+ needs_another_round = true if feedback[:needs_another_round]
104
122
 
105
- event[:mode] = mode.to_s
106
- event[:output] = "#{prefix}#{output[:message]}#{suffix}"
107
-
108
- @state[:history] << event
109
-
110
- self.print(output[:message]) unless streaming
123
+ updated_at = Time.now
111
124
 
112
- ready = true
113
- flush
114
- elsif streaming
115
- self.print(output[:message])
125
+ if feedback[:interaction] &&
126
+ feedback.dig(:interaction, :meta, :tool, :action) &&
127
+ feedback[:interaction][:meta][:tool][:action] == 'confirming'
128
+ Interfaces::Tool.confirming(self, @cartridge, mode, feedback[:interaction][:meta][:tool])
129
+ else
130
+
131
+ if feedback[:interaction] && feedback.dig(:interaction, :meta, :tool, :action)
132
+ Interfaces::Tool.dispatch_feedback(
133
+ self, @cartridge, mode, feedback[:interaction][:meta][:tool]
134
+ )
135
+ end
136
+
137
+ if feedback[:interaction]
138
+ event = Marshal.load(Marshal.dump(feedback[:interaction]))
139
+ event[:mode] = mode.to_s
140
+ event[:output] = nil
141
+
142
+ if feedback[:interaction][:who] == 'AI' && feedback[:interaction][:message]
143
+ event[:output] = feedback[:interaction][:message]
144
+ unless streaming
145
+ output = Logic::Cartridge::Interaction.output(
146
+ @cartridge, mode.to_sym, feedback[:interaction], streaming, feedback[:finished]
147
+ )
148
+ output[:message] = Components::Adapter.apply(output[:message], @cartridge)
149
+ event[:output] = (output[:message]).to_s
150
+ end
151
+ end
152
+
153
+ @state[:history] << event if feedback[:should_be_stored]
154
+
155
+ if event[:output] && ((!feedback[:finished] && streaming) || (!streaming && feedback[:finished]))
156
+ self.print(color ? Rainbow(event[:output]).send(color) : event[:output])
157
+ end
158
+
159
+ # The `print` function already outputs a prefix and a suffix, so
160
+ # we should add them afterwards to avoid printing them twice.
161
+ event[:output] = "#{prefix}#{event[:output]}#{suffix}"
162
+ end
163
+
164
+ if feedback[:finished]
165
+ flush
166
+ ready = true
167
+ end
116
168
  end
117
169
  end
118
170
 
@@ -122,6 +174,8 @@ module NanoBot
122
174
  end
123
175
 
124
176
  store_state! unless @stateless
177
+
178
+ needs_another_round
125
179
  end
126
180
 
127
181
  def flush
@@ -1,9 +1,8 @@
1
- version: '3.7'
2
-
1
+ ---
3
2
  services:
4
3
  nano-bots:
5
- image: ruby:3.2.2-slim-bullseye
6
- command: sh -c "apt-get update && apt-get install -y --no-install-recommends build-essential libffi-dev libsodium-dev lua5.4-dev && gem install nano-bots -v 0.1.1 && bash"
4
+ image: ruby:3.2.2-slim-bookworm
5
+ command: sh -c "apt-get update && apt-get install -y --no-install-recommends build-essential libffi-dev libsodium-dev lua5.4-dev curl && curl -s https://raw.githubusercontent.com/babashka/babashka/master/install | bash && gem install nano-bots -v 1.0.1 && bash"
7
6
  environment:
8
7
  OPENAI_API_ADDRESS: https://api.openai.com
9
8
  OPENAI_API_KEY: your-access-token
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../helpers/hash'
4
- require_relative './default'
4
+ require_relative 'default'
5
5
 
6
6
  module NanoBot
7
7
  module Logic
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../helpers/hash'
4
- require_relative './default'
4
+ require_relative 'default'
5
5
 
6
6
  module NanoBot
7
7
  module Logic
@@ -15,7 +15,7 @@ module NanoBot
15
15
  return @values if @values
16
16
 
17
17
  path = File.expand_path('../../static/cartridges/default.yml', __dir__)
18
- cartridge = YAML.safe_load(File.read(path), permitted_classes: [Symbol])
18
+ cartridge = YAML.safe_load_file(path, permitted_classes: [Symbol])
19
19
  @values = Logic::Helpers::Hash.symbolize_keys(cartridge)
20
20
  @values
21
21
  end
@@ -24,7 +24,7 @@ module NanoBot
24
24
  return @baseline if @baseline
25
25
 
26
26
  path = File.expand_path('../../static/cartridges/baseline.yml', __dir__)
27
- cartridge = YAML.safe_load(File.read(path), permitted_classes: [Symbol])
27
+ cartridge = YAML.safe_load_file(path, permitted_classes: [Symbol])
28
28
  @baseline = Logic::Helpers::Hash.symbolize_keys(cartridge)
29
29
  @baseline
30
30
  end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'default'
4
+ require_relative '../helpers/hash'
5
+
6
+ module NanoBot
7
+ module Logic
8
+ module Cartridge
9
+ module Fetch
10
+ def self.cascate(cartridge, paths)
11
+ results = paths.map { |path| Helpers::Hash.fetch(cartridge, path) }
12
+ result = results.find { |candidate| !candidate.nil? }
13
+ return result unless result.nil?
14
+
15
+ results = paths.map { |path| Helpers::Hash.fetch(Default.instance.values, path) }
16
+ result = results.find { |candidate| !candidate.nil? }
17
+ return result unless result.nil?
18
+
19
+ nil
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,9 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'sweet-moon'
4
-
5
- require_relative './affixes'
6
- require_relative './adapters'
3
+ require_relative 'affixes'
4
+ require_relative 'adapters'
7
5
 
8
6
  module NanoBot
9
7
  module Logic
@@ -12,23 +10,25 @@ module NanoBot
12
10
  def self.input(cartridge, interface, content)
13
11
  lua = Adapter.expression(cartridge, interface, :input, :lua)
14
12
  fennel = Adapter.expression(cartridge, interface, :input, :fennel)
13
+ clojure = Adapter.expression(cartridge, interface, :input, :clojure)
15
14
 
16
15
  prefix = Affixes.get(cartridge, interface, :input, :prefix)
17
16
  suffix = Affixes.get(cartridge, interface, :input, :suffix)
18
17
 
19
- { content:, prefix:, suffix:, lua:, fennel: }
18
+ { content:, prefix:, suffix:, lua:, fennel:, clojure: }
20
19
  end
21
20
 
22
21
  def self.output(cartridge, interface, result, streaming, _finished)
23
22
  if streaming
24
- result[:message] = { content: result[:message], lua: nil, fennel: nil }
23
+ result[:message] = { content: result[:message], lua: nil, fennel: nil, clojure: nil }
25
24
  return result
26
25
  end
27
26
 
28
27
  lua = Adapter.expression(cartridge, interface, :output, :lua)
29
28
  fennel = Adapter.expression(cartridge, interface, :output, :fennel)
29
+ clojure = Adapter.expression(cartridge, interface, :output, :clojure)
30
30
 
31
- result[:message] = { content: result[:message], lua:, fennel: }
31
+ result[:message] = { content: result[:message], lua:, fennel:, clojure: }
32
32
 
33
33
  result
34
34
  end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'fetch'
4
+
5
+ module NanoBot
6
+ module Logic
7
+ module Cartridge
8
+ module Safety
9
+ def self.default_answer(cartridge)
10
+ default = Fetch.cascate(cartridge, [%i[interfaces tools confirming default]])
11
+ return [] if default.nil?
12
+
13
+ default
14
+ end
15
+
16
+ def self.yeses(cartridge)
17
+ yeses_values = Fetch.cascate(cartridge, [%i[interfaces tools confirming yeses]])
18
+ return [] if yeses_values.nil?
19
+
20
+ yeses_values
21
+ end
22
+
23
+ def self.confirmable?(cartridge)
24
+ confirmable = Fetch.cascate(cartridge, [%i[safety tools confirmable]])
25
+ return true if confirmable.nil?
26
+
27
+ confirmable
28
+ end
29
+
30
+ def self.sandboxed?(cartridge)
31
+ sandboxed = Fetch.cascate(cartridge, [%i[safety functions sandboxed]])
32
+ return true if sandboxed.nil?
33
+
34
+ sandboxed
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end