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.
Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +68 -0
  3. data/LICENSE +21 -0
  4. data/README.md +321 -0
  5. data/Rakefile +32 -0
  6. data/docs/agent-md-spec.md +803 -0
  7. data/docs/parser-design.md +1369 -0
  8. data/docs/registry-api.md +882 -0
  9. data/examples/README.md +60 -0
  10. data/examples/manifests/changelog_writer.agent.md +81 -0
  11. data/examples/manifests/usage.rb +96 -0
  12. data/examples/memory_handoff/app/agents/researcher_agent.rb +36 -0
  13. data/examples/memory_handoff/app/agents/writer_agent.rb +41 -0
  14. data/examples/memory_handoff/usage.rb +45 -0
  15. data/examples/persistent_conversation/app/agents/support_agent.rb +59 -0
  16. data/examples/persistent_conversation/app/controllers/support_conversations_controller.rb +24 -0
  17. data/examples/persistent_conversation/app/views/agents/support/instructions.md.erb +8 -0
  18. data/examples/persistent_conversation/usage.rb +51 -0
  19. data/examples/reasoning/app/agents/analysis_agent.rb +52 -0
  20. data/examples/reasoning/usage.rb +52 -0
  21. data/examples/run_tracking/app/agents/report_agent.rb +30 -0
  22. data/examples/run_tracking/app/controllers/agent_runs_controller.rb +43 -0
  23. data/examples/run_tracking/app/jobs/document_analysis_job.rb +17 -0
  24. data/examples/run_tracking/app/services/document_analysis_run.rb +68 -0
  25. data/examples/run_tracking/usage.rb +85 -0
  26. data/examples/tool_streaming/app/agents/browser_agent.rb +65 -0
  27. data/examples/tool_streaming/app/channels/tool_status_channel.rb +24 -0
  28. data/examples/tool_streaming/app/views/browser_agent/tools/fetch_url.json.erb +15 -0
  29. data/examples/tool_streaming/usage.rb +47 -0
  30. data/lib/generators/solid_agent/agent/agent_generator.rb +95 -0
  31. data/lib/generators/solid_agent/agent/templates/action.text.erb +10 -0
  32. data/lib/generators/solid_agent/agent/templates/agent.rb.erb +93 -0
  33. data/lib/generators/solid_agent/context/context_generator.rb +124 -0
  34. data/lib/generators/solid_agent/context/templates/context_model.rb.erb +134 -0
  35. data/lib/generators/solid_agent/context/templates/create_context.rb.erb +32 -0
  36. data/lib/generators/solid_agent/context/templates/create_generations.rb.erb +46 -0
  37. data/lib/generators/solid_agent/context/templates/create_messages.rb.erb +37 -0
  38. data/lib/generators/solid_agent/context/templates/generation_model.rb.erb +51 -0
  39. data/lib/generators/solid_agent/context/templates/message_model.rb.erb +47 -0
  40. data/lib/generators/solid_agent/install/install_generator.rb +92 -0
  41. data/lib/generators/solid_agent/install/templates/agent_context.rb.erb +171 -0
  42. data/lib/generators/solid_agent/install/templates/agent_generation.rb.erb +76 -0
  43. data/lib/generators/solid_agent/install/templates/agent_memory.rb.erb +51 -0
  44. data/lib/generators/solid_agent/install/templates/agent_memory_entry.rb.erb +12 -0
  45. data/lib/generators/solid_agent/install/templates/agent_message.rb.erb +76 -0
  46. data/lib/generators/solid_agent/install/templates/agent_run.rb.erb +122 -0
  47. data/lib/generators/solid_agent/install/templates/create_agent_contexts.rb.erb +32 -0
  48. data/lib/generators/solid_agent/install/templates/create_agent_generations.rb.erb +51 -0
  49. data/lib/generators/solid_agent/install/templates/create_agent_memories.rb.erb +35 -0
  50. data/lib/generators/solid_agent/install/templates/create_agent_messages.rb.erb +38 -0
  51. data/lib/generators/solid_agent/install/templates/create_agent_runs.rb.erb +46 -0
  52. data/lib/generators/solid_agent/install/templates/initializer.rb.erb +51 -0
  53. data/lib/generators/solid_agent/manifest/manifest_generator.rb +209 -0
  54. data/lib/generators/solid_agent/manifest/templates/agent.md.erb +39 -0
  55. data/lib/generators/solid_agent/manifest/templates/prompt.erb +13 -0
  56. data/lib/generators/solid_agent/reasons/reasons_generator.rb +83 -0
  57. data/lib/generators/solid_agent/reasons/templates/add_reasoning_columns.rb.erb +12 -0
  58. data/lib/generators/solid_agent/tool/templates/tool.json.erb +19 -0
  59. data/lib/generators/solid_agent/tool/tool_generator.rb +117 -0
  60. data/lib/solid_agent/agent_manifest/agent_builder.rb +323 -0
  61. data/lib/solid_agent/agent_manifest/errors.rb +26 -0
  62. data/lib/solid_agent/agent_manifest/exporter_registry.rb +117 -0
  63. data/lib/solid_agent/agent_manifest/exporters/agent_md_exporter.rb +115 -0
  64. data/lib/solid_agent/agent_manifest/exporters/base_exporter.rb +152 -0
  65. data/lib/solid_agent/agent_manifest/exporters/crewai_exporter.rb +125 -0
  66. data/lib/solid_agent/agent_manifest/exporters/dotprompt_exporter.rb +92 -0
  67. data/lib/solid_agent/agent_manifest/input_schema.rb +154 -0
  68. data/lib/solid_agent/agent_manifest/manifest.rb +306 -0
  69. data/lib/solid_agent/agent_manifest/parser_registry.rb +185 -0
  70. data/lib/solid_agent/agent_manifest/parsers/agent_md_parser.rb +87 -0
  71. data/lib/solid_agent/agent_manifest/parsers/base_parser.rb +223 -0
  72. data/lib/solid_agent/agent_manifest/parsers/crewai_parser.rb +201 -0
  73. data/lib/solid_agent/agent_manifest/parsers/dotprompt_parser.rb +122 -0
  74. data/lib/solid_agent/agent_manifest/parsers/github_prompt_parser.rb +143 -0
  75. data/lib/solid_agent/agent_manifest/picoschema.rb +254 -0
  76. data/lib/solid_agent/agent_manifest/registry/auth.rb +103 -0
  77. data/lib/solid_agent/agent_manifest/registry/client.rb +384 -0
  78. data/lib/solid_agent/agent_manifest/resource.rb +103 -0
  79. data/lib/solid_agent/agent_manifest/tool.rb +160 -0
  80. data/lib/solid_agent/agent_manifest/validator.rb +368 -0
  81. data/lib/solid_agent/agent_manifest.rb +381 -0
  82. data/lib/solid_agent/engine.rb +16 -0
  83. data/lib/solid_agent/has_context.rb +670 -0
  84. data/lib/solid_agent/has_memory.rb +136 -0
  85. data/lib/solid_agent/has_reasons.rb +230 -0
  86. data/lib/solid_agent/has_tools.rb +257 -0
  87. data/lib/solid_agent/model_naming.rb +42 -0
  88. data/lib/solid_agent/model_pricing.rb +93 -0
  89. data/lib/solid_agent/reasonable/reason.rb +205 -0
  90. data/lib/solid_agent/reasonable.rb +181 -0
  91. data/lib/solid_agent/records/agent.rb +520 -0
  92. data/lib/solid_agent/records/agent_run.rb +520 -0
  93. data/lib/solid_agent/records/agent_template.rb +142 -0
  94. data/lib/solid_agent/records/agent_version.rb +141 -0
  95. data/lib/solid_agent/records/ownable.rb +130 -0
  96. data/lib/solid_agent/records.rb +152 -0
  97. data/lib/solid_agent/run_fingerprint.rb +51 -0
  98. data/lib/solid_agent/streams_tool_updates.rb +178 -0
  99. data/lib/solid_agent/tool_cache.rb +91 -0
  100. data/lib/solid_agent/version.rb +5 -0
  101. data/lib/solid_agent.rb +95 -0
  102. data/sig/solid_agent.rbs +4 -0
  103. metadata +174 -14
