solid_agent 0.1.1 → 0.2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +68 -0
- data/LICENSE +21 -0
- data/README.md +209 -18
- data/Rakefile +22 -2
- data/docs/agent-md-spec.md +803 -0
- data/docs/parser-design.md +1369 -0
- data/docs/registry-api.md +882 -0
- data/examples/README.md +60 -0
- data/examples/manifests/changelog_writer.agent.md +81 -0
- data/examples/manifests/usage.rb +96 -0
- data/examples/memory_handoff/app/agents/researcher_agent.rb +36 -0
- data/examples/memory_handoff/app/agents/writer_agent.rb +41 -0
- data/examples/memory_handoff/usage.rb +45 -0
- data/examples/persistent_conversation/app/agents/support_agent.rb +59 -0
- data/examples/persistent_conversation/app/controllers/support_conversations_controller.rb +24 -0
- data/examples/persistent_conversation/app/views/agents/support/instructions.md.erb +8 -0
- data/examples/persistent_conversation/usage.rb +51 -0
- data/examples/reasoning/app/agents/analysis_agent.rb +52 -0
- data/examples/reasoning/usage.rb +52 -0
- data/examples/run_tracking/app/agents/report_agent.rb +30 -0
- data/examples/run_tracking/app/controllers/agent_runs_controller.rb +43 -0
- data/examples/run_tracking/app/jobs/document_analysis_job.rb +17 -0
- data/examples/run_tracking/app/services/document_analysis_run.rb +68 -0
- data/examples/run_tracking/usage.rb +85 -0
- data/examples/tool_streaming/app/agents/browser_agent.rb +65 -0
- data/examples/tool_streaming/app/channels/tool_status_channel.rb +24 -0
- data/examples/tool_streaming/app/views/browser_agent/tools/fetch_url.json.erb +15 -0
- data/examples/tool_streaming/usage.rb +47 -0
- data/lib/generators/solid_agent/agent/agent_generator.rb +2 -2
- data/lib/generators/solid_agent/agent/templates/agent.rb.erb +3 -3
- data/lib/generators/solid_agent/context/templates/context_model.rb.erb +50 -16
- data/lib/generators/solid_agent/context/templates/create_generations.rb.erb +8 -0
- data/lib/generators/solid_agent/context/templates/create_messages.rb.erb +4 -0
- data/lib/generators/solid_agent/context/templates/generation_model.rb.erb +11 -0
- data/lib/generators/solid_agent/install/install_generator.rb +9 -0
- data/lib/generators/solid_agent/install/templates/agent_context.rb.erb +60 -17
- data/lib/generators/solid_agent/install/templates/agent_generation.rb.erb +23 -6
- data/lib/generators/solid_agent/install/templates/agent_memory.rb.erb +51 -0
- data/lib/generators/solid_agent/install/templates/agent_memory_entry.rb.erb +12 -0
- data/lib/generators/solid_agent/install/templates/agent_run.rb.erb +122 -0
- data/lib/generators/solid_agent/install/templates/create_agent_generations.rb.erb +13 -0
- data/lib/generators/solid_agent/install/templates/create_agent_memories.rb.erb +35 -0
- data/lib/generators/solid_agent/install/templates/create_agent_messages.rb.erb +5 -0
- data/lib/generators/solid_agent/install/templates/create_agent_runs.rb.erb +46 -0
- data/lib/generators/solid_agent/manifest/manifest_generator.rb +209 -0
- data/lib/generators/solid_agent/manifest/templates/agent.md.erb +39 -0
- data/lib/generators/solid_agent/manifest/templates/prompt.erb +13 -0
- data/lib/generators/solid_agent/reasons/reasons_generator.rb +83 -0
- data/lib/generators/solid_agent/reasons/templates/add_reasoning_columns.rb.erb +12 -0
- data/lib/solid_agent/agent_manifest/agent_builder.rb +323 -0
- data/lib/solid_agent/agent_manifest/errors.rb +26 -0
- data/lib/solid_agent/agent_manifest/exporter_registry.rb +117 -0
- data/lib/solid_agent/agent_manifest/exporters/agent_md_exporter.rb +115 -0
- data/lib/solid_agent/agent_manifest/exporters/base_exporter.rb +152 -0
- data/lib/solid_agent/agent_manifest/exporters/crewai_exporter.rb +125 -0
- data/lib/solid_agent/agent_manifest/exporters/dotprompt_exporter.rb +92 -0
- data/lib/solid_agent/agent_manifest/input_schema.rb +154 -0
- data/lib/solid_agent/agent_manifest/manifest.rb +306 -0
- data/lib/solid_agent/agent_manifest/parser_registry.rb +185 -0
- data/lib/solid_agent/agent_manifest/parsers/agent_md_parser.rb +87 -0
- data/lib/solid_agent/agent_manifest/parsers/base_parser.rb +223 -0
- data/lib/solid_agent/agent_manifest/parsers/crewai_parser.rb +201 -0
- data/lib/solid_agent/agent_manifest/parsers/dotprompt_parser.rb +122 -0
- data/lib/solid_agent/agent_manifest/parsers/github_prompt_parser.rb +143 -0
- data/lib/solid_agent/agent_manifest/picoschema.rb +254 -0
- data/lib/solid_agent/agent_manifest/registry/auth.rb +103 -0
- data/lib/solid_agent/agent_manifest/registry/client.rb +384 -0
- data/lib/solid_agent/agent_manifest/resource.rb +103 -0
- data/lib/solid_agent/agent_manifest/tool.rb +160 -0
- data/lib/solid_agent/agent_manifest/validator.rb +368 -0
- data/lib/solid_agent/agent_manifest.rb +381 -0
- data/lib/solid_agent/has_context.rb +251 -30
- data/lib/solid_agent/has_memory.rb +136 -0
- data/lib/solid_agent/has_reasons.rb +230 -0
- data/lib/solid_agent/model_naming.rb +42 -0
- data/lib/solid_agent/model_pricing.rb +93 -0
- data/lib/solid_agent/reasonable/reason.rb +205 -0
- data/lib/solid_agent/reasonable.rb +181 -0
- data/lib/solid_agent/records/agent.rb +520 -0
- data/lib/solid_agent/records/agent_run.rb +520 -0
- data/lib/solid_agent/records/agent_template.rb +142 -0
- data/lib/solid_agent/records/agent_version.rb +141 -0
- data/lib/solid_agent/records/ownable.rb +130 -0
- data/lib/solid_agent/records.rb +152 -0
- data/lib/solid_agent/run_fingerprint.rb +51 -0
- data/lib/solid_agent/tool_cache.rb +91 -0
- data/lib/solid_agent/version.rb +1 -1
- data/lib/solid_agent.rb +70 -3
- metadata +87 -1
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: <%= file_name %>
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
description: <%= @description %>
|
|
5
|
+
model: <%= @model %>
|
|
6
|
+
<% if @tools.any? -%>
|
|
7
|
+
|
|
8
|
+
tools:
|
|
9
|
+
<% @tools.each do |tool| -%>
|
|
10
|
+
- name: <%= tool %>
|
|
11
|
+
description: "TODO - Describe what <%= tool %> does"
|
|
12
|
+
inputSchema:
|
|
13
|
+
type: object
|
|
14
|
+
properties:
|
|
15
|
+
query:
|
|
16
|
+
type: string
|
|
17
|
+
description: "Input for <%= tool %>"
|
|
18
|
+
required:
|
|
19
|
+
- query
|
|
20
|
+
<% end -%>
|
|
21
|
+
<% end -%>
|
|
22
|
+
|
|
23
|
+
activeagent:
|
|
24
|
+
class_name: <%= agent_class_name %>
|
|
25
|
+
concerns:
|
|
26
|
+
<% if @contextual -%>
|
|
27
|
+
- has_context:
|
|
28
|
+
contextual: <%= @contextual %>
|
|
29
|
+
<% end -%>
|
|
30
|
+
<% if @tools.any? -%>
|
|
31
|
+
- has_tools: [<%= @tools.join(', ') %>]
|
|
32
|
+
<% end -%>
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
# <%= agent_title %>
|
|
36
|
+
|
|
37
|
+
<%= @description %>
|
|
38
|
+
|
|
39
|
+
<%= default_instructions %>
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
require "rails/generators/active_record"
|
|
5
|
+
|
|
6
|
+
module SolidAgent
|
|
7
|
+
module Generators
|
|
8
|
+
class ReasonsGenerator < Rails::Generators::Base
|
|
9
|
+
include Rails::Generators::Migration
|
|
10
|
+
source_root File.expand_path("templates", __dir__)
|
|
11
|
+
|
|
12
|
+
desc "Adds reasoning columns to an existing generation model for extended thinking support"
|
|
13
|
+
|
|
14
|
+
argument :model_name, type: :string, default: "AgentGeneration",
|
|
15
|
+
desc: "The model to add reasoning columns to"
|
|
16
|
+
|
|
17
|
+
class_option :content_column, type: :string, default: "reasoning_content",
|
|
18
|
+
desc: "Column name for storing reasoning content"
|
|
19
|
+
|
|
20
|
+
class_option :tokens_column, type: :string, default: "reasoning_tokens",
|
|
21
|
+
desc: "Column name for storing reasoning token count"
|
|
22
|
+
|
|
23
|
+
class_option :metadata_column, type: :string, default: "reasoning_metadata",
|
|
24
|
+
desc: "Column name for storing reasoning metadata (JSON)"
|
|
25
|
+
|
|
26
|
+
def self.next_migration_number(dirname)
|
|
27
|
+
ActiveRecord::Generators::Base.next_migration_number(dirname)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def create_migration
|
|
31
|
+
@table_name = model_name.underscore.pluralize
|
|
32
|
+
@content_column = options[:content_column]
|
|
33
|
+
@tokens_column = options[:tokens_column]
|
|
34
|
+
@metadata_column = options[:metadata_column]
|
|
35
|
+
|
|
36
|
+
migration_template(
|
|
37
|
+
"add_reasoning_columns.rb.erb",
|
|
38
|
+
"db/migrate/add_reasoning_to_#{@table_name}.rb"
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def add_concern_to_model
|
|
43
|
+
model_file = "app/models/#{model_name.underscore}.rb"
|
|
44
|
+
|
|
45
|
+
return unless File.exist?(model_file)
|
|
46
|
+
|
|
47
|
+
inject_into_class model_file, model_name do
|
|
48
|
+
" include SolidAgent::Reasonable\n\n"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
say "Added SolidAgent::Reasonable to #{model_name}", :green
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def show_post_install_message
|
|
55
|
+
say ""
|
|
56
|
+
say "Reasoning support added to #{model_name}!", :green
|
|
57
|
+
say ""
|
|
58
|
+
say "Next steps:", :yellow
|
|
59
|
+
say " 1. Run migrations: rails db:migrate"
|
|
60
|
+
say " 2. Use reasoning in your agents:"
|
|
61
|
+
say ""
|
|
62
|
+
say " class MyAgent < ApplicationAgent"
|
|
63
|
+
say " include SolidAgent::HasReasons"
|
|
64
|
+
say " include SolidAgent::HasContext"
|
|
65
|
+
say ""
|
|
66
|
+
say " has_reasons auto_capture: true, persist: true"
|
|
67
|
+
say ""
|
|
68
|
+
say " def analyze"
|
|
69
|
+
say " result = prompt("
|
|
70
|
+
say " messages: messages,"
|
|
71
|
+
say " extended_thinking: true"
|
|
72
|
+
say " )"
|
|
73
|
+
say ""
|
|
74
|
+
say " # Access reasoning"
|
|
75
|
+
say " last_reasoning.content"
|
|
76
|
+
say " total_reasoning_tokens"
|
|
77
|
+
say " end"
|
|
78
|
+
say " end"
|
|
79
|
+
say ""
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class AddReasoningTo<%= @table_name.camelize %> < ActiveRecord::Migration[7.0]
|
|
4
|
+
def change
|
|
5
|
+
add_column :<%= @table_name %>, :<%= @content_column %>, :text
|
|
6
|
+
add_column :<%= @table_name %>, :<%= @tokens_column %>, :integer, default: 0
|
|
7
|
+
add_column :<%= @table_name %>, :<%= @metadata_column %>, :jsonb, default: {}
|
|
8
|
+
|
|
9
|
+
add_index :<%= @table_name %>, :<%= @tokens_column %>,
|
|
10
|
+
name: "index_<%= @table_name %>_on_<%= @tokens_column %>"
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidAgent
|
|
4
|
+
module AgentManifest
|
|
5
|
+
# AgentBuilder generates ActiveAgent classes from Manifest definitions.
|
|
6
|
+
#
|
|
7
|
+
# Creates anonymous or named classes with the appropriate concerns included,
|
|
8
|
+
# tools defined, and system instructions configured.
|
|
9
|
+
#
|
|
10
|
+
# @example Build an agent class from a manifest
|
|
11
|
+
# manifest = AgentManifest.parse("research_assistant.agent.md")
|
|
12
|
+
# klass = AgentBuilder.build(manifest)
|
|
13
|
+
# agent = klass.new
|
|
14
|
+
# agent.research(query: "Ruby concurrency")
|
|
15
|
+
#
|
|
16
|
+
# @example Build with a custom base class
|
|
17
|
+
# klass = AgentBuilder.build(manifest, base_class: ApplicationAgent)
|
|
18
|
+
#
|
|
19
|
+
# @example Build with explicit naming
|
|
20
|
+
# klass = AgentBuilder.build(manifest, class_name: "CustomResearchAgent")
|
|
21
|
+
# CustomResearchAgent.new.call
|
|
22
|
+
#
|
|
23
|
+
class AgentBuilder
|
|
24
|
+
class << self
|
|
25
|
+
# Build an agent class from a manifest
|
|
26
|
+
#
|
|
27
|
+
# @param manifest [Manifest] The manifest to build from
|
|
28
|
+
# @param base_class [Class] Base class to inherit from
|
|
29
|
+
# @param class_name [String, nil] Optional class name for constant registration
|
|
30
|
+
# @param namespace [Module, nil] Optional namespace for constant registration
|
|
31
|
+
# @return [Class] The generated agent class
|
|
32
|
+
def build(manifest, base_class: nil, class_name: nil, namespace: nil)
|
|
33
|
+
# Determine base class
|
|
34
|
+
parent_class = determine_base_class(manifest, base_class)
|
|
35
|
+
|
|
36
|
+
# Create the class
|
|
37
|
+
klass = Class.new(parent_class)
|
|
38
|
+
|
|
39
|
+
# Configure the agent
|
|
40
|
+
configure_model(klass, manifest)
|
|
41
|
+
configure_concerns(klass, manifest)
|
|
42
|
+
configure_tools(klass, manifest)
|
|
43
|
+
configure_instructions(klass, manifest)
|
|
44
|
+
configure_metadata(klass, manifest)
|
|
45
|
+
|
|
46
|
+
# Register as constant if requested
|
|
47
|
+
if class_name || manifest_class_name(manifest)
|
|
48
|
+
register_constant(klass, class_name || manifest_class_name(manifest), namespace)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
klass
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Build and instantiate an agent from a manifest
|
|
55
|
+
#
|
|
56
|
+
# @param manifest [Manifest] The manifest to build from
|
|
57
|
+
# @param params [Hash] Parameters to pass to the agent
|
|
58
|
+
# @return [Object] The instantiated agent
|
|
59
|
+
def build_instance(manifest, params: {}, **options)
|
|
60
|
+
klass = build(manifest, **options)
|
|
61
|
+
klass.new(params)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Build from a file path
|
|
65
|
+
#
|
|
66
|
+
# @param path [String] Path to manifest file
|
|
67
|
+
# @param options [Hash] Options passed to build
|
|
68
|
+
# @return [Class] The generated agent class
|
|
69
|
+
def build_from_file(path, **options)
|
|
70
|
+
manifest = ParserRegistry.parse(path)
|
|
71
|
+
build(manifest, **options)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
# Determine the base class for the agent
|
|
77
|
+
def determine_base_class(manifest, explicit_base)
|
|
78
|
+
return explicit_base if explicit_base
|
|
79
|
+
|
|
80
|
+
# Check activeagent extension for parent class
|
|
81
|
+
aa_config = manifest.extensions&.dig(:activeagent) || {}
|
|
82
|
+
if aa_config[:parent_class]
|
|
83
|
+
aa_config[:parent_class].to_s.constantize
|
|
84
|
+
elsif defined?(ApplicationAgent)
|
|
85
|
+
ApplicationAgent
|
|
86
|
+
elsif defined?(ActiveAgent::Base)
|
|
87
|
+
ActiveAgent::Base
|
|
88
|
+
else
|
|
89
|
+
# Fallback to a basic class
|
|
90
|
+
Object
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Configure model/provider settings
|
|
95
|
+
def configure_model(klass, manifest)
|
|
96
|
+
return unless manifest.model.present?
|
|
97
|
+
|
|
98
|
+
provider, model_name = parse_model_identifier(manifest.model)
|
|
99
|
+
|
|
100
|
+
# Set class-level configuration
|
|
101
|
+
klass.class_eval do
|
|
102
|
+
class_attribute :_manifest_model, default: nil
|
|
103
|
+
class_attribute :_manifest_provider, default: nil
|
|
104
|
+
class_attribute :_manifest_config, default: {}
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
klass._manifest_model = model_name
|
|
108
|
+
klass._manifest_provider = provider
|
|
109
|
+
klass._manifest_config = manifest.config || {}
|
|
110
|
+
|
|
111
|
+
# If the base class supports model configuration, use it
|
|
112
|
+
if klass.respond_to?(:model)
|
|
113
|
+
klass.model(model_name)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
if klass.respond_to?(:provider) && provider
|
|
117
|
+
klass.provider(provider)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Parse model identifier into provider and model name
|
|
122
|
+
def parse_model_identifier(model_string)
|
|
123
|
+
if model_string.include?("/")
|
|
124
|
+
parts = model_string.split("/", 2)
|
|
125
|
+
[parts[0], parts[1]]
|
|
126
|
+
else
|
|
127
|
+
[nil, model_string]
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Configure concerns based on manifest extensions
|
|
132
|
+
def configure_concerns(klass, manifest)
|
|
133
|
+
aa_config = manifest.extensions&.dig(:activeagent) || {}
|
|
134
|
+
concerns = aa_config[:concerns] || []
|
|
135
|
+
|
|
136
|
+
concerns.each do |concern_config|
|
|
137
|
+
apply_concern(klass, concern_config)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Auto-include HasTools if manifest defines tools
|
|
141
|
+
if manifest.tools&.any? && !has_concern?(klass, SolidAgent::HasTools)
|
|
142
|
+
apply_concern(klass, "has_tools")
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Apply a single concern to the class
|
|
147
|
+
def apply_concern(klass, concern_config)
|
|
148
|
+
case concern_config
|
|
149
|
+
when String, Symbol
|
|
150
|
+
concern_name = concern_config.to_s
|
|
151
|
+
options = {}
|
|
152
|
+
when Hash
|
|
153
|
+
concern_name = concern_config.keys.first.to_s
|
|
154
|
+
options = concern_config.values.first || {}
|
|
155
|
+
else
|
|
156
|
+
return
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
concern_module = resolve_concern(concern_name)
|
|
160
|
+
return unless concern_module
|
|
161
|
+
|
|
162
|
+
klass.include(concern_module)
|
|
163
|
+
|
|
164
|
+
# Call the DSL method if it exists (e.g., has_context, has_tools)
|
|
165
|
+
if klass.respond_to?(concern_name)
|
|
166
|
+
if options.is_a?(Hash) && options.any?
|
|
167
|
+
klass.send(concern_name, **symbolize_keys(options))
|
|
168
|
+
elsif options.is_a?(Array)
|
|
169
|
+
klass.send(concern_name, *options)
|
|
170
|
+
else
|
|
171
|
+
klass.send(concern_name)
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Resolve a concern name to a module
|
|
177
|
+
def resolve_concern(name)
|
|
178
|
+
case name.to_s
|
|
179
|
+
when "has_context"
|
|
180
|
+
SolidAgent::HasContext
|
|
181
|
+
when "has_tools"
|
|
182
|
+
SolidAgent::HasTools
|
|
183
|
+
when "streams_tool_updates"
|
|
184
|
+
SolidAgent::StreamsToolUpdates
|
|
185
|
+
else
|
|
186
|
+
# Try to constantize
|
|
187
|
+
begin
|
|
188
|
+
name.to_s.camelize.constantize
|
|
189
|
+
rescue NameError
|
|
190
|
+
nil
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Check if a class already includes a concern
|
|
196
|
+
def has_concern?(klass, concern)
|
|
197
|
+
klass.included_modules.include?(concern)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Configure tools from manifest
|
|
201
|
+
def configure_tools(klass, manifest)
|
|
202
|
+
return unless manifest.tools&.any?
|
|
203
|
+
|
|
204
|
+
manifest.tools.each do |tool|
|
|
205
|
+
define_tool(klass, tool) unless tool.reference?
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Store tool references for later resolution
|
|
209
|
+
tool_refs = manifest.tools.select(&:reference?).map(&:ref)
|
|
210
|
+
if tool_refs.any?
|
|
211
|
+
klass.class_attribute :_manifest_tool_refs, default: []
|
|
212
|
+
klass._manifest_tool_refs = tool_refs
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Define a tool on the class
|
|
217
|
+
def define_tool(klass, tool)
|
|
218
|
+
return unless klass.respond_to?(:tool)
|
|
219
|
+
|
|
220
|
+
tool_name = tool.name.to_sym
|
|
221
|
+
tool_desc = tool.description
|
|
222
|
+
tool_schema = tool.input_schema || {}
|
|
223
|
+
|
|
224
|
+
klass.tool(tool_name) do
|
|
225
|
+
description tool_desc if tool_desc
|
|
226
|
+
|
|
227
|
+
# Define parameters from schema
|
|
228
|
+
properties = tool_schema["properties"] || tool_schema[:properties] || {}
|
|
229
|
+
required = tool_schema["required"] || tool_schema[:required] || []
|
|
230
|
+
|
|
231
|
+
properties.each do |param_name, param_schema|
|
|
232
|
+
param_opts = {
|
|
233
|
+
type: param_schema["type"] || param_schema[:type] || "string",
|
|
234
|
+
required: required.include?(param_name.to_s),
|
|
235
|
+
description: param_schema["description"] || param_schema[:description]
|
|
236
|
+
}
|
|
237
|
+
param_opts[:enum] = param_schema["enum"] || param_schema[:enum] if param_schema["enum"] || param_schema[:enum]
|
|
238
|
+
param_opts[:default] = param_schema["default"] || param_schema[:default] if param_schema.key?("default") || param_schema.key?(:default)
|
|
239
|
+
|
|
240
|
+
parameter param_name.to_sym, **param_opts.compact
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# Define a placeholder method if it doesn't exist
|
|
245
|
+
unless klass.instance_methods.include?(tool_name)
|
|
246
|
+
klass.define_method(tool_name) do |**args|
|
|
247
|
+
raise NotImplementedError, "Tool method '#{tool_name}' must be implemented"
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# Configure system instructions
|
|
253
|
+
def configure_instructions(klass, manifest)
|
|
254
|
+
return unless manifest.instructions.present? || manifest.template.present?
|
|
255
|
+
|
|
256
|
+
instructions = manifest.instructions || manifest.template
|
|
257
|
+
|
|
258
|
+
klass.class_attribute :_manifest_instructions, default: nil
|
|
259
|
+
klass._manifest_instructions = instructions
|
|
260
|
+
|
|
261
|
+
# If the base class supports system instructions, set them
|
|
262
|
+
if klass.respond_to?(:system_instructions)
|
|
263
|
+
klass.system_instructions(instructions)
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
# Store template for rendering
|
|
267
|
+
if manifest.template.present?
|
|
268
|
+
klass.class_attribute :_manifest_template, default: nil
|
|
269
|
+
klass._manifest_template = manifest.template
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# Configure metadata
|
|
274
|
+
def configure_metadata(klass, manifest)
|
|
275
|
+
klass.class_attribute :_manifest, default: nil
|
|
276
|
+
klass._manifest = manifest
|
|
277
|
+
|
|
278
|
+
klass.class_attribute :_manifest_name, default: nil
|
|
279
|
+
klass._manifest_name = manifest.name
|
|
280
|
+
|
|
281
|
+
klass.class_attribute :_manifest_version, default: nil
|
|
282
|
+
klass._manifest_version = manifest.version
|
|
283
|
+
|
|
284
|
+
klass.class_attribute :_manifest_description, default: nil
|
|
285
|
+
klass._manifest_description = manifest.description
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# Get class name from manifest
|
|
289
|
+
def manifest_class_name(manifest)
|
|
290
|
+
aa_config = manifest.extensions&.dig(:activeagent) || {}
|
|
291
|
+
return aa_config[:class_name] if aa_config[:class_name]
|
|
292
|
+
|
|
293
|
+
# Generate from manifest name
|
|
294
|
+
if manifest.name.present?
|
|
295
|
+
name = manifest.name.to_s.tr("-", "_").camelize
|
|
296
|
+
name += "Agent" unless name.end_with?("Agent")
|
|
297
|
+
name
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# Register the class as a constant
|
|
302
|
+
def register_constant(klass, name, namespace)
|
|
303
|
+
target = namespace || Object
|
|
304
|
+
|
|
305
|
+
if target.const_defined?(name, false)
|
|
306
|
+
target.send(:remove_const, name)
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
target.const_set(name, klass)
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
# Deep symbolize keys
|
|
313
|
+
def symbolize_keys(hash)
|
|
314
|
+
return hash unless hash.is_a?(Hash)
|
|
315
|
+
|
|
316
|
+
hash.transform_keys(&:to_sym).transform_values do |v|
|
|
317
|
+
v.is_a?(Hash) ? symbolize_keys(v) : v
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidAgent
|
|
4
|
+
module AgentManifest
|
|
5
|
+
# Base error class for all AgentManifest errors
|
|
6
|
+
class Error < SolidAgent::Error; end
|
|
7
|
+
|
|
8
|
+
# Raised when parsing a manifest file fails
|
|
9
|
+
class ParseError < Error; end
|
|
10
|
+
|
|
11
|
+
# Raised when manifest validation fails
|
|
12
|
+
class ValidationError < Error; end
|
|
13
|
+
|
|
14
|
+
# Raised when an unknown format is encountered
|
|
15
|
+
class UnknownFormatError < Error; end
|
|
16
|
+
|
|
17
|
+
# Raised when exporting a manifest fails
|
|
18
|
+
class ExportError < Error; end
|
|
19
|
+
|
|
20
|
+
# Raised when registry operations fail
|
|
21
|
+
class RegistryError < Error; end
|
|
22
|
+
|
|
23
|
+
# Raised when schema parsing or conversion fails
|
|
24
|
+
class SchemaError < Error; end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidAgent
|
|
4
|
+
module AgentManifest
|
|
5
|
+
# ExporterRegistry manages format exporters for converting Manifests to various formats.
|
|
6
|
+
#
|
|
7
|
+
# @example Register an exporter
|
|
8
|
+
# ExporterRegistry.register(:agent_md, AgentMdExporter)
|
|
9
|
+
#
|
|
10
|
+
# @example Export a manifest
|
|
11
|
+
# content = ExporterRegistry.export(manifest, :dotprompt)
|
|
12
|
+
#
|
|
13
|
+
# @example Export to file
|
|
14
|
+
# ExporterRegistry.export_to_file(manifest, :agent_md, "agent.agent.md")
|
|
15
|
+
#
|
|
16
|
+
class ExporterRegistry
|
|
17
|
+
class << self
|
|
18
|
+
# Registered exporters by format name
|
|
19
|
+
#
|
|
20
|
+
# @return [Hash<Symbol, Class>]
|
|
21
|
+
def exporters
|
|
22
|
+
@exporters ||= {}
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Register an exporter for a format
|
|
26
|
+
#
|
|
27
|
+
# @param format [Symbol] Format name
|
|
28
|
+
# @param exporter_class [Class] Exporter class
|
|
29
|
+
def register(format, exporter_class)
|
|
30
|
+
exporters[format.to_sym] = exporter_class
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Get the exporter for a format
|
|
34
|
+
#
|
|
35
|
+
# @param format [Symbol, String] Format name
|
|
36
|
+
# @return [Class] Exporter class
|
|
37
|
+
# @raise [UnknownFormatError] if format not recognized
|
|
38
|
+
def exporter_for(format)
|
|
39
|
+
format_sym = format.to_sym
|
|
40
|
+
exporters[format_sym] || raise(UnknownFormatError, "Unknown export format: #{format}")
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Check if a format is supported
|
|
44
|
+
#
|
|
45
|
+
# @param format [Symbol, String] Format name
|
|
46
|
+
# @return [Boolean]
|
|
47
|
+
def supports?(format)
|
|
48
|
+
exporters.key?(format.to_sym)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# List all registered format names
|
|
52
|
+
#
|
|
53
|
+
# @return [Array<Symbol>]
|
|
54
|
+
def formats
|
|
55
|
+
exporters.keys
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Export a manifest to a format string
|
|
59
|
+
#
|
|
60
|
+
# @param manifest [Manifest] The manifest to export
|
|
61
|
+
# @param format [Symbol, String] Target format
|
|
62
|
+
# @param options [Hash] Format-specific options
|
|
63
|
+
# @return [String] Exported content
|
|
64
|
+
def export(manifest, format, **options)
|
|
65
|
+
exporter = exporter_for(format)
|
|
66
|
+
exporter.export(manifest, **options)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Export a manifest to a file
|
|
70
|
+
#
|
|
71
|
+
# @param manifest [Manifest] The manifest to export
|
|
72
|
+
# @param format [Symbol, String] Target format
|
|
73
|
+
# @param path [String] Output file path
|
|
74
|
+
# @param options [Hash] Format-specific options
|
|
75
|
+
# @return [String] The path written to
|
|
76
|
+
def export_to_file(manifest, format, path, **options)
|
|
77
|
+
content = export(manifest, format, **options)
|
|
78
|
+
File.write(path, content, encoding: "UTF-8")
|
|
79
|
+
path
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Convert between formats
|
|
83
|
+
#
|
|
84
|
+
# @param input_path [String] Source file path
|
|
85
|
+
# @param output_format [Symbol, String] Target format
|
|
86
|
+
# @param output_path [String, nil] Output path (auto-generated if nil)
|
|
87
|
+
# @return [String] Output path
|
|
88
|
+
def convert(input_path, output_format, output_path = nil)
|
|
89
|
+
manifest = ParserRegistry.parse(input_path)
|
|
90
|
+
|
|
91
|
+
output_path ||= generate_output_path(input_path, output_format)
|
|
92
|
+
export_to_file(manifest, output_format, output_path)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
private
|
|
96
|
+
|
|
97
|
+
# Generate output path based on format
|
|
98
|
+
def generate_output_path(input_path, format)
|
|
99
|
+
base = File.basename(input_path, ".*")
|
|
100
|
+
# Remove any existing format extensions
|
|
101
|
+
base = base.sub(/\.(agent|prompt)$/, "")
|
|
102
|
+
dir = File.dirname(input_path)
|
|
103
|
+
|
|
104
|
+
extension = case format.to_sym
|
|
105
|
+
when :agent_md then ".agent.md"
|
|
106
|
+
when :dotprompt then ".prompt"
|
|
107
|
+
when :crewai then ".yaml"
|
|
108
|
+
when :github_prompt then ".prompt.md"
|
|
109
|
+
else ".#{format}"
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
File.join(dir, "#{base}#{extension}")
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|