a2a-ruby 1.0.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 +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +137 -0
- data/.simplecov +46 -0
- data/.yardopts +10 -0
- data/CHANGELOG.md +33 -0
- data/CODE_OF_CONDUCT.md +128 -0
- data/CONTRIBUTING.md +165 -0
- data/Gemfile +43 -0
- data/Guardfile +34 -0
- data/LICENSE.txt +21 -0
- data/PUBLISHING_CHECKLIST.md +214 -0
- data/README.md +171 -0
- data/Rakefile +165 -0
- data/docs/agent_execution.md +309 -0
- data/docs/api_reference.md +792 -0
- data/docs/configuration.md +780 -0
- data/docs/events.md +475 -0
- data/docs/getting_started.md +668 -0
- data/docs/integration.md +262 -0
- data/docs/server_apps.md +621 -0
- data/docs/troubleshooting.md +765 -0
- data/lib/a2a/client/api_methods.rb +263 -0
- data/lib/a2a/client/auth/api_key.rb +161 -0
- data/lib/a2a/client/auth/interceptor.rb +288 -0
- data/lib/a2a/client/auth/jwt.rb +189 -0
- data/lib/a2a/client/auth/oauth2.rb +146 -0
- data/lib/a2a/client/auth.rb +137 -0
- data/lib/a2a/client/base.rb +316 -0
- data/lib/a2a/client/config.rb +210 -0
- data/lib/a2a/client/connection_pool.rb +233 -0
- data/lib/a2a/client/http_client.rb +524 -0
- data/lib/a2a/client/json_rpc_handler.rb +136 -0
- data/lib/a2a/client/middleware/circuit_breaker_interceptor.rb +245 -0
- data/lib/a2a/client/middleware/logging_interceptor.rb +371 -0
- data/lib/a2a/client/middleware/rate_limit_interceptor.rb +142 -0
- data/lib/a2a/client/middleware/retry_interceptor.rb +161 -0
- data/lib/a2a/client/middleware.rb +116 -0
- data/lib/a2a/client/performance_tracker.rb +60 -0
- data/lib/a2a/configuration/defaults.rb +34 -0
- data/lib/a2a/configuration/environment_loader.rb +76 -0
- data/lib/a2a/configuration/file_loader.rb +115 -0
- data/lib/a2a/configuration/inheritance.rb +101 -0
- data/lib/a2a/configuration/validator.rb +180 -0
- data/lib/a2a/configuration.rb +201 -0
- data/lib/a2a/errors.rb +291 -0
- data/lib/a2a/modules.rb +50 -0
- data/lib/a2a/monitoring/alerting.rb +490 -0
- data/lib/a2a/monitoring/distributed_tracing.rb +398 -0
- data/lib/a2a/monitoring/health_endpoints.rb +204 -0
- data/lib/a2a/monitoring/metrics_collector.rb +438 -0
- data/lib/a2a/monitoring.rb +463 -0
- data/lib/a2a/plugin.rb +358 -0
- data/lib/a2a/plugin_manager.rb +159 -0
- data/lib/a2a/plugins/example_auth.rb +81 -0
- data/lib/a2a/plugins/example_middleware.rb +118 -0
- data/lib/a2a/plugins/example_transport.rb +76 -0
- data/lib/a2a/protocol/agent_card.rb +8 -0
- data/lib/a2a/protocol/agent_card_server.rb +584 -0
- data/lib/a2a/protocol/capability.rb +496 -0
- data/lib/a2a/protocol/json_rpc.rb +254 -0
- data/lib/a2a/protocol/message.rb +8 -0
- data/lib/a2a/protocol/task.rb +8 -0
- data/lib/a2a/rails/a2a_controller.rb +258 -0
- data/lib/a2a/rails/controller_helpers.rb +499 -0
- data/lib/a2a/rails/engine.rb +167 -0
- data/lib/a2a/rails/generators/agent_generator.rb +311 -0
- data/lib/a2a/rails/generators/install_generator.rb +209 -0
- data/lib/a2a/rails/generators/migration_generator.rb +232 -0
- data/lib/a2a/rails/generators/templates/add_a2a_indexes.rb +57 -0
- data/lib/a2a/rails/generators/templates/agent_controller.rb +122 -0
- data/lib/a2a/rails/generators/templates/agent_controller_spec.rb +160 -0
- data/lib/a2a/rails/generators/templates/agent_readme.md +200 -0
- data/lib/a2a/rails/generators/templates/create_a2a_push_notification_configs.rb +68 -0
- data/lib/a2a/rails/generators/templates/create_a2a_tasks.rb +83 -0
- data/lib/a2a/rails/generators/templates/example_agent_controller.rb +228 -0
- data/lib/a2a/rails/generators/templates/initializer.rb +108 -0
- data/lib/a2a/rails/generators/templates/push_notification_config_model.rb +228 -0
- data/lib/a2a/rails/generators/templates/task_model.rb +200 -0
- data/lib/a2a/rails/tasks/a2a.rake +228 -0
- data/lib/a2a/server/a2a_methods.rb +520 -0
- data/lib/a2a/server/agent.rb +537 -0
- data/lib/a2a/server/agent_execution/agent_executor.rb +279 -0
- data/lib/a2a/server/agent_execution/request_context.rb +219 -0
- data/lib/a2a/server/apps/rack_app.rb +311 -0
- data/lib/a2a/server/apps/sinatra_app.rb +261 -0
- data/lib/a2a/server/default_request_handler.rb +350 -0
- data/lib/a2a/server/events/event_consumer.rb +116 -0
- data/lib/a2a/server/events/event_queue.rb +226 -0
- data/lib/a2a/server/example_agent.rb +248 -0
- data/lib/a2a/server/handler.rb +281 -0
- data/lib/a2a/server/middleware/authentication_middleware.rb +212 -0
- data/lib/a2a/server/middleware/cors_middleware.rb +171 -0
- data/lib/a2a/server/middleware/logging_middleware.rb +362 -0
- data/lib/a2a/server/middleware/rate_limit_middleware.rb +382 -0
- data/lib/a2a/server/middleware.rb +213 -0
- data/lib/a2a/server/push_notification_manager.rb +327 -0
- data/lib/a2a/server/request_handler.rb +136 -0
- data/lib/a2a/server/storage/base.rb +141 -0
- data/lib/a2a/server/storage/database.rb +266 -0
- data/lib/a2a/server/storage/memory.rb +274 -0
- data/lib/a2a/server/storage/redis.rb +320 -0
- data/lib/a2a/server/storage.rb +38 -0
- data/lib/a2a/server/task_manager.rb +534 -0
- data/lib/a2a/transport/grpc.rb +481 -0
- data/lib/a2a/transport/http.rb +415 -0
- data/lib/a2a/transport/sse.rb +499 -0
- data/lib/a2a/types/agent_card.rb +540 -0
- data/lib/a2a/types/artifact.rb +99 -0
- data/lib/a2a/types/base_model.rb +223 -0
- data/lib/a2a/types/events.rb +117 -0
- data/lib/a2a/types/message.rb +106 -0
- data/lib/a2a/types/part.rb +288 -0
- data/lib/a2a/types/push_notification.rb +139 -0
- data/lib/a2a/types/security.rb +167 -0
- data/lib/a2a/types/task.rb +154 -0
- data/lib/a2a/types.rb +88 -0
- data/lib/a2a/utils/helpers.rb +245 -0
- data/lib/a2a/utils/message_buffer.rb +278 -0
- data/lib/a2a/utils/performance.rb +247 -0
- data/lib/a2a/utils/rails_detection.rb +97 -0
- data/lib/a2a/utils/structured_logger.rb +306 -0
- data/lib/a2a/utils/time_helpers.rb +167 -0
- data/lib/a2a/utils/validation.rb +8 -0
- data/lib/a2a/version.rb +6 -0
- data/lib/a2a-rails.rb +58 -0
- data/lib/a2a.rb +198 -0
- metadata +437 -0
@@ -0,0 +1,232 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails/generators"
|
4
|
+
require "rails/generators/active_record"
|
5
|
+
|
6
|
+
##
|
7
|
+
# Rails generator for creating A2A database migrations
|
8
|
+
#
|
9
|
+
# This generator creates database tables needed for A2A task storage,
|
10
|
+
# push notification configurations, and other persistent data.
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# rails generate a2a:migration
|
14
|
+
# rails generate a2a:migration --skip-tasks
|
15
|
+
# rails generate a2a:migration --with-indexes
|
16
|
+
#
|
17
|
+
module A2A
|
18
|
+
module Rails
|
19
|
+
module Generators
|
20
|
+
class MigrationGenerator < ActiveRecord::Generators::Base
|
21
|
+
source_root File.expand_path("templates", __dir__)
|
22
|
+
|
23
|
+
class_option :skip_tasks, type: :boolean, default: false,
|
24
|
+
desc: "Skip creating tasks table"
|
25
|
+
|
26
|
+
class_option :skip_push_notifications, type: :boolean, default: false,
|
27
|
+
desc: "Skip creating push notification config table"
|
28
|
+
|
29
|
+
class_option :with_indexes, type: :boolean, default: true,
|
30
|
+
desc: "Add database indexes for performance"
|
31
|
+
|
32
|
+
class_option :table_prefix, type: :string, default: "a2a_",
|
33
|
+
desc: "Prefix for A2A table names"
|
34
|
+
|
35
|
+
desc "Generate A2A database migrations"
|
36
|
+
|
37
|
+
def create_tasks_migration
|
38
|
+
return if options[:skip_tasks]
|
39
|
+
|
40
|
+
migration_template "create_a2a_tasks.rb",
|
41
|
+
"db/migrate/create_#{table_prefix}tasks.rb",
|
42
|
+
migration_version: migration_version
|
43
|
+
|
44
|
+
say "Created tasks migration", :green
|
45
|
+
end
|
46
|
+
|
47
|
+
def create_push_notifications_migration
|
48
|
+
return if options[:skip_push_notifications]
|
49
|
+
|
50
|
+
migration_template "create_a2a_push_notification_configs.rb",
|
51
|
+
"db/migrate/create_#{table_prefix}push_notification_configs.rb",
|
52
|
+
migration_version: migration_version
|
53
|
+
|
54
|
+
say "Created push notification configs migration", :green
|
55
|
+
end
|
56
|
+
|
57
|
+
def create_indexes_migration
|
58
|
+
return unless options[:with_indexes]
|
59
|
+
|
60
|
+
migration_template "add_a2a_indexes.rb",
|
61
|
+
"db/migrate/add_#{table_prefix}indexes.rb",
|
62
|
+
migration_version: migration_version
|
63
|
+
|
64
|
+
say "Created indexes migration", :green
|
65
|
+
end
|
66
|
+
|
67
|
+
def create_models
|
68
|
+
template "task_model.rb", "app/models/#{table_prefix}task.rb"
|
69
|
+
template "push_notification_config_model.rb",
|
70
|
+
"app/models/#{table_prefix}push_notification_config.rb"
|
71
|
+
|
72
|
+
say "Created A2A models", :green
|
73
|
+
end
|
74
|
+
|
75
|
+
def show_post_generation_instructions
|
76
|
+
say "\n#{'=' * 60}", :green
|
77
|
+
say "A2A database migrations generated successfully!", :green
|
78
|
+
say "=" * 60, :green
|
79
|
+
|
80
|
+
say "\nNext steps:", :yellow
|
81
|
+
say "1. Review the generated migrations in db/migrate/"
|
82
|
+
say "2. Run 'rails db:migrate' to create the tables"
|
83
|
+
say "3. Customize the models in app/models/ if needed"
|
84
|
+
|
85
|
+
say "\nGenerated tables:", :yellow
|
86
|
+
say " #{table_prefix}tasks - Stores A2A task data"
|
87
|
+
say " #{table_prefix}push_notification_configs - Stores push notification settings"
|
88
|
+
|
89
|
+
if options[:with_indexes]
|
90
|
+
say "\nPerformance indexes will be created for:"
|
91
|
+
say " - Task lookups by ID and context_id"
|
92
|
+
say " - Task status filtering"
|
93
|
+
say " - Push notification config lookups"
|
94
|
+
end
|
95
|
+
|
96
|
+
say "\nConfiguration:", :yellow
|
97
|
+
say "Update config/initializers/a2a.rb to use database storage:"
|
98
|
+
say " config.task_storage = :database"
|
99
|
+
|
100
|
+
say "\nFor more information, visit: https://a2a-protocol.org/sdk/ruby/storage/", :blue
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
def migration_version
|
106
|
+
if ::Rails.version >= "5.0"
|
107
|
+
"[#{::Rails::VERSION::MAJOR}.#{::Rails::VERSION::MINOR}]"
|
108
|
+
else
|
109
|
+
""
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def table_prefix
|
114
|
+
options[:table_prefix]
|
115
|
+
end
|
116
|
+
|
117
|
+
def tasks_table_name
|
118
|
+
"#{table_prefix}tasks"
|
119
|
+
end
|
120
|
+
|
121
|
+
def push_notification_configs_table_name
|
122
|
+
"#{table_prefix}push_notification_configs"
|
123
|
+
end
|
124
|
+
|
125
|
+
def with_indexes?
|
126
|
+
options[:with_indexes]
|
127
|
+
end
|
128
|
+
|
129
|
+
def model_class_name(base_name)
|
130
|
+
"#{table_prefix.classify}#{base_name.classify}"
|
131
|
+
end
|
132
|
+
|
133
|
+
def model_file_name(base_name)
|
134
|
+
"#{table_prefix}#{base_name.underscore}"
|
135
|
+
end
|
136
|
+
|
137
|
+
# Helper methods for templates
|
138
|
+
def json_column_type
|
139
|
+
# Use jsonb for PostgreSQL, json for others
|
140
|
+
if postgresql?
|
141
|
+
"jsonb"
|
142
|
+
else
|
143
|
+
"json"
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def text_column_type
|
148
|
+
# Use text for larger content
|
149
|
+
"text"
|
150
|
+
end
|
151
|
+
|
152
|
+
def uuid_column_type
|
153
|
+
if postgresql?
|
154
|
+
"uuid"
|
155
|
+
else
|
156
|
+
"string"
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def postgresql?
|
161
|
+
# Try to detect PostgreSQL adapter
|
162
|
+
return false unless defined?(ActiveRecord::Base)
|
163
|
+
|
164
|
+
begin
|
165
|
+
ActiveRecord::Base.connection.adapter_name.downcase.include?("postgresql")
|
166
|
+
rescue StandardError
|
167
|
+
false
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def mysql?
|
172
|
+
return false unless defined?(ActiveRecord::Base)
|
173
|
+
|
174
|
+
begin
|
175
|
+
adapter_name = ActiveRecord::Base.connection.adapter_name.downcase
|
176
|
+
adapter_name.include?("mysql") || adapter_name.include?("trilogy")
|
177
|
+
rescue StandardError
|
178
|
+
false
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def sqlite?
|
183
|
+
return false unless defined?(ActiveRecord::Base)
|
184
|
+
|
185
|
+
begin
|
186
|
+
ActiveRecord::Base.connection.adapter_name.downcase.include?("sqlite")
|
187
|
+
rescue StandardError
|
188
|
+
false
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
# Generate appropriate column definitions based on database adapter
|
193
|
+
def id_column_definition
|
194
|
+
if postgresql?
|
195
|
+
"t.uuid :id, primary_key: true, default: 'gen_random_uuid()'"
|
196
|
+
else
|
197
|
+
"t.string :id, primary_key: true, limit: 36"
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def uuid_column_definition(name, **options)
|
202
|
+
if postgresql?
|
203
|
+
"t.uuid :#{name}#{format_column_options(options)}"
|
204
|
+
else
|
205
|
+
"t.string :#{name}, limit: 36#{format_column_options(options)}"
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def json_column_definition(name, **options)
|
210
|
+
"t.#{json_column_type} :#{name}#{format_column_options(options)}"
|
211
|
+
end
|
212
|
+
|
213
|
+
def format_column_options(options)
|
214
|
+
return "" if options.empty?
|
215
|
+
|
216
|
+
formatted_options = options.map do |key, value|
|
217
|
+
case value
|
218
|
+
when String
|
219
|
+
"#{key}: '#{value}'"
|
220
|
+
when Symbol
|
221
|
+
"#{key}: :#{value}"
|
222
|
+
else
|
223
|
+
"#{key}: #{value}"
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
", #{formatted_options.join(', ')}"
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Add<%= table_prefix.classify %>Indexes < ActiveRecord::Migration<%= migration_version %>
|
4
|
+
def change
|
5
|
+
# Composite indexes for common query patterns
|
6
|
+
|
7
|
+
# Tasks table indexes
|
8
|
+
add_index :<%= tasks_table_name %>, [:context_id, :status_state],
|
9
|
+
name: "index_<%= tasks_table_name %>_on_context_and_status"
|
10
|
+
|
11
|
+
add_index :<%= tasks_table_name %>, [:status_state, :created_at],
|
12
|
+
name: "index_<%= tasks_table_name %>_on_status_and_created"
|
13
|
+
|
14
|
+
add_index :<%= tasks_table_name %>, [:type, :status_state],
|
15
|
+
name: "index_<%= tasks_table_name %>_on_type_and_status"
|
16
|
+
|
17
|
+
# Index for soft delete queries
|
18
|
+
add_index :<%= tasks_table_name %>, [:deleted_at, :status_state],
|
19
|
+
name: "index_<%= tasks_table_name %>_on_deleted_and_status"
|
20
|
+
|
21
|
+
<% if postgresql? %>
|
22
|
+
# PostgreSQL-specific partial indexes for better performance
|
23
|
+
add_index :<%= tasks_table_name %>, :id,
|
24
|
+
where: "deleted_at IS NULL",
|
25
|
+
name: "index_<%= tasks_table_name %>_active_tasks"
|
26
|
+
|
27
|
+
add_index :<%= tasks_table_name %>, :status_state,
|
28
|
+
where: "deleted_at IS NULL AND status_state IN ('submitted', 'working')",
|
29
|
+
name: "index_<%= tasks_table_name %>_active_processing"
|
30
|
+
|
31
|
+
# JSON path indexes for common metadata queries
|
32
|
+
add_index :<%= tasks_table_name %>, "(metadata->>'priority')",
|
33
|
+
name: "index_<%= tasks_table_name %>_on_priority"
|
34
|
+
|
35
|
+
add_index :<%= tasks_table_name %>, "(metadata->>'source')",
|
36
|
+
name: "index_<%= tasks_table_name %>_on_source"
|
37
|
+
<% end %>
|
38
|
+
|
39
|
+
# Push notification configs indexes
|
40
|
+
add_index :<%= push_notification_configs_table_name %>, [:task_id, :active],
|
41
|
+
name: "index_<%= push_notification_configs_table_name %>_on_task_and_active"
|
42
|
+
|
43
|
+
add_index :<%= push_notification_configs_table_name %>, [:active, :last_success_at],
|
44
|
+
name: "index_<%= push_notification_configs_table_name %>_on_active_and_success"
|
45
|
+
|
46
|
+
# Index for retry logic
|
47
|
+
add_index :<%= push_notification_configs_table_name %>, [:retry_count, :last_failure_at],
|
48
|
+
name: "index_<%= push_notification_configs_table_name %>_on_retry"
|
49
|
+
|
50
|
+
<% if postgresql? %>
|
51
|
+
# PostgreSQL partial index for active configs only
|
52
|
+
add_index :<%= push_notification_configs_table_name %>, :task_id,
|
53
|
+
where: "active = true AND deleted_at IS NULL",
|
54
|
+
name: "index_<%= push_notification_configs_table_name %>_active_only"
|
55
|
+
<% end %>
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
##
|
4
|
+
# <%= controller_class_name %>
|
5
|
+
#
|
6
|
+
# A2A Agent controller for <%= class_name.humanize.downcase %> functionality.
|
7
|
+
# This controller provides A2A protocol endpoints for agent-to-agent communication.
|
8
|
+
#
|
9
|
+
class <%= controller_class_name %> < <%= controller_parent_class %>
|
10
|
+
include A2A::Rails::ControllerHelpers
|
11
|
+
|
12
|
+
# Configure this agent
|
13
|
+
a2a_agent name: "<%= class_name.humanize %> Agent",
|
14
|
+
description: "<%= agent_description %>",
|
15
|
+
version: "1.0.0",
|
16
|
+
tags: <%= agent_tags.inspect %>
|
17
|
+
|
18
|
+
<% if with_authentication? %>
|
19
|
+
# Configure authentication for specific methods
|
20
|
+
<%= authentication_config %>
|
21
|
+
<% end %>
|
22
|
+
|
23
|
+
<% if skills.any? %>
|
24
|
+
# Define agent skills
|
25
|
+
<%= generate_skill_definitions %>
|
26
|
+
<% else %>
|
27
|
+
# Define agent skills
|
28
|
+
a2a_skill "default" do |skill|
|
29
|
+
skill.description = "<%= class_name.humanize %> default functionality"
|
30
|
+
skill.tags = ["<%= class_name.underscore %>", "default"]
|
31
|
+
skill.examples = [
|
32
|
+
{
|
33
|
+
input: { action: "process" },
|
34
|
+
output: { result: "processed" }
|
35
|
+
}
|
36
|
+
]
|
37
|
+
end
|
38
|
+
<% end %>
|
39
|
+
|
40
|
+
# A2A method implementations
|
41
|
+
|
42
|
+
<% if skills.any? %>
|
43
|
+
<%= generate_skill_methods %>
|
44
|
+
<% else %>
|
45
|
+
##
|
46
|
+
# Default processing method
|
47
|
+
#
|
48
|
+
# @param params [Hash] Request parameters
|
49
|
+
# @return [Hash] Processing result
|
50
|
+
#
|
51
|
+
a2a_method "process" do |params|
|
52
|
+
# TODO: Implement your <%= class_name.humanize.downcase %> logic here
|
53
|
+
{
|
54
|
+
agent: "<%= class_name %>",
|
55
|
+
action: "process",
|
56
|
+
params: params,
|
57
|
+
result: "Processing completed",
|
58
|
+
timestamp: Time.now.iso8601
|
59
|
+
}
|
60
|
+
end
|
61
|
+
<% end %>
|
62
|
+
|
63
|
+
##
|
64
|
+
# Get agent status
|
65
|
+
#
|
66
|
+
# @param params [Hash] Request parameters (unused)
|
67
|
+
# @return [Hash] Status information
|
68
|
+
#
|
69
|
+
a2a_method "status" do |params|
|
70
|
+
{
|
71
|
+
agent: "<%= class_name %>",
|
72
|
+
status: "active",
|
73
|
+
version: "1.0.0",
|
74
|
+
capabilities: self.class._a2a_capabilities&.map(&:name) || [],
|
75
|
+
methods: self.class._a2a_methods&.keys || [],
|
76
|
+
timestamp: Time.now.iso8601
|
77
|
+
}
|
78
|
+
end
|
79
|
+
|
80
|
+
# Standard Rails actions
|
81
|
+
|
82
|
+
##
|
83
|
+
# Serve the agent card
|
84
|
+
#
|
85
|
+
def agent_card
|
86
|
+
render_agent_card
|
87
|
+
end
|
88
|
+
|
89
|
+
##
|
90
|
+
# Handle JSON-RPC requests
|
91
|
+
#
|
92
|
+
def rpc
|
93
|
+
handle_a2a_rpc
|
94
|
+
end
|
95
|
+
|
96
|
+
<% if with_authentication? %>
|
97
|
+
private
|
98
|
+
|
99
|
+
# Override authentication methods as needed
|
100
|
+
def current_user_info
|
101
|
+
if respond_to?(:current_user) && current_user.present?
|
102
|
+
{
|
103
|
+
id: current_user.id,
|
104
|
+
email: current_user.email,
|
105
|
+
name: current_user.name || current_user.email
|
106
|
+
}
|
107
|
+
else
|
108
|
+
{ id: "anonymous", name: "Anonymous User" }
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def current_user_permissions
|
113
|
+
# Customize based on your authorization system
|
114
|
+
if respond_to?(:current_user) && current_user.present?
|
115
|
+
# Example: return user roles or permissions
|
116
|
+
current_user.roles&.map(&:name) || ["user"]
|
117
|
+
else
|
118
|
+
["guest"]
|
119
|
+
end
|
120
|
+
end
|
121
|
+
<% end %>
|
122
|
+
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails_helper'
|
4
|
+
|
5
|
+
RSpec.describe <%= controller_class_name %>, type: :controller do
|
6
|
+
describe "A2A Agent functionality" do
|
7
|
+
describe "GET #agent_card" do
|
8
|
+
it "returns the agent card" do
|
9
|
+
get :agent_card
|
10
|
+
|
11
|
+
expect(response).to have_http_status(:ok)
|
12
|
+
expect(response.content_type).to eq("application/json; charset=utf-8")
|
13
|
+
|
14
|
+
card = JSON.parse(response.body)
|
15
|
+
expect(card).to include(
|
16
|
+
"name" => "<%= class_name.humanize %> Agent",
|
17
|
+
"description" => "<%= agent_description %>",
|
18
|
+
"version" => "1.0.0"
|
19
|
+
)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "POST #rpc" do
|
24
|
+
let(:valid_rpc_request) do
|
25
|
+
{
|
26
|
+
jsonrpc: "2.0",
|
27
|
+
method: "status",
|
28
|
+
params: {},
|
29
|
+
id: 1
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
it "handles valid JSON-RPC requests" do
|
34
|
+
post :rpc, body: valid_rpc_request.to_json, as: :json
|
35
|
+
|
36
|
+
expect(response).to have_http_status(:ok)
|
37
|
+
|
38
|
+
rpc_response = JSON.parse(response.body)
|
39
|
+
expect(rpc_response).to include(
|
40
|
+
"jsonrpc" => "2.0",
|
41
|
+
"id" => 1
|
42
|
+
)
|
43
|
+
expect(rpc_response["result"]).to be_present
|
44
|
+
end
|
45
|
+
|
46
|
+
it "returns error for invalid JSON-RPC" do
|
47
|
+
post :rpc, body: "invalid json", as: :json
|
48
|
+
|
49
|
+
expect(response).to have_http_status(:bad_request)
|
50
|
+
|
51
|
+
error_response = JSON.parse(response.body)
|
52
|
+
expect(error_response["error"]).to be_present
|
53
|
+
end
|
54
|
+
|
55
|
+
<% if skills.any? %>
|
56
|
+
<% skills.each do |skill| %>
|
57
|
+
describe "<%= skill %> method" do
|
58
|
+
let(:<%= skill %>_request) do
|
59
|
+
{
|
60
|
+
jsonrpc: "2.0",
|
61
|
+
method: "<%= skill.underscore %>",
|
62
|
+
params: { test: "data" },
|
63
|
+
id: 1
|
64
|
+
}
|
65
|
+
end
|
66
|
+
|
67
|
+
it "handles <%= skill %> requests" do
|
68
|
+
post :rpc, body: <%= skill %>_request.to_json, as: :json
|
69
|
+
|
70
|
+
expect(response).to have_http_status(:ok)
|
71
|
+
|
72
|
+
rpc_response = JSON.parse(response.body)
|
73
|
+
expect(rpc_response["result"]).to include("skill" => "<%= skill %>")
|
74
|
+
end
|
75
|
+
end
|
76
|
+
<% end %>
|
77
|
+
<% end %>
|
78
|
+
|
79
|
+
describe "status method" do
|
80
|
+
let(:status_request) do
|
81
|
+
{
|
82
|
+
jsonrpc: "2.0",
|
83
|
+
method: "status",
|
84
|
+
params: {},
|
85
|
+
id: 1
|
86
|
+
}
|
87
|
+
end
|
88
|
+
|
89
|
+
it "returns agent status information" do
|
90
|
+
post :rpc, body: status_request.to_json, as: :json
|
91
|
+
|
92
|
+
expect(response).to have_http_status(:ok)
|
93
|
+
|
94
|
+
rpc_response = JSON.parse(response.body)
|
95
|
+
status_result = rpc_response["result"]
|
96
|
+
|
97
|
+
expect(status_result).to include(
|
98
|
+
"agent" => "<%= class_name %>",
|
99
|
+
"status" => "active",
|
100
|
+
"version" => "1.0.0"
|
101
|
+
)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
<% if with_authentication? %>
|
107
|
+
describe "authentication" do
|
108
|
+
context "when authentication is required" do
|
109
|
+
<% authentication_methods.each do |method| %>
|
110
|
+
describe "<%= method %> method" do
|
111
|
+
let(:auth_request) do
|
112
|
+
{
|
113
|
+
jsonrpc: "2.0",
|
114
|
+
method: "<%= method %>",
|
115
|
+
params: {},
|
116
|
+
id: 1
|
117
|
+
}
|
118
|
+
end
|
119
|
+
|
120
|
+
it "requires authentication" do
|
121
|
+
post :rpc, body: auth_request.to_json, as: :json
|
122
|
+
|
123
|
+
expect(response).to have_http_status(:unauthorized)
|
124
|
+
end
|
125
|
+
|
126
|
+
it "allows authenticated requests" do
|
127
|
+
# Mock authentication - customize based on your auth system
|
128
|
+
allow(controller).to receive(:current_user_authenticated?).and_return(true)
|
129
|
+
allow(controller).to receive(:current_user_info).and_return({ id: 1, name: "Test User" })
|
130
|
+
|
131
|
+
post :rpc, body: auth_request.to_json, as: :json
|
132
|
+
|
133
|
+
expect(response).to have_http_status(:ok)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
<% end %>
|
137
|
+
end
|
138
|
+
end
|
139
|
+
<% end %>
|
140
|
+
end
|
141
|
+
|
142
|
+
describe "A2A protocol compliance" do
|
143
|
+
it "includes A2A::Rails::ControllerHelpers" do
|
144
|
+
expect(controller.class.included_modules).to include(A2A::Rails::ControllerHelpers)
|
145
|
+
end
|
146
|
+
|
147
|
+
it "has A2A capabilities defined" do
|
148
|
+
capabilities = controller.class._a2a_capabilities
|
149
|
+
expect(capabilities).to be_present
|
150
|
+
expect(capabilities).to be_an(Array)
|
151
|
+
end
|
152
|
+
|
153
|
+
it "has A2A methods defined" do
|
154
|
+
methods = controller.class._a2a_methods
|
155
|
+
expect(methods).to be_present
|
156
|
+
expect(methods).to be_a(Hash)
|
157
|
+
expect(methods).to include("status")
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|