foobara-agent 0.0.13 → 0.0.16
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 +4 -4
- data/CHANGELOG.md +14 -0
- data/src/foobara/agent/accomplish_goal.rb +29 -9
- data/src/foobara/agent/determine_base.rb +21 -27
- data/src/foobara/agent/determine_next_command_name_and_inputs.rb +56 -0
- data/src/foobara/agent/types/context.rb +6 -4
- data/src/foobara/agent/types/goal.rb +18 -0
- data/src/foobara/agent.rb +46 -23
- metadata +22 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 240c7fa690a890cd583288e9ed1ae2fb7fe5c70a5322778eccf29ac4bc6554f9
|
4
|
+
data.tar.gz: 1d41b1f61ef8126fda960b91231558cf3911fc91a806e005d6c1d36db17d16d6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c02671a8ad44659aa2c5aed14bf57f71d3bf14252757302d040e4e2b6060b89c3cbccbc54e35e647cd2c5433a03ef15b12c7dcb97c602fcf4d5edb153d7b2c01
|
7
|
+
data.tar.gz: 7b250f7059fb4fda74fff80c4b7c886219abe44b234b48e41a54b954df20a7f549b161cada201b9215d259f17ace85c2ad174f3c95631f45c1ce7c6e965bffa1
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
## [0.0.16] - 2025-07-09
|
2
|
+
|
3
|
+
- Tell the llm about previous goals
|
4
|
+
- Allow agents to be nameless
|
5
|
+
- Improve support for killing a running agent
|
6
|
+
|
7
|
+
## [0.0.15] - 2025-07-09
|
8
|
+
|
9
|
+
- Make use of Ai.default_llm_model
|
10
|
+
|
11
|
+
## [0.0.14] - 2025-07-08
|
12
|
+
|
13
|
+
- Better handle serialization/pre-commit loading for various entity-depth scenarios
|
14
|
+
|
1
15
|
## [0.0.13] - 2025-07-06
|
2
16
|
|
3
17
|
- Eliminate NotifyUser... result_type to save tokens/improve accuracy
|
@@ -19,18 +19,19 @@ module Foobara
|
|
19
19
|
io_out :duck
|
20
20
|
io_err :duck
|
21
21
|
agent Agent, :required
|
22
|
-
context Context, :required, "The current context of the agent"
|
23
22
|
maximum_command_calls :integer,
|
24
23
|
:allow_nil,
|
25
24
|
default: 25,
|
26
25
|
description: "Maximum number of commands to run before giving up"
|
27
26
|
llm_model :string,
|
28
27
|
:allow_nil,
|
29
|
-
one_of:
|
30
|
-
default:
|
28
|
+
one_of: Ai::AnswerBot::Types::ModelEnum,
|
29
|
+
default: Ai.default_llm_model,
|
31
30
|
description: "The model to use for the LLM"
|
32
31
|
max_llm_calls_per_minute :integer, :allow_nil
|
33
|
-
|
32
|
+
user_association_depth :symbol, :allow_nil, one_of: Foobara::AssociationDepth
|
33
|
+
result_entity_depth :symbol, :allow_nil, one_of: Foobara::AssociationDepth
|
34
|
+
pass_aggregates_to_llm :boolean, :allow_nil
|
34
35
|
end
|
35
36
|
|
36
37
|
result do
|
@@ -41,11 +42,13 @@ module Foobara
|
|
41
42
|
depends_on ListCommands
|
42
43
|
|
43
44
|
def execute
|
44
|
-
|
45
|
-
|
45
|
+
unless list_commands_already_ran?
|
46
|
+
simulate_describe_list_commands_command
|
47
|
+
simulate_list_commands_run
|
48
|
+
end
|
46
49
|
# simulate_describe_command_run_for_all_commands
|
47
50
|
|
48
|
-
until mission_accomplished or given_up
|
51
|
+
until mission_accomplished or given_up or killed
|
49
52
|
increment_command_calls
|
50
53
|
check_if_too_many_calls
|
51
54
|
|
@@ -66,7 +69,11 @@ module Foobara
|
|
66
69
|
attr_accessor :next_command_name, :next_command_inputs, :next_command_raw_inputs, :mission_accomplished,
|
67
70
|
:given_up, :next_command_class, :next_command, :command_outcome, :timed_out,
|
68
71
|
:final_result, :final_message, :command_response, :delayed_command_name,
|
69
|
-
:command_calls
|
72
|
+
:command_calls, :killed
|
73
|
+
|
74
|
+
def list_commands_already_ran?
|
75
|
+
context.command_log.any? { |log_entry| log_entry.command_name =~ /\bListCommands\z/ }
|
76
|
+
end
|
70
77
|
|
71
78
|
def simulate_list_commands_run
|
72
79
|
self.next_command_name = ListCommands.full_command_name
|
@@ -124,6 +131,8 @@ module Foobara
|
|
124
131
|
end
|
125
132
|
|
126
133
|
def determine_next_command_and_inputs(retries = 3, error_outcome = nil)
|
134
|
+
return if killed
|
135
|
+
|
127
136
|
if retries == 0
|
128
137
|
# TODO: test this path by irreparably breaking the needed commands
|
129
138
|
# :nocov:
|
@@ -141,7 +150,10 @@ module Foobara
|
|
141
150
|
|
142
151
|
compact_command_log
|
143
152
|
|
144
|
-
inputs_for_determine = {
|
153
|
+
inputs_for_determine = { agent:, llm_model: }
|
154
|
+
unless pass_aggregates_to_llm.nil?
|
155
|
+
inputs_for_determine[:pass_aggregates_to_llm] = pass_aggregates_to_llm
|
156
|
+
end
|
145
157
|
determine_command = DetermineNextCommandNameAndInputs.new(inputs_for_determine)
|
146
158
|
|
147
159
|
outcome = begin
|
@@ -429,6 +441,10 @@ module Foobara
|
|
429
441
|
self.final_message = message
|
430
442
|
end
|
431
443
|
|
444
|
+
def kill!
|
445
|
+
self.killed = true
|
446
|
+
end
|
447
|
+
|
432
448
|
def give_up!(message)
|
433
449
|
self.given_up = true
|
434
450
|
self.final_message = message
|
@@ -516,6 +532,10 @@ module Foobara
|
|
516
532
|
sleep seconds
|
517
533
|
end
|
518
534
|
end
|
535
|
+
|
536
|
+
def context
|
537
|
+
agent.context
|
538
|
+
end
|
519
539
|
end
|
520
540
|
end
|
521
541
|
end
|
@@ -2,17 +2,24 @@ require "foobara/llm_backed_command"
|
|
2
2
|
|
3
3
|
module Foobara
|
4
4
|
class Agent < CommandConnector
|
5
|
+
# TODO: just move this back to DetermineNextCommandAndInputs since it's now the only base class
|
5
6
|
class DetermineBase < Foobara::LlmBackedCommand
|
6
7
|
inputs do
|
7
|
-
|
8
|
+
pass_aggregates_to_llm :boolean, :allow_nil, "Should we send aggregates to the LLM or " \
|
9
|
+
"require it to fetch what it needs?"
|
10
|
+
agent Agent, :required
|
8
11
|
llm_model :string,
|
9
12
|
one_of: Foobara::Ai::AnswerBot::Types::ModelEnum,
|
10
|
-
default:
|
13
|
+
default: Ai.default_llm_model,
|
11
14
|
description: "The model to use for the LLM"
|
12
15
|
end
|
13
16
|
|
14
17
|
def association_depth
|
15
|
-
|
18
|
+
if pass_aggregates_to_llm
|
19
|
+
Foobara::AssociationDepth::AGGREGATE
|
20
|
+
else
|
21
|
+
Foobara::AssociationDepth::ATOM
|
22
|
+
end
|
16
23
|
end
|
17
24
|
|
18
25
|
def build_messages
|
@@ -49,33 +56,20 @@ module Foobara
|
|
49
56
|
p
|
50
57
|
end
|
51
58
|
|
52
|
-
def
|
53
|
-
|
54
|
-
|
55
|
-
description = self.class.description
|
56
|
-
|
57
|
-
instructions = "You are the implementation of a command called #{self.class.scoped_full_name}"
|
58
|
-
|
59
|
-
instructions += if description && !description.empty?
|
60
|
-
" which has the following description:\n\n#{self.class.description}\n\n"
|
61
|
-
else
|
62
|
-
# :nocov:
|
63
|
-
". "
|
64
|
-
# :nocov:
|
65
|
-
end
|
66
|
-
|
67
|
-
instructions += "You are working towards accomplishing the following goal:\n\n#{goal}\n\n"
|
68
|
-
instructions += "Your response should match the following JSON schema: \n\n#{self.class.result_json_schema}\n\n"
|
69
|
-
instructions += "You can get more details about the result schema for a specific command by " \
|
70
|
-
"choosing the DescribeCommand command. " \
|
71
|
-
"You will reply with nothing more than the JSON you've generated so that the calling code " \
|
72
|
-
"can successfully parse your answer."
|
59
|
+
def goal
|
60
|
+
context.current_goal.text
|
61
|
+
end
|
73
62
|
|
74
|
-
|
63
|
+
def previous_goal_and_status_pairs
|
64
|
+
if context.previous_goals && !context.previous_goals.empty?
|
65
|
+
context.previous_goals.map do |previous_goal|
|
66
|
+
[previous_goal.text, previous_goal.state]
|
67
|
+
end
|
68
|
+
end
|
75
69
|
end
|
76
70
|
|
77
|
-
def
|
78
|
-
context
|
71
|
+
def context
|
72
|
+
agent.context
|
79
73
|
end
|
80
74
|
end
|
81
75
|
end
|
@@ -3,6 +3,54 @@ require_relative "determine_base"
|
|
3
3
|
module Foobara
|
4
4
|
class Agent < CommandConnector
|
5
5
|
class DetermineNextCommandNameAndInputs < DetermineBase
|
6
|
+
class << self
|
7
|
+
def llm_instructions(assistant_association_depth, goal, previous_goals = nil)
|
8
|
+
key = [assistant_association_depth, goal, previous_goals]
|
9
|
+
|
10
|
+
@llm_instructions_cache ||= {}
|
11
|
+
|
12
|
+
if @llm_instructions_cache.key?(key)
|
13
|
+
@llm_instructions_cache[key]
|
14
|
+
else
|
15
|
+
@llm_instructions_cache[key] = build_llm_instructions(assistant_association_depth, goal, previous_goals)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def build_llm_instructions(assistant_association_depth, goal, previous_goals)
|
20
|
+
instructions = "You are the implementation of a command called #{scoped_full_name}"
|
21
|
+
|
22
|
+
instructions += if description && !description.empty?
|
23
|
+
" which has the following description:\n\n#{description}\n\n"
|
24
|
+
else
|
25
|
+
# :nocov:
|
26
|
+
". "
|
27
|
+
# :nocov:
|
28
|
+
end
|
29
|
+
|
30
|
+
result_schema = result_json_schema(assistant_association_depth)
|
31
|
+
|
32
|
+
if previous_goals && !previous_goals.empty?
|
33
|
+
instructions += "You have previously accomplished the following goals:\n\n"
|
34
|
+
|
35
|
+
previous_goals.each.with_index do |(previous_goal, state), index|
|
36
|
+
instructions += "previous goal #{index + 1}: #{previous_goal}\n"
|
37
|
+
instructions += "status of goal #{index + 1}: #{state}\n\n"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
instructions += "You are working towards accomplishing the following goal:\n\n#{goal}\n\n"
|
42
|
+
|
43
|
+
instructions += "Your response of which command to run next should match the following JSON schema:"
|
44
|
+
instructions += "\n\n#{result_schema}\n\n"
|
45
|
+
instructions += "You can get more details about the inputs and result schemas for a specific command by " \
|
46
|
+
"choosing the DescribeCommand command. " \
|
47
|
+
"You will reply with nothing more than the JSON you've generated so that the calling code " \
|
48
|
+
"can successfully parse your answer."
|
49
|
+
|
50
|
+
instructions
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
6
54
|
description "Returns the name of the next command to run and its inputs given the progress " \
|
7
55
|
"towards accomplishing the current goal. " \
|
8
56
|
"If the goal has been accomplished it will choose the " \
|
@@ -12,6 +60,14 @@ module Foobara
|
|
12
60
|
command :string, :required
|
13
61
|
inputs :attributes, :allow_nil
|
14
62
|
end
|
63
|
+
|
64
|
+
def determine_llm_instructions
|
65
|
+
self.llm_instructions = self.class.llm_instructions(
|
66
|
+
computed_user_association_depth,
|
67
|
+
goal,
|
68
|
+
previous_goal_and_status_pairs
|
69
|
+
)
|
70
|
+
end
|
15
71
|
end
|
16
72
|
end
|
17
73
|
end
|
@@ -1,15 +1,17 @@
|
|
1
|
+
require_relative "goal"
|
2
|
+
|
1
3
|
module Foobara
|
2
4
|
class Agent < CommandConnector
|
3
5
|
class Context < Foobara::Model
|
4
6
|
class << self
|
5
7
|
def for(goal)
|
6
|
-
new(command_log: [], current_goal: goal)
|
8
|
+
new(command_log: [], current_goal: Goal.new(text: goal))
|
7
9
|
end
|
8
10
|
end
|
9
11
|
|
10
12
|
attributes do
|
11
|
-
current_goal
|
12
|
-
previous_goals [
|
13
|
+
current_goal Goal, :required, "The current goal the agent needs to accomplish"
|
14
|
+
previous_goals [Goal]
|
13
15
|
# TODO: why doesn't this default of [] work as expected on newly created models?
|
14
16
|
command_log [CommandLogEntry], default: [],
|
15
17
|
description: "Log of all commands run so far and their outcomes"
|
@@ -18,7 +20,7 @@ module Foobara
|
|
18
20
|
def set_new_goal(goal)
|
19
21
|
self.previous_goals ||= []
|
20
22
|
previous_goals << current_goal
|
21
|
-
self.current_goal = goal
|
23
|
+
self.current_goal = Goal.new(text: goal)
|
22
24
|
end
|
23
25
|
end
|
24
26
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Foobara
|
2
|
+
class Agent < CommandConnector
|
3
|
+
class Goal < Foobara::Model
|
4
|
+
module States
|
5
|
+
ACCOMPLISHED = :accomplished
|
6
|
+
KILLED = :killed
|
7
|
+
FAILED = :failed
|
8
|
+
ERROR = :error
|
9
|
+
GAVE_UP = :gave_up
|
10
|
+
end
|
11
|
+
|
12
|
+
attributes do
|
13
|
+
text :string, :required
|
14
|
+
state :symbol, :allow_nil, one_of: States
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/src/foobara/agent.rb
CHANGED
@@ -15,6 +15,7 @@ module Foobara
|
|
15
15
|
|
16
16
|
attr_accessor :context,
|
17
17
|
:agent_name,
|
18
|
+
:agent_id,
|
18
19
|
:llm_model,
|
19
20
|
:current_accomplish_goal_command,
|
20
21
|
:result_type,
|
@@ -24,6 +25,7 @@ module Foobara
|
|
24
25
|
:io_out,
|
25
26
|
:io_err,
|
26
27
|
:max_llm_calls_per_minute,
|
28
|
+
:pass_aggregates_to_llm,
|
27
29
|
:result_entity_depth
|
28
30
|
|
29
31
|
def initialize(
|
@@ -38,12 +40,16 @@ module Foobara
|
|
38
40
|
io_err: nil,
|
39
41
|
max_llm_calls_per_minute: nil,
|
40
42
|
result_entity_depth: AssociationDepth::AGGREGATE,
|
43
|
+
pass_aggregates_to_llm: nil,
|
41
44
|
**opts
|
42
45
|
)
|
43
46
|
# TODO: shouldn't have to pass command_log here since it has a default, debug that
|
44
47
|
self.context = context
|
45
|
-
|
46
|
-
|
48
|
+
if agent_name
|
49
|
+
self.agent_name = agent_name
|
50
|
+
end
|
51
|
+
self.agent_id = agent_name || "Anon#{SecureRandom.hex(2)}"
|
52
|
+
self.llm_model = llm_model || Ai.default_llm_model
|
47
53
|
self.result_type = result_type
|
48
54
|
self.include_message_to_user_in_result = include_message_to_user_in_result
|
49
55
|
self.verbose = verbose
|
@@ -51,25 +57,21 @@ module Foobara
|
|
51
57
|
self.io_err = io_err
|
52
58
|
self.max_llm_calls_per_minute = max_llm_calls_per_minute
|
53
59
|
self.result_entity_depth = result_entity_depth
|
60
|
+
self.pass_aggregates_to_llm = pass_aggregates_to_llm
|
54
61
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
# end
|
62
|
+
pre_commit_transformer = if pass_aggregates_to_llm
|
63
|
+
CommandConnectors::Transformers::LoadAggregatesPreCommitTransformer
|
64
|
+
else
|
65
|
+
CommandConnectors::Transformers::LoadAtomsPreCommitTransformer
|
66
|
+
end
|
61
67
|
|
62
|
-
|
63
|
-
opts
|
64
|
-
|
65
|
-
|
66
|
-
end
|
68
|
+
opts = opts.merge(default_pre_commit_transformers: [
|
69
|
+
*opts[:default_pre_commit_transformers],
|
70
|
+
pre_commit_transformer
|
71
|
+
].uniq)
|
67
72
|
|
68
73
|
super(**opts)
|
69
74
|
|
70
|
-
# TODO: this should work now, switch to this approach
|
71
|
-
# add_default_inputs_transformer EntityToPrimaryKeyInputsTransformer
|
72
|
-
|
73
75
|
# TODO: push this convenience method up into base class?
|
74
76
|
command_classes&.each do |command_class|
|
75
77
|
connect(command_class)
|
@@ -107,11 +109,16 @@ module Foobara
|
|
107
109
|
end
|
108
110
|
|
109
111
|
def kill!
|
110
|
-
|
112
|
+
current_accomplish_goal_command&.kill!
|
113
|
+
state_machine.perform_transition!(:kill) do
|
114
|
+
if context
|
115
|
+
context.current_goal.state = Goal::States::KILLED
|
116
|
+
end
|
117
|
+
end
|
111
118
|
end
|
112
119
|
|
113
120
|
def killed?
|
114
|
-
state_machine.current_state ==
|
121
|
+
state_machine.current_state == Goal::States::KILLED
|
115
122
|
end
|
116
123
|
|
117
124
|
def accomplish_goal(
|
@@ -151,14 +158,27 @@ module Foobara
|
|
151
158
|
transition = if outcome.success?
|
152
159
|
:goal_accomplished
|
153
160
|
else
|
154
|
-
:
|
161
|
+
:goal_failed
|
155
162
|
end
|
156
163
|
|
157
|
-
|
164
|
+
unless killed?
|
165
|
+
state_machine.perform_transition!(transition) do
|
166
|
+
context.current_goal.state = if outcome.success?
|
167
|
+
Goal::States::ACCOMPLISHED
|
168
|
+
else
|
169
|
+
Goal::States::FAILED
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
158
173
|
end
|
159
174
|
rescue
|
160
175
|
# :nocov:
|
161
|
-
|
176
|
+
unless killed?
|
177
|
+
state_machine.perform_transition!(:goal_errored) do
|
178
|
+
context.current_goal.state = Goal::States::ERROR
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
162
182
|
raise
|
163
183
|
# :nocov:
|
164
184
|
end
|
@@ -171,7 +191,6 @@ module Foobara
|
|
171
191
|
inputs = {
|
172
192
|
goal:,
|
173
193
|
final_result_type: self.result_type,
|
174
|
-
context:,
|
175
194
|
agent: self
|
176
195
|
}
|
177
196
|
|
@@ -209,6 +228,10 @@ module Foobara
|
|
209
228
|
inputs[:result_entity_depth] = result_entity_depth
|
210
229
|
end
|
211
230
|
|
231
|
+
unless pass_aggregates_to_llm.nil?
|
232
|
+
inputs[:pass_aggregates_to_llm] = pass_aggregates_to_llm
|
233
|
+
end
|
234
|
+
|
212
235
|
inputs
|
213
236
|
end
|
214
237
|
|
@@ -246,7 +269,7 @@ module Foobara
|
|
246
269
|
# TODO: Support changing the final result type when the goal changes
|
247
270
|
NotifyUserThatCurrentGoalHasBeenAccomplished.for(
|
248
271
|
result_type:,
|
249
|
-
agent_id
|
272
|
+
agent_id:,
|
250
273
|
# TODO: Support changing this flag when the goal changes
|
251
274
|
include_message_to_user_in_result:,
|
252
275
|
result_entity_depth:
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: foobara-agent
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.16
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Miles Georgi
|
@@ -13,30 +13,42 @@ dependencies:
|
|
13
13
|
name: foobara
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
15
15
|
requirements:
|
16
|
-
- - "
|
16
|
+
- - ">="
|
17
17
|
- !ruby/object:Gem::Version
|
18
|
-
version: 0.0.
|
18
|
+
version: 0.0.136
|
19
|
+
- - "<"
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 2.0.0
|
19
22
|
type: :runtime
|
20
23
|
prerelease: false
|
21
24
|
version_requirements: !ruby/object:Gem::Requirement
|
22
25
|
requirements:
|
23
|
-
- - "
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
version: 0.0.136
|
29
|
+
- - "<"
|
24
30
|
- !ruby/object:Gem::Version
|
25
|
-
version: 0.0
|
31
|
+
version: 2.0.0
|
26
32
|
- !ruby/object:Gem::Dependency
|
27
33
|
name: foobara-llm-backed-command
|
28
34
|
requirement: !ruby/object:Gem::Requirement
|
29
35
|
requirements:
|
30
|
-
- - "
|
36
|
+
- - ">="
|
31
37
|
- !ruby/object:Gem::Version
|
32
|
-
version: 0.0
|
38
|
+
version: 1.0.0
|
39
|
+
- - "<"
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: 2.0.0
|
33
42
|
type: :runtime
|
34
43
|
prerelease: false
|
35
44
|
version_requirements: !ruby/object:Gem::Requirement
|
36
45
|
requirements:
|
37
|
-
- - "
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: 1.0.0
|
49
|
+
- - "<"
|
38
50
|
- !ruby/object:Gem::Version
|
39
|
-
version: 0.0
|
51
|
+
version: 2.0.0
|
40
52
|
email:
|
41
53
|
- azimux@gmail.com
|
42
54
|
executables: []
|
@@ -61,6 +73,7 @@ files:
|
|
61
73
|
- src/foobara/agent/notify_user_that_current_goal_has_been_accomplished.rb
|
62
74
|
- src/foobara/agent/types/command_log_entry.rb
|
63
75
|
- src/foobara/agent/types/context.rb
|
76
|
+
- src/foobara/agent/types/goal.rb
|
64
77
|
homepage: https://github.com/foobara/agent
|
65
78
|
licenses:
|
66
79
|
- MPL-2.0
|