agent2agent 1.0.8 → 1.1.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/lib/a2a/agent.rb +165 -117
- data/lib/a2a/client.rb +470 -51
- data/lib/a2a/errors/json_rpc_error.rb +71 -0
- data/lib/a2a/errors/rest_error.rb +68 -0
- data/lib/a2a/errors.rb +535 -0
- data/lib/a2a/faraday/middleware/json_rpc/request.rb +96 -0
- data/lib/a2a/faraday/middleware/json_rpc/response.rb +131 -0
- data/lib/a2a/faraday/middleware/rest/request.rb +166 -0
- data/lib/a2a/faraday/middleware/rest/response.rb +144 -0
- data/lib/a2a/faraday/middleware/schema_request.rb +69 -0
- data/lib/a2a/middleware/extract_message.rb +120 -0
- data/lib/a2a/middleware/fetch_task.rb +228 -0
- data/lib/a2a/middleware/limit_history_length.rb +123 -0
- data/lib/a2a/middleware/limit_pagination_size.rb +133 -0
- data/lib/a2a/middleware/sse_stream.rb +235 -0
- data/lib/a2a/middleware.rb +7 -0
- data/lib/a2a/schema/definition.rb +35 -1
- data/lib/a2a/schema.rb +126 -0
- data/lib/a2a/{bindings → server/bindings}/json_rpc.rb +12 -8
- data/lib/a2a/{bindings → server/bindings}/rest.rb +12 -8
- data/lib/a2a/server/dispatcher.rb +52 -54
- data/lib/a2a/server/env.rb +4 -6
- data/lib/a2a/server/triage.rb +1 -1
- data/lib/a2a/server.rb +10 -10
- data/lib/a2a/sse/event_parser.rb +202 -0
- data/lib/a2a/sse/json_rpc_stream.rb +27 -5
- data/lib/a2a/sse/rest_stream.rb +17 -5
- data/lib/a2a/sse/stream.rb +135 -7
- data/lib/a2a/sse.rb +1 -0
- data/lib/a2a/test_helpers.rb +89 -0
- data/lib/a2a/version.rb +1 -1
- data/lib/a2a.rb +6 -2
- data/lib/traces/provider/a2a/{bindings → server/bindings}/json_rpc.rb +2 -2
- data/lib/traces/provider/a2a/{bindings → server/bindings}/rest.rb +2 -2
- data/lib/traces/provider/a2a.rb +2 -2
- metadata +49 -22
- data/lib/a2a/server/cancel_task.rb +0 -14
- data/lib/a2a/server/create_task_push_notification_config.rb +0 -14
- data/lib/a2a/server/delete_task_push_notification_config.rb +0 -14
- data/lib/a2a/server/get_extended_agent_card.rb +0 -15
- data/lib/a2a/server/get_task.rb +0 -14
- data/lib/a2a/server/get_task_push_notification_config.rb +0 -14
- data/lib/a2a/server/list_task_push_notification_configs.rb +0 -14
- data/lib/a2a/server/list_tasks.rb +0 -14
- data/lib/a2a/server/send_message.rb +0 -14
- data/lib/a2a/server/send_streaming_message.rb +0 -14
- data/lib/a2a/server/subscribe_to_task.rb +0 -14
- data/lib/a2a/store/processor.rb +0 -136
- data/lib/a2a/store/pub_sub.rb +0 -149
- data/lib/a2a/store/sqlite.rb +0 -533
- data/lib/a2a/store/webhooks.rb +0 -94
- data/lib/a2a/store.rb +0 -6
- data/lib/a2a/task_store.rb +0 -315
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fadb35d489fe59b7b010bafd2ee6e94adeb0d3cb0d8c6baa2d05fefcdd449fef
|
|
4
|
+
data.tar.gz: 1fa489faf0e9f155d56d995bb49f208dbc4e28f9ae3bf84796b0bd4af02264b9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7ce0595b5f4fbb554306d49fe40588ff16b9036e048012b946930d95143dd3f9f98db62cf7e53c9977b8b0d723e469dbe478aa524ee019da57fd1f29e5b90059
|
|
7
|
+
data.tar.gz: 8caf8f2af8d3f8becdc7b1404ccaebd0333d05c0452cd68a49b59a0ab419bebed4886b1e8f35c1b7eb131a044cd11f97f5ba1ddc70bbf484f923b8bb55826e1e
|
data/lib/a2a/agent.rb
CHANGED
|
@@ -7,17 +7,22 @@ module A2A
|
|
|
7
7
|
# DSL wrapper that collects operation handlers for an A2A agent.
|
|
8
8
|
#
|
|
9
9
|
# An Agent produces handler objects that conform to the Dispatcher's
|
|
10
|
-
#
|
|
11
|
-
#
|
|
10
|
+
# contract (#operations, #call). Register an agent on a Server the
|
|
11
|
+
# same way you would register a plain handler.
|
|
12
12
|
#
|
|
13
13
|
# agent = A2A::Agent.new do
|
|
14
|
-
# on "SendMessage" do
|
|
15
|
-
#
|
|
14
|
+
# on "SendMessage" do
|
|
15
|
+
# respond_with -> (env) {
|
|
16
|
+
# A2A::Schema["Send Message Response"].new({})
|
|
17
|
+
# }
|
|
16
18
|
# end
|
|
17
19
|
#
|
|
18
|
-
# on "GetTask" do
|
|
19
|
-
#
|
|
20
|
-
#
|
|
20
|
+
# on "GetTask" do
|
|
21
|
+
# respond_with -> (env) {
|
|
22
|
+
# task = Task.find_by(id: env["a2a.request"].id)
|
|
23
|
+
# raise A2A::TaskNotFoundError.new(env["a2a.request"].id) unless task
|
|
24
|
+
# task.to_a2a
|
|
25
|
+
# }
|
|
21
26
|
# end
|
|
22
27
|
# end
|
|
23
28
|
#
|
|
@@ -28,105 +33,89 @@ module A2A
|
|
|
28
33
|
|
|
29
34
|
def initialize(&block)
|
|
30
35
|
@handlers = []
|
|
31
|
-
|
|
32
36
|
instance_eval(&block) if block
|
|
33
37
|
end
|
|
34
38
|
|
|
35
|
-
#
|
|
39
|
+
# Define a handler stack for one or more A2A operations.
|
|
40
|
+
#
|
|
41
|
+
# The block is evaluated at definition time to build the middleware
|
|
42
|
+
# stack. Use `use` to add middleware, `respond_with` to set the
|
|
43
|
+
# terminal handler.
|
|
36
44
|
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
45
|
+
# on "SendMessage" do
|
|
46
|
+
# use SomeMiddleware
|
|
47
|
+
# respond_with -> (env) { ... }
|
|
48
|
+
# end
|
|
39
49
|
#
|
|
40
50
|
def on(*operations, &block)
|
|
41
51
|
raise ArgumentError, "on requires at least one operation" if operations.empty?
|
|
42
52
|
raise ArgumentError, "on requires a block" unless block
|
|
43
53
|
|
|
54
|
+
builder = StackBuilder.new
|
|
55
|
+
builder.instance_eval(&block)
|
|
56
|
+
|
|
44
57
|
handler = Handler.new(
|
|
45
|
-
agent: self,
|
|
46
58
|
operations: operations.flatten,
|
|
47
|
-
|
|
59
|
+
app: builder.to_app
|
|
48
60
|
)
|
|
49
61
|
|
|
50
62
|
@handlers << handler
|
|
51
63
|
handler
|
|
52
64
|
end
|
|
53
65
|
|
|
54
|
-
#
|
|
55
|
-
#
|
|
56
|
-
class
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
@agent = agent
|
|
61
|
-
@operations = operations
|
|
62
|
-
@block = block
|
|
66
|
+
# Builds a per-operation middleware stack.
|
|
67
|
+
# Collects `use` and `respond_with` calls, compiles into a callable app.
|
|
68
|
+
class StackBuilder
|
|
69
|
+
def initialize
|
|
70
|
+
@middleware = []
|
|
71
|
+
@terminal = nil
|
|
63
72
|
end
|
|
64
73
|
|
|
65
|
-
def
|
|
66
|
-
|
|
74
|
+
def use(middleware, *args, &block)
|
|
75
|
+
@middleware << [middleware, args, block]
|
|
67
76
|
end
|
|
68
|
-
end
|
|
69
77
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
# directly without holding a reference to the env hash.
|
|
73
|
-
class Context
|
|
74
|
-
def initialize(env)
|
|
75
|
-
@env = env
|
|
78
|
+
def respond_with(callable)
|
|
79
|
+
@terminal = callable
|
|
76
80
|
end
|
|
77
81
|
|
|
78
|
-
def
|
|
79
|
-
|
|
80
|
-
end
|
|
82
|
+
def to_app
|
|
83
|
+
raise ArgumentError, "respond_with is required" unless @terminal
|
|
81
84
|
|
|
82
|
-
|
|
83
|
-
@env["a2a.store"]
|
|
84
|
-
end
|
|
85
|
+
app = Terminal.new(@terminal)
|
|
85
86
|
|
|
86
|
-
|
|
87
|
-
|
|
87
|
+
@middleware.reverse_each do |klass, args, block|
|
|
88
|
+
app = klass.new(app, *args, &block)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
app
|
|
88
92
|
end
|
|
93
|
+
end
|
|
89
94
|
|
|
90
|
-
|
|
91
|
-
|
|
95
|
+
# Wraps the respond_with lambda as a callable.
|
|
96
|
+
# Returns whatever the lambda returns.
|
|
97
|
+
class Terminal
|
|
98
|
+
def initialize(callable)
|
|
99
|
+
@callable = callable
|
|
92
100
|
end
|
|
93
101
|
|
|
94
|
-
def
|
|
95
|
-
@env
|
|
102
|
+
def call(env)
|
|
103
|
+
@callable.call(env)
|
|
96
104
|
end
|
|
105
|
+
end
|
|
97
106
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
# - REST binding -> RestStream (bare JSON events)
|
|
103
|
-
#
|
|
104
|
-
# The stream is registered on env["a2a.stream"] so the binding
|
|
105
|
-
# middleware returns it as the Rack body. Falcon streams it natively
|
|
106
|
-
# via Protocol::HTTP::Body::Writable — no threads, no polling.
|
|
107
|
-
#
|
|
108
|
-
# Usage in a handler block:
|
|
109
|
-
#
|
|
110
|
-
# on "SendStreamingMessage" do |request|
|
|
111
|
-
# s = stream
|
|
112
|
-
# Async do
|
|
113
|
-
# s.event({ "task" => { ... } })
|
|
114
|
-
# s.event({ "statusUpdate" => { ... } })
|
|
115
|
-
# s.finish
|
|
116
|
-
# end
|
|
117
|
-
# end
|
|
118
|
-
#
|
|
119
|
-
def stream
|
|
120
|
-
require "a2a/sse"
|
|
107
|
+
# Handler object produced by the #on DSL method.
|
|
108
|
+
# Conforms to the Dispatcher contract: #operations, #call.
|
|
109
|
+
class Handler
|
|
110
|
+
attr_reader :operations
|
|
121
111
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
end
|
|
112
|
+
def initialize(operations:, app:)
|
|
113
|
+
@operations = operations
|
|
114
|
+
@app = app
|
|
115
|
+
end
|
|
127
116
|
|
|
128
|
-
|
|
129
|
-
|
|
117
|
+
def call(env)
|
|
118
|
+
@app.call(env)
|
|
130
119
|
end
|
|
131
120
|
end
|
|
132
121
|
end
|
|
@@ -136,8 +125,8 @@ test do
|
|
|
136
125
|
describe "A2A::Agent" do
|
|
137
126
|
it "registers handlers via the on DSL" do
|
|
138
127
|
agent = A2A::Agent.new do
|
|
139
|
-
on "SendMessage" do
|
|
140
|
-
|
|
128
|
+
on "SendMessage" do
|
|
129
|
+
respond_with -> (env) { :ok }
|
|
141
130
|
end
|
|
142
131
|
end
|
|
143
132
|
|
|
@@ -147,8 +136,8 @@ test do
|
|
|
147
136
|
|
|
148
137
|
it "registers multiple operations on a single handler" do
|
|
149
138
|
agent = A2A::Agent.new do
|
|
150
|
-
on "SendMessage", "GetTask" do
|
|
151
|
-
|
|
139
|
+
on "SendMessage", "GetTask" do
|
|
140
|
+
respond_with -> (env) { :ok }
|
|
152
141
|
end
|
|
153
142
|
end
|
|
154
143
|
|
|
@@ -158,7 +147,9 @@ test do
|
|
|
158
147
|
it "raises if on is called without operations" do
|
|
159
148
|
lambda {
|
|
160
149
|
A2A::Agent.new do
|
|
161
|
-
on do
|
|
150
|
+
on do
|
|
151
|
+
respond_with -> (env) { :ok }
|
|
152
|
+
end
|
|
162
153
|
end
|
|
163
154
|
}.should.raise(ArgumentError)
|
|
164
155
|
end
|
|
@@ -170,74 +161,131 @@ test do
|
|
|
170
161
|
}.should.raise(ArgumentError)
|
|
171
162
|
end
|
|
172
163
|
|
|
173
|
-
it "
|
|
164
|
+
it "raises if respond_with is not provided" do
|
|
165
|
+
lambda {
|
|
166
|
+
A2A::Agent.new do
|
|
167
|
+
on "SendMessage" do
|
|
168
|
+
use Class.new
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
}.should.raise(ArgumentError)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
it "returns the respond_with lambda result" do
|
|
174
175
|
agent = A2A::Agent.new do
|
|
175
|
-
on "SendMessage" do
|
|
176
|
-
|
|
176
|
+
on "SendMessage" do
|
|
177
|
+
respond_with -> (env) { { "echo" => true } }
|
|
177
178
|
end
|
|
178
179
|
end
|
|
179
180
|
|
|
180
|
-
env = {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
}
|
|
181
|
+
env = { "a2a.request" => {} }
|
|
182
|
+
result = agent.handlers.first.call(env)
|
|
183
|
+
result.should == { "echo" => true }
|
|
184
|
+
end
|
|
185
185
|
|
|
186
|
-
|
|
186
|
+
it "returns a Schema::Definition from respond_with" do
|
|
187
|
+
schema_obj = A2A::Schema["Task"].new(
|
|
188
|
+
"id" => "t-1",
|
|
189
|
+
"contextId" => "c-1",
|
|
190
|
+
"status" => { "state" => "TASK_STATE_COMPLETED", "timestamp" => "2025-01-01T00:00:00.000Z" },
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
agent = A2A::Agent.new do
|
|
194
|
+
on "GetTask" do
|
|
195
|
+
respond_with -> (env) { schema_obj }
|
|
196
|
+
end
|
|
197
|
+
end
|
|
187
198
|
|
|
188
|
-
env
|
|
199
|
+
env = { "a2a.request" => {} }
|
|
200
|
+
result = agent.handlers.first.call(env)
|
|
201
|
+
result.should == schema_obj
|
|
189
202
|
end
|
|
190
203
|
|
|
191
|
-
it "
|
|
192
|
-
|
|
193
|
-
seen_store = nil
|
|
204
|
+
it "passes env to the respond_with lambda" do
|
|
205
|
+
seen_env = nil
|
|
194
206
|
|
|
195
207
|
agent = A2A::Agent.new do
|
|
196
|
-
on "SendMessage" do
|
|
197
|
-
|
|
208
|
+
on "SendMessage" do
|
|
209
|
+
respond_with -> (env) { seen_env = env; :ok }
|
|
198
210
|
end
|
|
199
211
|
end
|
|
200
212
|
|
|
201
|
-
env = { "a2a.
|
|
213
|
+
env = { "a2a.request" => { "msg" => "hi" } }
|
|
202
214
|
agent.handlers.first.call(env)
|
|
203
|
-
|
|
204
|
-
seen_store.should == store
|
|
215
|
+
seen_env.should == env
|
|
205
216
|
end
|
|
206
217
|
|
|
207
|
-
it "
|
|
218
|
+
it "executes middleware in order" do
|
|
219
|
+
order = []
|
|
220
|
+
|
|
221
|
+
mw1 = Class.new do
|
|
222
|
+
define_method(:initialize) { |app| @app = app }
|
|
223
|
+
define_method(:call) { |env| order << :mw1; @app.call(env) }
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
mw2 = Class.new do
|
|
227
|
+
define_method(:initialize) { |app| @app = app }
|
|
228
|
+
define_method(:call) { |env| order << :mw2; @app.call(env) }
|
|
229
|
+
end
|
|
230
|
+
|
|
208
231
|
agent = A2A::Agent.new do
|
|
209
|
-
on "
|
|
210
|
-
|
|
211
|
-
|
|
232
|
+
on "SendMessage" do
|
|
233
|
+
use mw1
|
|
234
|
+
use mw2
|
|
235
|
+
respond_with -> (env) { order << :terminal; :done }
|
|
212
236
|
end
|
|
213
237
|
end
|
|
214
238
|
|
|
215
|
-
env = {
|
|
216
|
-
"a2a.store" => A2A::TaskStore.new,
|
|
217
|
-
"a2a.request" => {},
|
|
218
|
-
"a2a.json_rpc_id" => 42,
|
|
219
|
-
}
|
|
239
|
+
env = { "a2a.request" => {} }
|
|
220
240
|
agent.handlers.first.call(env)
|
|
241
|
+
order.should == [:mw1, :mw2, :terminal]
|
|
242
|
+
end
|
|
221
243
|
|
|
222
|
-
|
|
223
|
-
|
|
244
|
+
it "middleware can intercept and return early" do
|
|
245
|
+
blocker = Class.new do
|
|
246
|
+
define_method(:initialize) { |app| @app = app }
|
|
247
|
+
define_method(:call) { |env| :blocked }
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
agent = A2A::Agent.new do
|
|
251
|
+
on "SendMessage" do
|
|
252
|
+
use blocker
|
|
253
|
+
respond_with -> (env) { :should_not_reach }
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
env = { "a2a.request" => {} }
|
|
258
|
+
result = agent.handlers.first.call(env)
|
|
259
|
+
result.should == :blocked
|
|
224
260
|
end
|
|
225
261
|
|
|
226
|
-
|
|
262
|
+
# ── Error behavior ─────────────────────────────────────────────────
|
|
263
|
+
# Errors propagate to the Dispatcher which catches them.
|
|
264
|
+
|
|
265
|
+
it "lets A2A::Error propagate (Dispatcher catches it)" do
|
|
227
266
|
agent = A2A::Agent.new do
|
|
228
|
-
on "
|
|
229
|
-
|
|
230
|
-
|
|
267
|
+
on "GetTask" do
|
|
268
|
+
respond_with -> (env) {
|
|
269
|
+
raise A2A::TaskNotFoundError.new("task-abc")
|
|
270
|
+
}
|
|
231
271
|
end
|
|
232
272
|
end
|
|
233
273
|
|
|
234
|
-
env = {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
274
|
+
env = { "a2a.request" => {} }
|
|
275
|
+
lambda { agent.handlers.first.call(env) }.should.raise(A2A::TaskNotFoundError)
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
it "lets unexpected errors propagate" do
|
|
279
|
+
agent = A2A::Agent.new do
|
|
280
|
+
on "SendMessage" do
|
|
281
|
+
respond_with -> (env) {
|
|
282
|
+
raise RuntimeError, "unexpected bug"
|
|
283
|
+
}
|
|
284
|
+
end
|
|
285
|
+
end
|
|
239
286
|
|
|
240
|
-
env
|
|
287
|
+
env = { "a2a.request" => {} }
|
|
288
|
+
lambda { agent.handlers.first.call(env) }.should.raise(RuntimeError)
|
|
241
289
|
end
|
|
242
290
|
end
|
|
243
291
|
end
|