@@ -0,0 +1,124 @@
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 ContextGenerator < Rails::Generators::NamedBase
9
+ include ActiveRecord::Generators::Migration
10
+
11
+ source_root File.expand_path("templates", __dir__)
12
+
13
+ desc "Generates a custom context model with migrations for domain-specific agent contexts"
14
+
15
+ argument :agent_name, type: :string, required: false,
16
+ desc: "The agent class name that will use this context (optional)"
17
+
18
+ class_option :skip_migrations, type: :boolean, default: false,
19
+ desc: "Skip generating migrations"
20
+
21
+ def create_migrations
22
+ return if options[:skip_migrations]
23
+
24
+ migration_template "create_context.rb.erb",
25
+ "db/migrate/create_#{table_name}.rb"
26
+
27
+ migration_template "create_messages.rb.erb",
28
+ "db/migrate/create_#{message_table_name}.rb"
29
+
30
+ migration_template "create_generations.rb.erb",
31
+ "db/migrate/create_#{generation_table_name}.rb"
32
+ end
33
+
34
+ def create_models
35
+ template "context_model.rb.erb", "app/models/#{file_name}.rb"
36
+ template "message_model.rb.erb", "app/models/#{message_file_name}.rb"
37
+ template "generation_model.rb.erb", "app/models/#{generation_file_name}.rb"
38
+ end
39
+
40
+ def show_usage
41
+ say ""
42
+ say "Context models created!", :green
43
+ say ""
44
+ say "Files generated:"
45
+ say " app/models/#{file_name}.rb"
46
+ say " app/models/#{message_file_name}.rb"
47
+ say " app/models/#{generation_file_name}.rb"
48
+ say " db/migrate/*_create_#{table_name}.rb"
49
+ say " db/migrate/*_create_#{message_table_name}.rb"
50
+ say " db/migrate/*_create_#{generation_table_name}.rb"
51
+ say ""
52
+ say "Next steps:", :yellow
53
+ say " 1. Run migrations: rails db:migrate"
54
+ say ""
55
+ say " 2. Add to your agent:"
56
+ say " class #{agent_class_name} < ApplicationAgent"
57
+ say " include SolidAgent::HasContext"
58
+ say " has_context :#{context_name}"
59
+ say ""
60
+ say " def perform"
61
+ say " create_#{context_name}(contextable: params[:user])"
62
+ say " prompt"
63
+ say " end"
64
+ say " end"
65
+ say ""
66
+ end
67
+
68
+ private
69
+
70
+ def context_name
71
+ name.underscore.singularize
72
+ end
73
+
74
+ def class_name
75
+ name.camelize
76
+ end
77
+
78
+ def file_name
79
+ name.underscore
80
+ end
81
+
82
+ def table_name
83
+ name.underscore.pluralize
84
+ end
85
+
86
+ def message_class_name
87
+ "#{class_name}Message"
88
+ end
89
+
90
+ def message_file_name
91
+ "#{file_name}_message"
92
+ end
93
+
94
+ def message_table_name
95
+ "#{file_name}_messages"
96
+ end
97
+
98
+ def generation_class_name
99
+ "#{class_name}Generation"
100
+ end
101
+
102
+ def generation_file_name
103
+ "#{file_name}_generation"
104
+ end
105
+
106
+ def generation_table_name
107
+ "#{file_name}_generations"
108
+ end
109
+
110
+ def agent_class_name
111
+ agent_name&.camelize || "MyAgent"
112
+ end
113
+
114
+ def migration_version
115
+ "[#{ActiveRecord::Migration.current_version}]"
116
+ end
117
+
118
+ def self.next_migration_number(dirname)
119
+ next_migration_number = current_migration_number(dirname) + 1
120
+ ActiveRecord::Migration.next_migration_number(next_migration_number)
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ # <%= class_name %> stores context for <%= context_name %>-based agent interactions.
4
+ #
5
+ # @example Creating a <%= context_name %> for a user
6
+ # <%= context_name %> = <%= class_name %>.create!(
7
+ # contextable: current_user,
8
+ # agent_name: "MyAgent",
9
+ # action_name: "perform"
10
+ # )
11
+ #
12
+ # @example Using with has_context
13
+ # class MyAgent < ApplicationAgent
14
+ # include SolidAgent::HasContext
15
+ # has_context :<%= context_name %>
16
+ #
17
+ # def perform
18
+ # create_<%= context_name %>(contextable: params[:user])
19
+ # prompt
20
+ # end
21
+ # end
22
+ #
23
+ class <%= class_name %> < ApplicationRecord
24
+ # Associations
25
+ belongs_to :contextable, polymorphic: true, optional: true
26
+ has_many :messages, class_name: "<%= message_class_name %>", dependent: :destroy
27
+ has_many :generations, class_name: "<%= generation_class_name %>", dependent: :destroy
28
+
29
+ # Validations
30
+ validates :agent_name, presence: true
31
+ validates :action_name, presence: true
32
+
33
+ # Scopes
34
+ scope :recent, -> { order(created_at: :desc) }
35
+ scope :for_agent, ->(name) { where(agent_name: name) }
36
+ scope :for_action, ->(name) { where(action_name: name) }
37
+
38
+ # Convenience method to get input_params from options
39
+ def input_params
40
+ options&.dig("input_params") || options&.dig(:input_params) || {}
41
+ end
42
+
43
+ # Records a generation response and updates token counts.
44
+ # Response attributes are read defensively — see the install generator's
45
+ # AgentContext template for details.
46
+ def record_generation!(response, extra_attributes = {})
47
+ usage = response.respond_to?(:usage) ? response.usage : nil
48
+
49
+ generation = generations.create!({
50
+ content: response.message&.content,
51
+ model: response_value(response, :model),
52
+ provider: response_value(response, :provider),
53
+ finish_reason: response_value(response, :finish_reason),
54
+ input_tokens: usage&.input_tokens || 0,
55
+ output_tokens: usage&.output_tokens || 0,
56
+ cached_tokens: response_value(usage, :cached_tokens) || 0,
57
+ reasoning_tokens: response_value(usage, :reasoning_tokens) || 0,
58
+ tool_calls: extract_tool_calls(response),
59
+ raw_response: response_value(response, :raw_response),
60
+ duration_seconds: extract_duration_seconds(response, usage)
61
+ }.merge(extra_attributes))
62
+
63
+ increment!(:total_input_tokens, generation.input_tokens)
64
+ increment!(:total_output_tokens, generation.output_tokens)
65
+
66
+ add_assistant_message(response.message&.content, metadata: { "tool_calls" => generation.tool_calls })
67
+
68
+ generation
69
+ end
70
+
71
+ # Records a generation together with its provenance snapshot (called
72
+ # automatically by SolidAgent::HasContext when this method exists).
73
+ def record_generation_with_provenance!(response, provenance)
74
+ provenance = (provenance || {}).deep_stringify_keys
75
+
76
+ record_generation!(
77
+ response,
78
+ trace_id: provenance["trace_id"],
79
+ provenance: provenance
80
+ )
81
+ end
82
+
83
+ def add_user_message(content, **attributes)
84
+ messages.create!(role: "user", content: content, **attributes)
85
+ end
86
+
87
+ def add_assistant_message(content, **attributes)
88
+ messages.create!(role: "assistant", content: content, **attributes)
89
+ end
90
+
91
+ def add_system_message(content)
92
+ messages.create!(role: "system", content: content)
93
+ end
94
+
95
+ def add_tool_message(tool_call_id:, tool_name:, result:)
96
+ messages.create!(
97
+ role: "tool",
98
+ tool_call_id: tool_call_id,
99
+ tool_name: tool_name,
100
+ tool_result: result,
101
+ content: result.is_a?(String) ? result : result.to_json
102
+ )
103
+ end
104
+
105
+ def total_tokens
106
+ total_input_tokens + total_output_tokens
107
+ end
108
+
109
+ private
110
+
111
+ def response_value(response, method)
112
+ response.respond_to?(method) ? response.public_send(method) : nil
113
+ end
114
+
115
+ def extract_duration_seconds(response, usage)
116
+ return response.duration if response.respond_to?(:duration) && response.duration
117
+
118
+ duration_ms = usage.respond_to?(:duration_ms) ? usage.duration_ms : nil
119
+ duration_ms ? duration_ms / 1000.0 : nil
120
+ end
121
+
122
+ def extract_tool_calls(response)
123
+ message = response.message
124
+ return [] unless message.respond_to?(:tool_calls) && message.tool_calls.present?
125
+
126
+ message.tool_calls.map do |tc|
127
+ {
128
+ id: tc.respond_to?(:id) ? tc.id : nil,
129
+ name: tc.respond_to?(:name) ? tc.name : nil,
130
+ arguments: tc.respond_to?(:arguments) ? tc.arguments : nil
131
+ }
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Create<%= class_name.pluralize %> < ActiveRecord::Migration<%= migration_version %>
4
+ def change
5
+ create_table :<%= table_name %> do |t|
6
+ # Polymorphic association to any model (User, Document, Project, etc.)
7
+ t.references :contextable, polymorphic: true, index: true
8
+
9
+ # Agent identification
10
+ t.string :agent_name, null: false
11
+ t.string :action_name, null: false
12
+
13
+ # System instructions for the conversation
14
+ t.text :instructions
15
+
16
+ # Flexible options storage (input_params, model settings, etc.)
17
+ t.jsonb :options, default: {}
18
+
19
+ # Tracing/debugging
20
+ t.string :trace_id, index: true
21
+
22
+ # Token usage tracking
23
+ t.integer :total_input_tokens, default: 0
24
+ t.integer :total_output_tokens, default: 0
25
+
26
+ t.timestamps
27
+ end
28
+
29
+ add_index :<%= table_name %>, [:agent_name, :action_name]
30
+ add_index :<%= table_name %>, :created_at
31
+ end
32
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Create<%= generation_class_name.pluralize %> < ActiveRecord::Migration<%= migration_version %>
4
+ def change
5
+ create_table :<%= generation_table_name %> do |t|
6
+ t.references :<%= file_name %>, null: false, foreign_key: true, index: true
7
+
8
+ # The assistant's response content
9
+ t.text :content
10
+
11
+ # Model information
12
+ t.string :model
13
+ t.string :provider
14
+
15
+ # Finish reason: stop, tool_calls, length, etc.
16
+ t.string :finish_reason
17
+
18
+ # Token usage for this generation
19
+ t.integer :input_tokens, default: 0
20
+ t.integer :output_tokens, default: 0
21
+ # Provider prompt-cache hits and extended-thinking usage
22
+ t.integer :cached_tokens, default: 0
23
+ t.integer :reasoning_tokens, default: 0
24
+
25
+ # Tool calls made in this generation
26
+ t.jsonb :tool_calls, default: []
27
+
28
+ # Raw response from the provider
29
+ t.jsonb :raw_response
30
+
31
+ # Timing
32
+ t.float :duration_seconds
33
+
34
+ # Telemetry correlation + provenance snapshot
35
+ t.string :trace_id
36
+ t.jsonb :provenance, default: {}
37
+
38
+ t.timestamps
39
+ end
40
+
41
+ add_index :<%= generation_table_name %>, :model
42
+ add_index :<%= generation_table_name %>, :finish_reason
43
+ add_index :<%= generation_table_name %>, :trace_id
44
+ add_index :<%= generation_table_name %>, [:<%= file_name %>_id, :created_at]
45
+ end
46
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Create<%= message_class_name.pluralize %> < ActiveRecord::Migration<%= migration_version %>
4
+ def change
5
+ create_table :<%= message_table_name %> do |t|
6
+ t.references :<%= file_name %>, null: false, foreign_key: true, index: true
7
+
8
+ # Message role: user, assistant, system, tool
9
+ t.string :role, null: false
10
+
11
+ # Message content
12
+ t.text :content
13
+
14
+ # For tool calls and results
15
+ t.string :tool_call_id
16
+ t.string :tool_name
17
+ t.jsonb :tool_arguments, default: {}
18
+ t.jsonb :tool_result
19
+
20
+ # For multimodal content
21
+ t.jsonb :attachments, default: []
22
+
23
+ # Metadata
24
+ t.jsonb :metadata, default: {}
25
+
26
+ # Provenance snapshot + content checksum (populated by HasContext)
27
+ t.jsonb :provenance, default: {}
28
+ t.string :content_checksum
29
+
30
+ t.timestamps
31
+ end
32
+
33
+ add_index :<%= message_table_name %>, :role
34
+ add_index :<%= message_table_name %>, :tool_call_id
35
+ add_index :<%= message_table_name %>, [:<%= file_name %>_id, :created_at]
36
+ end
37
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ # <%= generation_class_name %> stores LLM generation results for a <%= context_name %>.
4
+ #
5
+ class <%= generation_class_name %> < ApplicationRecord
6
+ # Associations
7
+ belongs_to :<%= file_name %>
8
+
9
+ # Scopes
10
+ scope :recent, -> { order(created_at: :desc) }
11
+ scope :by_model, ->(model) { where(model: model) }
12
+ scope :with_tool_calls, -> { where.not(tool_calls: []) }
13
+ scope :with_trace, ->(trace_id) { where(trace_id: trace_id) }
14
+ scope :completed, -> { where(finish_reason: "stop") }
15
+
16
+ def total_tokens
17
+ input_tokens + output_tokens
18
+ end
19
+
20
+ # Provider prompt-cache hit on this generation?
21
+ def cache_hit?
22
+ cached_tokens.to_i.positive?
23
+ end
24
+
25
+ # Extended thinking captured?
26
+ def thinking?
27
+ reasoning_tokens.to_i.positive?
28
+ end
29
+
30
+ def has_tool_calls?
31
+ tool_calls.present? && tool_calls.any?
32
+ end
33
+
34
+ def completed?
35
+ finish_reason == "stop"
36
+ end
37
+
38
+ def truncated?
39
+ finish_reason == "length"
40
+ end
41
+
42
+ def ended_with_tool_calls?
43
+ finish_reason == "tool_calls"
44
+ end
45
+
46
+ def estimated_cost(input_price_per_million: 3.0, output_price_per_million: 15.0)
47
+ input_cost = (input_tokens / 1_000_000.0) * input_price_per_million
48
+ output_cost = (output_tokens / 1_000_000.0) * output_price_per_million
49
+ input_cost + output_cost
50
+ end
51
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ # <%= message_class_name %> stores individual messages within a <%= context_name %>.
4
+ #
5
+ class <%= message_class_name %> < ApplicationRecord
6
+ # Associations
7
+ belongs_to :<%= file_name %>
8
+
9
+ # Validations
10
+ validates :role, presence: true, inclusion: { in: %w[user assistant system tool] }
11
+
12
+ # Scopes
13
+ scope :by_role, ->(role) { where(role: role) }
14
+ scope :user_messages, -> { by_role("user") }
15
+ scope :assistant_messages, -> { by_role("assistant") }
16
+ scope :system_messages, -> { by_role("system") }
17
+ scope :tool_messages, -> { by_role("tool") }
18
+ scope :chronological, -> { order(created_at: :asc) }
19
+
20
+ # Converts the message to a hash format for ActiveAgent prompts
21
+ def to_message_hash
22
+ hash = { role: role, content: content }
23
+
24
+ if role == "assistant" && tool_calls_data.present?
25
+ hash[:tool_calls] = tool_calls_data
26
+ end
27
+
28
+ if role == "tool"
29
+ hash[:tool_call_id] = tool_call_id
30
+ hash[:name] = tool_name
31
+ end
32
+
33
+ hash
34
+ end
35
+
36
+ def tool_calls_data
37
+ metadata&.dig("tool_calls") || []
38
+ end
39
+
40
+ def tool_call?
41
+ role == "assistant" && tool_calls_data.present?
42
+ end
43
+
44
+ def tool_result?
45
+ role == "tool"
46
+ end
47
+ end
@@ -0,0 +1,92 @@
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 InstallGenerator < Rails::Generators::Base
9
+ include ActiveRecord::Generators::Migration
10
+
11
+ source_root File.expand_path("templates", __dir__)
12
+
13
+ desc "Installs SolidAgent: creates migrations for context persistence and initializer"
14
+
15
+ class_option :skip_migrations, type: :boolean, default: false,
16
+ desc: "Skip generating migrations"
17
+
18
+ class_option :skip_initializer, type: :boolean, default: false,
19
+ desc: "Skip generating initializer"
20
+
21
+ def create_migrations
22
+ return if options[:skip_migrations]
23
+
24
+ migration_template "create_agent_contexts.rb.erb",
25
+ "db/migrate/create_agent_contexts.rb"
26
+
27
+ migration_template "create_agent_messages.rb.erb",
28
+ "db/migrate/create_agent_messages.rb"
29
+
30
+ migration_template "create_agent_generations.rb.erb",
31
+ "db/migrate/create_agent_generations.rb"
32
+
33
+ migration_template "create_agent_memories.rb.erb",
34
+ "db/migrate/create_agent_memories.rb"
35
+
36
+ migration_template "create_agent_runs.rb.erb",
37
+ "db/migrate/create_agent_runs.rb"
38
+ end
39
+
40
+ def create_models
41
+ return if options[:skip_migrations]
42
+
43
+ template "agent_context.rb.erb", "app/models/agent_context.rb"
44
+ template "agent_message.rb.erb", "app/models/agent_message.rb"
45
+ template "agent_generation.rb.erb", "app/models/agent_generation.rb"
46
+ template "agent_memory.rb.erb", "app/models/agent_memory.rb"
47
+ template "agent_memory_entry.rb.erb", "app/models/agent_memory_entry.rb"
48
+ template "agent_run.rb.erb", "app/models/agent_run.rb"
49
+ end
50
+
51
+ def create_initializer
52
+ return if options[:skip_initializer]
53
+
54
+ template "initializer.rb.erb", "config/initializers/solid_agent.rb"
55
+ end
56
+
57
+ def show_post_install_message
58
+ say ""
59
+ say "SolidAgent installed successfully!", :green
60
+ say ""
61
+ say "Next steps:"
62
+ say " 1. Run migrations: rails db:migrate"
63
+ say " 2. Include SolidAgent::HasContext in your ApplicationAgent"
64
+ say " 3. Use has_context in agents that need persistence"
65
+ say ""
66
+ say "Example:"
67
+ say " class WritingAssistantAgent < ApplicationAgent"
68
+ say " include SolidAgent::HasContext"
69
+ say " has_context"
70
+ say ""
71
+ say " def improve"
72
+ say " create_context(contextable: params[:document])"
73
+ say " prompt"
74
+ say " end"
75
+ say " end"
76
+ say ""
77
+ end
78
+
79
+ private
80
+
81
+ def migration_version
82
+ "[#{ActiveRecord::Migration.current_version}]"
83
+ end
84
+
85
+ # Ensure unique timestamps for multiple migrations
86
+ def self.next_migration_number(dirname)
87
+ next_migration_number = current_migration_number(dirname) + 1
88
+ ActiveRecord::Migration.next_migration_number(next_migration_number)
89
+ end
90
+ end
91
+ end
92
+ end