solid_agent 0.0.0 → 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 +321 -0
- data/Rakefile +32 -0
- 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 +95 -0
- data/lib/generators/solid_agent/agent/templates/action.text.erb +10 -0
- data/lib/generators/solid_agent/agent/templates/agent.rb.erb +93 -0
- data/lib/generators/solid_agent/context/context_generator.rb +124 -0
- data/lib/generators/solid_agent/context/templates/context_model.rb.erb +134 -0
- data/lib/generators/solid_agent/context/templates/create_context.rb.erb +32 -0
- data/lib/generators/solid_agent/context/templates/create_generations.rb.erb +46 -0
- data/lib/generators/solid_agent/context/templates/create_messages.rb.erb +37 -0
- data/lib/generators/solid_agent/context/templates/generation_model.rb.erb +51 -0
- data/lib/generators/solid_agent/context/templates/message_model.rb.erb +47 -0
- data/lib/generators/solid_agent/install/install_generator.rb +92 -0
- data/lib/generators/solid_agent/install/templates/agent_context.rb.erb +171 -0
- data/lib/generators/solid_agent/install/templates/agent_generation.rb.erb +76 -0
- 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_message.rb.erb +76 -0
- data/lib/generators/solid_agent/install/templates/agent_run.rb.erb +122 -0
- data/lib/generators/solid_agent/install/templates/create_agent_contexts.rb.erb +32 -0
- data/lib/generators/solid_agent/install/templates/create_agent_generations.rb.erb +51 -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 +38 -0
- data/lib/generators/solid_agent/install/templates/create_agent_runs.rb.erb +46 -0
- data/lib/generators/solid_agent/install/templates/initializer.rb.erb +51 -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/generators/solid_agent/tool/templates/tool.json.erb +19 -0
- data/lib/generators/solid_agent/tool/tool_generator.rb +117 -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/engine.rb +16 -0
- data/lib/solid_agent/has_context.rb +670 -0
- data/lib/solid_agent/has_memory.rb +136 -0
- data/lib/solid_agent/has_reasons.rb +230 -0
- data/lib/solid_agent/has_tools.rb +257 -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/streams_tool_updates.rb +178 -0
- data/lib/solid_agent/tool_cache.rb +91 -0
- data/lib/solid_agent/version.rb +5 -0
- data/lib/solid_agent.rb +95 -0
- data/sig/solid_agent.rbs +4 -0
- metadata +174 -14
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidAgent
|
|
4
|
+
module AgentManifest
|
|
5
|
+
# Picoschema provides bidirectional conversion between Picoschema
|
|
6
|
+
# (a compact, YAML-optimized schema format from Dotprompt) and JSON Schema.
|
|
7
|
+
#
|
|
8
|
+
# Picoschema is designed for human readability while JSON Schema provides
|
|
9
|
+
# full validation capabilities.
|
|
10
|
+
#
|
|
11
|
+
# @example Picoschema syntax
|
|
12
|
+
# # Simple types
|
|
13
|
+
# query: string, the search query
|
|
14
|
+
# limit?: integer # optional field
|
|
15
|
+
#
|
|
16
|
+
# # Enums
|
|
17
|
+
# status: string(draft, published, archived)
|
|
18
|
+
#
|
|
19
|
+
# # Arrays
|
|
20
|
+
# tags: [string]
|
|
21
|
+
#
|
|
22
|
+
# # Nested objects
|
|
23
|
+
# author: object
|
|
24
|
+
# name: string
|
|
25
|
+
# email: string
|
|
26
|
+
#
|
|
27
|
+
# # Array of objects
|
|
28
|
+
# comments: [object]
|
|
29
|
+
# author: string
|
|
30
|
+
# text: string
|
|
31
|
+
#
|
|
32
|
+
class Picoschema
|
|
33
|
+
SCALAR_TYPES = %w[string integer number boolean any].freeze
|
|
34
|
+
|
|
35
|
+
class << self
|
|
36
|
+
# Convert Picoschema to JSON Schema
|
|
37
|
+
#
|
|
38
|
+
# @param picoschema [Hash] Picoschema definition
|
|
39
|
+
# @return [Hash] JSON Schema object
|
|
40
|
+
def to_json_schema(picoschema)
|
|
41
|
+
return { "type" => "object", "properties" => {} } if picoschema.nil?
|
|
42
|
+
return picoschema if json_schema?(picoschema)
|
|
43
|
+
|
|
44
|
+
parse_object(picoschema)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Convert JSON Schema to Picoschema
|
|
48
|
+
#
|
|
49
|
+
# @param json_schema [Hash] JSON Schema object
|
|
50
|
+
# @return [Hash] Picoschema definition
|
|
51
|
+
def from_json_schema(json_schema)
|
|
52
|
+
return {} if json_schema.nil?
|
|
53
|
+
return json_schema unless json_schema?(json_schema)
|
|
54
|
+
|
|
55
|
+
properties = json_schema["properties"] || json_schema[:properties] || {}
|
|
56
|
+
required = json_schema["required"] || json_schema[:required] || []
|
|
57
|
+
|
|
58
|
+
convert_properties(properties, required)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Parse a single type string (e.g., "string, description")
|
|
62
|
+
#
|
|
63
|
+
# @param type_str [String] Type definition string
|
|
64
|
+
# @return [Hash] JSON Schema for the field
|
|
65
|
+
def parse_type_string(type_str)
|
|
66
|
+
return { "type" => "any" } if type_str.nil? || type_str.strip == "any"
|
|
67
|
+
|
|
68
|
+
str = type_str.to_s.strip
|
|
69
|
+
|
|
70
|
+
# Handle enum types first: string(a, b, c), description
|
|
71
|
+
# Need to split after the closing paren for enum types
|
|
72
|
+
if str =~ /\A(\w+\([^)]+\))(,\s*(.+))?\z/
|
|
73
|
+
type_part = ::Regexp.last_match(1).strip
|
|
74
|
+
description = ::Regexp.last_match(3)&.strip
|
|
75
|
+
else
|
|
76
|
+
# Normal split for non-enum types
|
|
77
|
+
parts = str.split(",", 2)
|
|
78
|
+
type_part = parts[0].strip
|
|
79
|
+
description = parts[1]&.strip
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
result = parse_type_part(type_part)
|
|
83
|
+
result["description"] = description if description.present?
|
|
84
|
+
result
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
# Check if a hash looks like JSON Schema
|
|
90
|
+
def json_schema?(schema)
|
|
91
|
+
return false unless schema.is_a?(Hash)
|
|
92
|
+
|
|
93
|
+
schema.key?("type") || schema.key?(:type) ||
|
|
94
|
+
schema.key?("properties") || schema.key?(:properties) ||
|
|
95
|
+
schema.key?("$schema") || schema.key?(:"$schema")
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Parse an object schema (hash of field definitions)
|
|
99
|
+
def parse_object(schema_hash)
|
|
100
|
+
properties = {}
|
|
101
|
+
required = []
|
|
102
|
+
|
|
103
|
+
schema_hash.each do |key, value|
|
|
104
|
+
field_name, optional = parse_field_name(key.to_s)
|
|
105
|
+
field_schema = parse_field_value(value)
|
|
106
|
+
|
|
107
|
+
properties[field_name] = field_schema
|
|
108
|
+
required << field_name unless optional
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
result = { "type" => "object", "properties" => properties }
|
|
112
|
+
result["required"] = required if required.any?
|
|
113
|
+
result
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Parse field name, detecting optional marker
|
|
117
|
+
def parse_field_name(key)
|
|
118
|
+
if key.end_with?("?")
|
|
119
|
+
[key.chomp("?"), true] # optional
|
|
120
|
+
else
|
|
121
|
+
[key, false] # required
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Parse a field value (could be type string, hash, or array)
|
|
126
|
+
def parse_field_value(value)
|
|
127
|
+
case value
|
|
128
|
+
when String
|
|
129
|
+
parse_type_string(value)
|
|
130
|
+
when Hash
|
|
131
|
+
# Nested object or already parsed schema
|
|
132
|
+
if json_schema?(value)
|
|
133
|
+
value
|
|
134
|
+
else
|
|
135
|
+
parse_object(value)
|
|
136
|
+
end
|
|
137
|
+
when Array
|
|
138
|
+
parse_array_type(value)
|
|
139
|
+
else
|
|
140
|
+
{ "type" => "any" }
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Parse array type definition
|
|
145
|
+
def parse_array_type(array_value)
|
|
146
|
+
if array_value.empty?
|
|
147
|
+
{ "type" => "array", "items" => {} }
|
|
148
|
+
elsif array_value.first.is_a?(Hash)
|
|
149
|
+
# Array of objects with nested schema
|
|
150
|
+
{ "type" => "array", "items" => parse_object(array_value.first) }
|
|
151
|
+
else
|
|
152
|
+
# Array of scalar type
|
|
153
|
+
{ "type" => "array", "items" => parse_type_string(array_value.first.to_s) }
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Parse the type part (before the comma)
|
|
158
|
+
def parse_type_part(type_part)
|
|
159
|
+
# Handle array shorthand: [string] or [object]
|
|
160
|
+
if type_part.start_with?("[") && type_part.end_with?("]")
|
|
161
|
+
inner = type_part[1..-2].strip
|
|
162
|
+
if inner == "object"
|
|
163
|
+
return { "type" => "array", "items" => { "type" => "object" } }
|
|
164
|
+
else
|
|
165
|
+
return { "type" => "array", "items" => parse_type_part(inner) }
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Handle enum: string(option1, option2, option3)
|
|
170
|
+
if type_part =~ /\A(\w+)\((.+)\)\z/
|
|
171
|
+
base_type = ::Regexp.last_match(1)
|
|
172
|
+
enum_str = ::Regexp.last_match(2)
|
|
173
|
+
enum_values = parse_enum_values(enum_str)
|
|
174
|
+
return { "type" => base_type, "enum" => enum_values }
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Handle nested object reference
|
|
178
|
+
if type_part == "object"
|
|
179
|
+
return { "type" => "object" }
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Handle scalar types
|
|
183
|
+
if SCALAR_TYPES.include?(type_part.downcase)
|
|
184
|
+
return { "type" => type_part.downcase }
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Unknown type - preserve as-is
|
|
188
|
+
{ "type" => type_part }
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Parse enum values from string like "option1, option2, option3"
|
|
192
|
+
def parse_enum_values(enum_str)
|
|
193
|
+
enum_str.split(",").map do |val|
|
|
194
|
+
val = val.strip
|
|
195
|
+
# Remove quotes if present
|
|
196
|
+
val = val.gsub(/\A["']|["']\z/, "")
|
|
197
|
+
val
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Convert JSON Schema properties to Picoschema format
|
|
202
|
+
def convert_properties(properties, required_fields)
|
|
203
|
+
result = {}
|
|
204
|
+
|
|
205
|
+
properties.each do |name, schema|
|
|
206
|
+
optional = !required_fields.include?(name.to_s)
|
|
207
|
+
key = optional ? "#{name}?" : name.to_s
|
|
208
|
+
result[key] = convert_schema_to_pico(schema)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
result
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Convert a single JSON Schema field to Picoschema string
|
|
215
|
+
def convert_schema_to_pico(schema)
|
|
216
|
+
type = schema["type"] || schema[:type]
|
|
217
|
+
desc = schema["description"] || schema[:description]
|
|
218
|
+
enum_values = schema["enum"] || schema[:enum]
|
|
219
|
+
|
|
220
|
+
# Handle array types
|
|
221
|
+
if type == "array"
|
|
222
|
+
items = schema["items"] || schema[:items] || {}
|
|
223
|
+
items_type = items["type"] || items[:type] || "any"
|
|
224
|
+
if items_type == "object"
|
|
225
|
+
# Complex - return as hash
|
|
226
|
+
return [convert_properties(items["properties"] || {}, items["required"] || [])]
|
|
227
|
+
else
|
|
228
|
+
return "[#{items_type}]"
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# Handle object types
|
|
233
|
+
if type == "object" && (schema["properties"] || schema[:properties])
|
|
234
|
+
return convert_properties(
|
|
235
|
+
schema["properties"] || schema[:properties],
|
|
236
|
+
schema["required"] || schema[:required] || []
|
|
237
|
+
)
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# Build picoschema string
|
|
241
|
+
pico = type.to_s
|
|
242
|
+
if enum_values&.any?
|
|
243
|
+
pico = "#{type}(#{enum_values.join(", ")})"
|
|
244
|
+
end
|
|
245
|
+
if desc.present?
|
|
246
|
+
pico = "#{pico}, #{desc}"
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
pico
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidAgent
|
|
4
|
+
module AgentManifest
|
|
5
|
+
module Registry
|
|
6
|
+
# Auth handles token management for the ActiveAgents registry.
|
|
7
|
+
#
|
|
8
|
+
# Tokens are stored in ~/.activeagent/token by default.
|
|
9
|
+
# Can also be configured via environment variable.
|
|
10
|
+
#
|
|
11
|
+
# @example Check if logged in
|
|
12
|
+
# Registry::Auth.logged_in?
|
|
13
|
+
#
|
|
14
|
+
# @example Get current token
|
|
15
|
+
# token = Registry::Auth.token
|
|
16
|
+
#
|
|
17
|
+
# @example Save a token
|
|
18
|
+
# Registry::Auth.save_token("aa_live_xxx")
|
|
19
|
+
#
|
|
20
|
+
class Auth
|
|
21
|
+
TOKEN_PATH = File.expand_path("~/.activeagent/token")
|
|
22
|
+
TOKEN_ENV_VAR = "ACTIVEAGENT_TOKEN"
|
|
23
|
+
|
|
24
|
+
class << self
|
|
25
|
+
# Get the current authentication token
|
|
26
|
+
#
|
|
27
|
+
# Checks in order:
|
|
28
|
+
# 1. Environment variable (ACTIVEAGENT_TOKEN)
|
|
29
|
+
# 2. Token file (~/.activeagent/token)
|
|
30
|
+
#
|
|
31
|
+
# @return [String, nil] The token or nil if not authenticated
|
|
32
|
+
def token
|
|
33
|
+
# Check environment variable first
|
|
34
|
+
env_token = ENV[TOKEN_ENV_VAR]
|
|
35
|
+
return env_token if env_token.present?
|
|
36
|
+
|
|
37
|
+
# Check token file
|
|
38
|
+
return nil unless File.exist?(TOKEN_PATH)
|
|
39
|
+
|
|
40
|
+
File.read(TOKEN_PATH).strip.presence
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Save a token to the token file
|
|
44
|
+
#
|
|
45
|
+
# @param new_token [String] The token to save
|
|
46
|
+
# @return [Boolean] true if saved successfully
|
|
47
|
+
def save_token(new_token)
|
|
48
|
+
# Ensure directory exists
|
|
49
|
+
FileUtils.mkdir_p(File.dirname(TOKEN_PATH))
|
|
50
|
+
|
|
51
|
+
# Write token with secure permissions
|
|
52
|
+
File.write(TOKEN_PATH, new_token.strip)
|
|
53
|
+
File.chmod(0o600, TOKEN_PATH)
|
|
54
|
+
|
|
55
|
+
true
|
|
56
|
+
rescue StandardError => e
|
|
57
|
+
raise RegistryError, "Failed to save token: #{e.message}"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Clear the saved token
|
|
61
|
+
#
|
|
62
|
+
# @return [Boolean] true if cleared successfully
|
|
63
|
+
def clear_token
|
|
64
|
+
return true unless File.exist?(TOKEN_PATH)
|
|
65
|
+
|
|
66
|
+
File.delete(TOKEN_PATH)
|
|
67
|
+
true
|
|
68
|
+
rescue StandardError => e
|
|
69
|
+
raise RegistryError, "Failed to clear token: #{e.message}"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Check if user is logged in
|
|
73
|
+
#
|
|
74
|
+
# @return [Boolean]
|
|
75
|
+
def logged_in?
|
|
76
|
+
token.present?
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Require authentication, raising if not logged in
|
|
80
|
+
#
|
|
81
|
+
# @raise [RegistryError] if not authenticated
|
|
82
|
+
# @return [String] The token
|
|
83
|
+
def require_token!
|
|
84
|
+
t = token
|
|
85
|
+
raise RegistryError, "Not authenticated. Run 'activeagent login' or set #{TOKEN_ENV_VAR}" unless t
|
|
86
|
+
|
|
87
|
+
t
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Get authorization header for API requests
|
|
91
|
+
#
|
|
92
|
+
# @return [Hash] Authorization header
|
|
93
|
+
def auth_header
|
|
94
|
+
t = token
|
|
95
|
+
return {} unless t
|
|
96
|
+
|
|
97
|
+
{ "Authorization" => "Bearer #{t}" }
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|