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.
Files changed (128) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +137 -0
  4. data/.simplecov +46 -0
  5. data/.yardopts +10 -0
  6. data/CHANGELOG.md +33 -0
  7. data/CODE_OF_CONDUCT.md +128 -0
  8. data/CONTRIBUTING.md +165 -0
  9. data/Gemfile +43 -0
  10. data/Guardfile +34 -0
  11. data/LICENSE.txt +21 -0
  12. data/PUBLISHING_CHECKLIST.md +214 -0
  13. data/README.md +171 -0
  14. data/Rakefile +165 -0
  15. data/docs/agent_execution.md +309 -0
  16. data/docs/api_reference.md +792 -0
  17. data/docs/configuration.md +780 -0
  18. data/docs/events.md +475 -0
  19. data/docs/getting_started.md +668 -0
  20. data/docs/integration.md +262 -0
  21. data/docs/server_apps.md +621 -0
  22. data/docs/troubleshooting.md +765 -0
  23. data/lib/a2a/client/api_methods.rb +263 -0
  24. data/lib/a2a/client/auth/api_key.rb +161 -0
  25. data/lib/a2a/client/auth/interceptor.rb +288 -0
  26. data/lib/a2a/client/auth/jwt.rb +189 -0
  27. data/lib/a2a/client/auth/oauth2.rb +146 -0
  28. data/lib/a2a/client/auth.rb +137 -0
  29. data/lib/a2a/client/base.rb +316 -0
  30. data/lib/a2a/client/config.rb +210 -0
  31. data/lib/a2a/client/connection_pool.rb +233 -0
  32. data/lib/a2a/client/http_client.rb +524 -0
  33. data/lib/a2a/client/json_rpc_handler.rb +136 -0
  34. data/lib/a2a/client/middleware/circuit_breaker_interceptor.rb +245 -0
  35. data/lib/a2a/client/middleware/logging_interceptor.rb +371 -0
  36. data/lib/a2a/client/middleware/rate_limit_interceptor.rb +142 -0
  37. data/lib/a2a/client/middleware/retry_interceptor.rb +161 -0
  38. data/lib/a2a/client/middleware.rb +116 -0
  39. data/lib/a2a/client/performance_tracker.rb +60 -0
  40. data/lib/a2a/configuration/defaults.rb +34 -0
  41. data/lib/a2a/configuration/environment_loader.rb +76 -0
  42. data/lib/a2a/configuration/file_loader.rb +115 -0
  43. data/lib/a2a/configuration/inheritance.rb +101 -0
  44. data/lib/a2a/configuration/validator.rb +180 -0
  45. data/lib/a2a/configuration.rb +201 -0
  46. data/lib/a2a/errors.rb +291 -0
  47. data/lib/a2a/modules.rb +50 -0
  48. data/lib/a2a/monitoring/alerting.rb +490 -0
  49. data/lib/a2a/monitoring/distributed_tracing.rb +398 -0
  50. data/lib/a2a/monitoring/health_endpoints.rb +204 -0
  51. data/lib/a2a/monitoring/metrics_collector.rb +438 -0
  52. data/lib/a2a/monitoring.rb +463 -0
  53. data/lib/a2a/plugin.rb +358 -0
  54. data/lib/a2a/plugin_manager.rb +159 -0
  55. data/lib/a2a/plugins/example_auth.rb +81 -0
  56. data/lib/a2a/plugins/example_middleware.rb +118 -0
  57. data/lib/a2a/plugins/example_transport.rb +76 -0
  58. data/lib/a2a/protocol/agent_card.rb +8 -0
  59. data/lib/a2a/protocol/agent_card_server.rb +584 -0
  60. data/lib/a2a/protocol/capability.rb +496 -0
  61. data/lib/a2a/protocol/json_rpc.rb +254 -0
  62. data/lib/a2a/protocol/message.rb +8 -0
  63. data/lib/a2a/protocol/task.rb +8 -0
  64. data/lib/a2a/rails/a2a_controller.rb +258 -0
  65. data/lib/a2a/rails/controller_helpers.rb +499 -0
  66. data/lib/a2a/rails/engine.rb +167 -0
  67. data/lib/a2a/rails/generators/agent_generator.rb +311 -0
  68. data/lib/a2a/rails/generators/install_generator.rb +209 -0
  69. data/lib/a2a/rails/generators/migration_generator.rb +232 -0
  70. data/lib/a2a/rails/generators/templates/add_a2a_indexes.rb +57 -0
  71. data/lib/a2a/rails/generators/templates/agent_controller.rb +122 -0
  72. data/lib/a2a/rails/generators/templates/agent_controller_spec.rb +160 -0
  73. data/lib/a2a/rails/generators/templates/agent_readme.md +200 -0
  74. data/lib/a2a/rails/generators/templates/create_a2a_push_notification_configs.rb +68 -0
  75. data/lib/a2a/rails/generators/templates/create_a2a_tasks.rb +83 -0
  76. data/lib/a2a/rails/generators/templates/example_agent_controller.rb +228 -0
  77. data/lib/a2a/rails/generators/templates/initializer.rb +108 -0
  78. data/lib/a2a/rails/generators/templates/push_notification_config_model.rb +228 -0
  79. data/lib/a2a/rails/generators/templates/task_model.rb +200 -0
  80. data/lib/a2a/rails/tasks/a2a.rake +228 -0
  81. data/lib/a2a/server/a2a_methods.rb +520 -0
  82. data/lib/a2a/server/agent.rb +537 -0
  83. data/lib/a2a/server/agent_execution/agent_executor.rb +279 -0
  84. data/lib/a2a/server/agent_execution/request_context.rb +219 -0
  85. data/lib/a2a/server/apps/rack_app.rb +311 -0
  86. data/lib/a2a/server/apps/sinatra_app.rb +261 -0
  87. data/lib/a2a/server/default_request_handler.rb +350 -0
  88. data/lib/a2a/server/events/event_consumer.rb +116 -0
  89. data/lib/a2a/server/events/event_queue.rb +226 -0
  90. data/lib/a2a/server/example_agent.rb +248 -0
  91. data/lib/a2a/server/handler.rb +281 -0
  92. data/lib/a2a/server/middleware/authentication_middleware.rb +212 -0
  93. data/lib/a2a/server/middleware/cors_middleware.rb +171 -0
  94. data/lib/a2a/server/middleware/logging_middleware.rb +362 -0
  95. data/lib/a2a/server/middleware/rate_limit_middleware.rb +382 -0
  96. data/lib/a2a/server/middleware.rb +213 -0
  97. data/lib/a2a/server/push_notification_manager.rb +327 -0
  98. data/lib/a2a/server/request_handler.rb +136 -0
  99. data/lib/a2a/server/storage/base.rb +141 -0
  100. data/lib/a2a/server/storage/database.rb +266 -0
  101. data/lib/a2a/server/storage/memory.rb +274 -0
  102. data/lib/a2a/server/storage/redis.rb +320 -0
  103. data/lib/a2a/server/storage.rb +38 -0
  104. data/lib/a2a/server/task_manager.rb +534 -0
  105. data/lib/a2a/transport/grpc.rb +481 -0
  106. data/lib/a2a/transport/http.rb +415 -0
  107. data/lib/a2a/transport/sse.rb +499 -0
  108. data/lib/a2a/types/agent_card.rb +540 -0
  109. data/lib/a2a/types/artifact.rb +99 -0
  110. data/lib/a2a/types/base_model.rb +223 -0
  111. data/lib/a2a/types/events.rb +117 -0
  112. data/lib/a2a/types/message.rb +106 -0
  113. data/lib/a2a/types/part.rb +288 -0
  114. data/lib/a2a/types/push_notification.rb +139 -0
  115. data/lib/a2a/types/security.rb +167 -0
  116. data/lib/a2a/types/task.rb +154 -0
  117. data/lib/a2a/types.rb +88 -0
  118. data/lib/a2a/utils/helpers.rb +245 -0
  119. data/lib/a2a/utils/message_buffer.rb +278 -0
  120. data/lib/a2a/utils/performance.rb +247 -0
  121. data/lib/a2a/utils/rails_detection.rb +97 -0
  122. data/lib/a2a/utils/structured_logger.rb +306 -0
  123. data/lib/a2a/utils/time_helpers.rb +167 -0
  124. data/lib/a2a/utils/validation.rb +8 -0
  125. data/lib/a2a/version.rb +6 -0
  126. data/lib/a2a-rails.rb +58 -0
  127. data/lib/a2a.rb +198 -0
  128. 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