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
data/lib/a2a/store/processor.rb
DELETED
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "async"
|
|
4
|
-
require "console"
|
|
5
|
-
|
|
6
|
-
module A2A
|
|
7
|
-
module Store
|
|
8
|
-
# Async background task processor, modeled after async-job's Inline processor.
|
|
9
|
-
#
|
|
10
|
-
# The gospel (async-job) teaches:
|
|
11
|
-
# - Async::Idler schedules tasks when the event loop is idle (backpressure)
|
|
12
|
-
# - Each job runs in its own fiber (not thread)
|
|
13
|
-
# - The returned Async::Task can be .wait'd for completion notification
|
|
14
|
-
# - Errors are caught and logged, not re-raised
|
|
15
|
-
# - task.defer_stop protects critical sections from interruption
|
|
16
|
-
#
|
|
17
|
-
# This processor enables A2A's non-blocking mode (return_immediately: true).
|
|
18
|
-
# The handler can enqueue work that executes after the HTTP response is sent.
|
|
19
|
-
#
|
|
20
|
-
# Usage:
|
|
21
|
-
#
|
|
22
|
-
# processor = A2A::Store::Processor.new
|
|
23
|
-
#
|
|
24
|
-
# # Fire and forget:
|
|
25
|
-
# processor.call { store.update_state(task_id, "WORKING"); do_work; store.complete(task_id, result) }
|
|
26
|
-
#
|
|
27
|
-
# # Wait for completion:
|
|
28
|
-
# task = processor.call { do_work }
|
|
29
|
-
# task.wait
|
|
30
|
-
#
|
|
31
|
-
class Processor
|
|
32
|
-
attr_reader :call_count, :complete_count, :failed_count
|
|
33
|
-
|
|
34
|
-
def initialize(parent: nil)
|
|
35
|
-
@parent = parent
|
|
36
|
-
@call_count = 0
|
|
37
|
-
@complete_count = 0
|
|
38
|
-
@failed_count = 0
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
# Execute a block asynchronously in a background fiber.
|
|
42
|
-
#
|
|
43
|
-
# Returns the Async::Task so callers can optionally .wait on it.
|
|
44
|
-
#
|
|
45
|
-
# @yield the work to perform
|
|
46
|
-
# @return [Async::Task]
|
|
47
|
-
#
|
|
48
|
-
def call(&block)
|
|
49
|
-
@call_count += 1
|
|
50
|
-
|
|
51
|
-
parent.async do |task|
|
|
52
|
-
task.defer_stop do
|
|
53
|
-
yield
|
|
54
|
-
end
|
|
55
|
-
@complete_count += 1
|
|
56
|
-
rescue => error
|
|
57
|
-
@failed_count += 1
|
|
58
|
-
Console.error(self, "Background task failed", error)
|
|
59
|
-
ensure
|
|
60
|
-
@call_count -= 1
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def start
|
|
65
|
-
# Ensure we have an async context available
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def stop
|
|
69
|
-
# Allow in-flight tasks to drain naturally
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def status
|
|
73
|
-
{
|
|
74
|
-
in_flight: @call_count,
|
|
75
|
-
completed: @complete_count,
|
|
76
|
-
failed: @failed_count,
|
|
77
|
-
}
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
private
|
|
81
|
-
|
|
82
|
-
def parent
|
|
83
|
-
@parent || Async::Task.current
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
test do
|
|
90
|
-
require "async"
|
|
91
|
-
|
|
92
|
-
describe "A2A::Store::Processor" do
|
|
93
|
-
it "executes blocks asynchronously" do
|
|
94
|
-
executed = false
|
|
95
|
-
|
|
96
|
-
Sync do
|
|
97
|
-
processor = A2A::Store::Processor.new
|
|
98
|
-
task = processor.call { executed = true }
|
|
99
|
-
task.wait
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
executed.should == true
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
it "tracks call counts" do
|
|
106
|
-
Sync do
|
|
107
|
-
processor = A2A::Store::Processor.new
|
|
108
|
-
task = processor.call { "work" }
|
|
109
|
-
task.wait
|
|
110
|
-
|
|
111
|
-
processor.complete_count.should == 1
|
|
112
|
-
processor.call_count.should == 0 # decremented after completion
|
|
113
|
-
end
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
it "handles errors gracefully" do
|
|
117
|
-
Sync do
|
|
118
|
-
processor = A2A::Store::Processor.new
|
|
119
|
-
task = processor.call { raise "boom" }
|
|
120
|
-
task.wait rescue nil # task may re-raise
|
|
121
|
-
|
|
122
|
-
processor.failed_count.should == 1
|
|
123
|
-
end
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
it "returns status" do
|
|
127
|
-
Sync do
|
|
128
|
-
processor = A2A::Store::Processor.new
|
|
129
|
-
status = processor.status
|
|
130
|
-
status[:in_flight].should == 0
|
|
131
|
-
status[:completed].should == 0
|
|
132
|
-
status[:failed].should == 0
|
|
133
|
-
end
|
|
134
|
-
end
|
|
135
|
-
end
|
|
136
|
-
end
|
data/lib/a2a/store/pub_sub.rb
DELETED
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "async"
|
|
4
|
-
require "async/queue"
|
|
5
|
-
|
|
6
|
-
module A2A
|
|
7
|
-
module Store
|
|
8
|
-
# Fiber-safe pub/sub for task update streaming.
|
|
9
|
-
#
|
|
10
|
-
# Following the gospel (async-job / protocol-http):
|
|
11
|
-
# - Async::Queue is fiber-safe (no locks needed)
|
|
12
|
-
# - enqueue/dequeue yield the fiber cooperatively
|
|
13
|
-
# - nil sentinel signals end of stream
|
|
14
|
-
#
|
|
15
|
-
# Each subscriber gets an Async::Queue. State mutations push events
|
|
16
|
-
# to all subscribers for the affected task. Terminal states close
|
|
17
|
-
# all subscribers for that task.
|
|
18
|
-
#
|
|
19
|
-
# Usage:
|
|
20
|
-
#
|
|
21
|
-
# pub_sub = A2A::Store::PubSub.new
|
|
22
|
-
#
|
|
23
|
-
# # Subscribe (returns an Async::Queue)
|
|
24
|
-
# queue = pub_sub.subscribe("task-123")
|
|
25
|
-
#
|
|
26
|
-
# # In another fiber, consume events:
|
|
27
|
-
# Async do
|
|
28
|
-
# while event = queue.dequeue
|
|
29
|
-
# process(event)
|
|
30
|
-
# end
|
|
31
|
-
# end
|
|
32
|
-
#
|
|
33
|
-
# # Publish an event to all subscribers:
|
|
34
|
-
# pub_sub.notify("task-123", { type: :status, data: { ... } })
|
|
35
|
-
#
|
|
36
|
-
# # Close all subscribers for a task (terminal state):
|
|
37
|
-
# pub_sub.close("task-123")
|
|
38
|
-
#
|
|
39
|
-
class PubSub
|
|
40
|
-
def initialize
|
|
41
|
-
@subscribers = Hash.new { |h, k| h[k] = [] }
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
# Subscribe to updates for a task.
|
|
45
|
-
# Returns an Async::Queue that will receive events.
|
|
46
|
-
# A nil sentinel signals end of stream.
|
|
47
|
-
def subscribe(task_id)
|
|
48
|
-
queue = Async::Queue.new
|
|
49
|
-
@subscribers[task_id] << queue
|
|
50
|
-
queue
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
# Remove a specific subscriber queue.
|
|
54
|
-
def unsubscribe(task_id, queue)
|
|
55
|
-
@subscribers[task_id].delete(queue)
|
|
56
|
-
@subscribers.delete(task_id) if @subscribers[task_id].empty?
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
# Push an event to all subscribers for a task.
|
|
60
|
-
#
|
|
61
|
-
# @param task_id [String]
|
|
62
|
-
# @param event [Hash] e.g. { type: :status, data: { ... } }
|
|
63
|
-
#
|
|
64
|
-
def notify(task_id, event)
|
|
65
|
-
@subscribers[task_id].each do |queue|
|
|
66
|
-
queue.enqueue(event)
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
# Close all subscribers for a task.
|
|
71
|
-
# Sends nil sentinel and removes all subscriptions.
|
|
72
|
-
def close(task_id)
|
|
73
|
-
@subscribers[task_id].each do |queue|
|
|
74
|
-
queue.enqueue(nil) # sentinel: end of stream
|
|
75
|
-
end
|
|
76
|
-
@subscribers.delete(task_id)
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
# Number of active subscribers for a task.
|
|
80
|
-
def subscriber_count(task_id)
|
|
81
|
-
@subscribers[task_id].size
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
# Total number of tasks with active subscribers.
|
|
85
|
-
def task_count
|
|
86
|
-
@subscribers.size
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
test do
|
|
93
|
-
require "async"
|
|
94
|
-
|
|
95
|
-
describe "A2A::Store::PubSub" do
|
|
96
|
-
it "subscribe returns an Async::Queue" do
|
|
97
|
-
ps = A2A::Store::PubSub.new
|
|
98
|
-
q = ps.subscribe("t1")
|
|
99
|
-
q.is_a?(Async::Queue).should == true
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
it "notify pushes events to all subscribers" do
|
|
103
|
-
ps = A2A::Store::PubSub.new
|
|
104
|
-
q1 = ps.subscribe("t1")
|
|
105
|
-
q2 = ps.subscribe("t1")
|
|
106
|
-
|
|
107
|
-
ps.notify("t1", { type: :status, data: "hello" })
|
|
108
|
-
|
|
109
|
-
Sync do
|
|
110
|
-
q1.dequeue.should == { type: :status, data: "hello" }
|
|
111
|
-
q2.dequeue.should == { type: :status, data: "hello" }
|
|
112
|
-
end
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
it "close sends nil sentinel and removes subscriptions" do
|
|
116
|
-
ps = A2A::Store::PubSub.new
|
|
117
|
-
q = ps.subscribe("t1")
|
|
118
|
-
|
|
119
|
-
ps.close("t1")
|
|
120
|
-
|
|
121
|
-
Sync do
|
|
122
|
-
q.dequeue.should.be.nil
|
|
123
|
-
end
|
|
124
|
-
ps.subscriber_count("t1").should == 0
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
it "unsubscribe removes a single subscriber" do
|
|
128
|
-
ps = A2A::Store::PubSub.new
|
|
129
|
-
q1 = ps.subscribe("t1")
|
|
130
|
-
q2 = ps.subscribe("t1")
|
|
131
|
-
|
|
132
|
-
ps.unsubscribe("t1", q1)
|
|
133
|
-
ps.subscriber_count("t1").should == 1
|
|
134
|
-
|
|
135
|
-
ps.notify("t1", { data: "only q2" })
|
|
136
|
-
|
|
137
|
-
Sync do
|
|
138
|
-
q2.dequeue[:data].should == "only q2"
|
|
139
|
-
end
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
it "does not leak when all subscribers unsubscribed" do
|
|
143
|
-
ps = A2A::Store::PubSub.new
|
|
144
|
-
q = ps.subscribe("t1")
|
|
145
|
-
ps.unsubscribe("t1", q)
|
|
146
|
-
ps.task_count.should == 0
|
|
147
|
-
end
|
|
148
|
-
end
|
|
149
|
-
end
|