simple_a2a 0.1.0 → 0.3.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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +67 -0
  3. data/README.md +78 -38
  4. data/compare_agent2agent.md +460 -0
  5. data/docs/api/client/index.md +19 -0
  6. data/docs/api/index.md +4 -3
  7. data/docs/api/models/index.md +13 -11
  8. data/docs/api/server/index.md +42 -10
  9. data/docs/api/storage/index.md +0 -1
  10. data/docs/architecture/index.md +17 -15
  11. data/docs/architecture/protocol.md +16 -1
  12. data/docs/assets/images/simple_a2a.jpg +0 -0
  13. data/docs/examples/agent-chaining.md +107 -0
  14. data/docs/examples/auth-headers.md +105 -0
  15. data/docs/examples/cancellation.md +105 -0
  16. data/docs/examples/index.md +123 -52
  17. data/docs/examples/interrupted-states.md +114 -0
  18. data/docs/examples/multipart.md +103 -0
  19. data/docs/examples/push-notifications.md +117 -0
  20. data/docs/examples/resubscribe.md +129 -0
  21. data/docs/examples/sqlite-storage.md +131 -0
  22. data/docs/examples/streaming.md +1 -4
  23. data/docs/guides/push-notifications.md +4 -1
  24. data/docs/guides/streaming.md +34 -5
  25. data/docs/index.md +55 -27
  26. data/examples/04_resubscribe/client.rb +140 -0
  27. data/examples/04_resubscribe/server.rb +75 -0
  28. data/examples/05_cancellation/client.rb +150 -0
  29. data/examples/05_cancellation/server.rb +77 -0
  30. data/examples/06_push_notifications/client.rb +192 -0
  31. data/examples/06_push_notifications/server.rb +123 -0
  32. data/examples/07_agent_chaining/client.rb +120 -0
  33. data/examples/07_agent_chaining/server.rb +150 -0
  34. data/examples/08_interrupted_states/client.rb +148 -0
  35. data/examples/08_interrupted_states/server.rb +142 -0
  36. data/examples/09_multipart/client.rb +117 -0
  37. data/examples/09_multipart/server.rb +97 -0
  38. data/examples/10_auth_headers/client.rb +92 -0
  39. data/examples/10_auth_headers/server.rb +98 -0
  40. data/examples/11_sqlite_storage/Brewfile +1 -0
  41. data/examples/11_sqlite_storage/Gemfile +9 -0
  42. data/examples/11_sqlite_storage/client.rb +114 -0
  43. data/examples/11_sqlite_storage/run +154 -0
  44. data/examples/11_sqlite_storage/server.rb +131 -0
  45. data/examples/README.md +384 -0
  46. data/lib/simple_a2a/client/sse.rb +15 -0
  47. data/lib/simple_a2a/server/app.rb +131 -45
  48. data/lib/simple_a2a/server/base.rb +19 -17
  49. data/lib/simple_a2a/server/broadcast_registry.rb +24 -0
  50. data/lib/simple_a2a/server/multi_agent.rb +1 -1
  51. data/lib/simple_a2a/server/push_config_store.rb +29 -0
  52. data/lib/simple_a2a/server/push_sender.rb +1 -0
  53. data/lib/simple_a2a/server/task_broadcast.rb +46 -0
  54. data/lib/simple_a2a/version.rb +1 -1
  55. metadata +38 -20
  56. data/lib/simple_a2a/server/event_router.rb +0 -50
@@ -3,33 +3,35 @@
3
3
  module A2A
4
4
  module Server
5
5
  class Base
6
- attr_reader :agent_card, :executor, :storage, :event_router, :push_sender
6
+ attr_reader :agent_card, :executor, :storage, :push_sender
7
7
 
8
8
  def initialize(
9
9
  agent_card:,
10
10
  executor:,
11
- storage: Storage::Memory.new,
12
- push_sender: nil,
13
- host: "localhost",
14
- port: 9292
11
+ storage: Storage::Memory.new,
12
+ push_sender: nil,
13
+ push_config_store: nil,
14
+ host: "localhost",
15
+ port: 9292
15
16
  )
16
- @agent_card = agent_card
17
- @executor = executor
18
- @storage = storage
19
- @event_router = EventRouter.new
20
- @push_sender = push_sender
21
- @host = host
22
- @port = port
17
+ @agent_card = agent_card
18
+ @executor = executor
19
+ @storage = storage
20
+ @push_sender = push_sender
21
+ @push_config_store = push_config_store
22
+ @host = host
23
+ @port = port
23
24
  end
24
25
 
25
26
  def rack_app
26
27
  klass = Class.new(App)
27
28
  klass.configure(
28
- agent_card: @agent_card,
29
- storage: @storage,
30
- executor: @executor,
31
- event_router: @event_router,
32
- push_sender: @push_sender
29
+ agent_card: @agent_card,
30
+ storage: @storage,
31
+ executor: @executor,
32
+ broadcast_registry: BroadcastRegistry.new,
33
+ push_sender: @push_sender,
34
+ push_config_store: @push_config_store
33
35
  )
34
36
  klass.freeze.app
35
37
  end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module A2A
4
+ module Server
5
+ class BroadcastRegistry
6
+ def initialize
7
+ @broadcasts = {}
8
+ @mutex = Mutex.new
9
+ end
10
+
11
+ def register(task_id, broadcast)
12
+ @mutex.synchronize { @broadcasts[task_id] = broadcast }
13
+ end
14
+
15
+ def unregister(task_id)
16
+ @mutex.synchronize { @broadcasts.delete(task_id) }
17
+ end
18
+
19
+ def find(task_id)
20
+ @mutex.synchronize { @broadcasts[task_id] }
21
+ end
22
+ end
23
+ end
24
+ end
@@ -40,7 +40,7 @@ module A2A
40
40
  agent_card: cfg[:agent_card],
41
41
  storage: cfg[:storage] || Storage::Memory.new,
42
42
  executor: cfg[:executor],
43
- event_router: cfg[:event_router] || EventRouter.new,
43
+ broadcast_registry: BroadcastRegistry.new,
44
44
  push_sender: cfg[:push_sender]
45
45
  )
46
46
  klass.freeze.app
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module A2A
4
+ module Server
5
+ class PushConfigStore
6
+ def initialize
7
+ @configs = {}
8
+ @mutex = Mutex.new
9
+ end
10
+
11
+ def set(task_id, config)
12
+ @mutex.synchronize { @configs[task_id] = config }
13
+ config
14
+ end
15
+
16
+ def get(task_id)
17
+ @mutex.synchronize { @configs[task_id] }
18
+ end
19
+
20
+ def delete(task_id)
21
+ @mutex.synchronize { @configs.delete(task_id) }
22
+ end
23
+
24
+ def list
25
+ @mutex.synchronize { @configs.dup }
26
+ end
27
+ end
28
+ end
29
+ end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "digest"
3
4
  require "jwt"
4
5
  require "net/http"
5
6
  require "uri"
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ractor_queue"
4
+
5
+ module A2A
6
+ module Server
7
+ class TaskBroadcast
8
+ DONE = :done.freeze
9
+
10
+ BroadcastError = Struct.new(:message)
11
+
12
+ def initialize
13
+ @queues = []
14
+ @mutex = Mutex.new
15
+ end
16
+
17
+ def subscribe(capacity: 64)
18
+ RactorQueue.new(capacity: capacity).tap do |q|
19
+ @mutex.synchronize { @queues << q }
20
+ end
21
+ end
22
+
23
+ def unsubscribe(queue)
24
+ @mutex.synchronize { @queues.delete(queue) }
25
+ end
26
+
27
+ # Duck-type compatible with the old EventRouter interface.
28
+ # task_id is accepted but ignored — the broadcast is already task-scoped.
29
+ def publish(_task_id, event)
30
+ snapshot = @mutex.synchronize { @queues.dup }
31
+ snapshot.each { |q| q.async_push(event) }
32
+ end
33
+
34
+ def error(message)
35
+ ev = BroadcastError.new(message)
36
+ snapshot = @mutex.synchronize { @queues.dup }
37
+ snapshot.each { |q| q.async_push(ev) }
38
+ end
39
+
40
+ def close
41
+ snapshot = @mutex.synchronize { @queues.dup }
42
+ snapshot.each { |q| q.async_push(DONE) }
43
+ end
44
+ end
45
+ end
46
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module A2A
4
- VERSION = "0.1.0"
4
+ VERSION = "0.3.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: simple_a2a
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dewayne VanHoozer
@@ -122,33 +122,19 @@ dependencies:
122
122
  - !ruby/object:Gem::Version
123
123
  version: '2.0'
124
124
  - !ruby/object:Gem::Dependency
125
- name: simple_flow
125
+ name: ractor_queue
126
126
  requirement: !ruby/object:Gem::Requirement
127
127
  requirements:
128
128
  - - "~>"
129
129
  - !ruby/object:Gem::Version
130
- version: '0.4'
130
+ version: '0.2'
131
131
  type: :runtime
132
132
  prerelease: false
133
133
  version_requirements: !ruby/object:Gem::Requirement
134
134
  requirements:
135
135
  - - "~>"
136
136
  - !ruby/object:Gem::Version
137
- version: '0.4'
138
- - !ruby/object:Gem::Dependency
139
- name: typed_bus
140
- requirement: !ruby/object:Gem::Requirement
141
- requirements:
142
- - - "~>"
143
- - !ruby/object:Gem::Version
144
- version: '0.0'
145
- type: :runtime
146
- prerelease: false
147
- version_requirements: !ruby/object:Gem::Requirement
148
- requirements:
149
- - - "~>"
150
- - !ruby/object:Gem::Version
151
- version: '0.0'
137
+ version: '0.2'
152
138
  - !ruby/object:Gem::Dependency
153
139
  name: rake
154
140
  requirement: !ruby/object:Gem::Requirement
@@ -248,7 +234,7 @@ dependencies:
248
234
  - !ruby/object:Gem::Version
249
235
  version: '0'
250
236
  description: Client and server for the A2A protocol — async-first, Rack-compatible,
251
- built on Falcon and TypedBus.
237
+ built on Falcon.
252
238
  email:
253
239
  - dvanhoozer@gmail.com
254
240
  executables: []
@@ -260,6 +246,7 @@ files:
260
246
  - LICENSE.txt
261
247
  - README.md
262
248
  - Rakefile
249
+ - compare_agent2agent.md
263
250
  - docs/api/client/index.md
264
251
  - docs/api/index.md
265
252
  - docs/api/models/index.md
@@ -268,9 +255,18 @@ files:
268
255
  - docs/architecture/index.md
269
256
  - docs/architecture/protocol.md
270
257
  - docs/assets/css/custom.css
258
+ - docs/assets/images/simple_a2a.jpg
259
+ - docs/examples/agent-chaining.md
260
+ - docs/examples/auth-headers.md
271
261
  - docs/examples/basic-usage.md
262
+ - docs/examples/cancellation.md
272
263
  - docs/examples/index.md
264
+ - docs/examples/interrupted-states.md
273
265
  - docs/examples/llm-research.md
266
+ - docs/examples/multipart.md
267
+ - docs/examples/push-notifications.md
268
+ - docs/examples/resubscribe.md
269
+ - docs/examples/sqlite-storage.md
274
270
  - docs/examples/streaming.md
275
271
  - docs/getting-started/installation.md
276
272
  - docs/getting-started/quick-start.md
@@ -286,6 +282,26 @@ files:
286
282
  - examples/03_llm_research/run
287
283
  - examples/03_llm_research/server.rb
288
284
  - examples/03_llm_research/web_client.rb
285
+ - examples/04_resubscribe/client.rb
286
+ - examples/04_resubscribe/server.rb
287
+ - examples/05_cancellation/client.rb
288
+ - examples/05_cancellation/server.rb
289
+ - examples/06_push_notifications/client.rb
290
+ - examples/06_push_notifications/server.rb
291
+ - examples/07_agent_chaining/client.rb
292
+ - examples/07_agent_chaining/server.rb
293
+ - examples/08_interrupted_states/client.rb
294
+ - examples/08_interrupted_states/server.rb
295
+ - examples/09_multipart/client.rb
296
+ - examples/09_multipart/server.rb
297
+ - examples/10_auth_headers/client.rb
298
+ - examples/10_auth_headers/server.rb
299
+ - examples/11_sqlite_storage/Brewfile
300
+ - examples/11_sqlite_storage/Gemfile
301
+ - examples/11_sqlite_storage/client.rb
302
+ - examples/11_sqlite_storage/run
303
+ - examples/11_sqlite_storage/server.rb
304
+ - examples/README.md
289
305
  - examples/common_config.rb
290
306
  - examples/run
291
307
  - lib/simple_a2a.rb
@@ -315,12 +331,14 @@ files:
315
331
  - lib/simple_a2a/server/agent_executor.rb
316
332
  - lib/simple_a2a/server/app.rb
317
333
  - lib/simple_a2a/server/base.rb
334
+ - lib/simple_a2a/server/broadcast_registry.rb
318
335
  - lib/simple_a2a/server/context.rb
319
- - lib/simple_a2a/server/event_router.rb
320
336
  - lib/simple_a2a/server/falcon_runner.rb
321
337
  - lib/simple_a2a/server/multi_agent.rb
338
+ - lib/simple_a2a/server/push_config_store.rb
322
339
  - lib/simple_a2a/server/push_sender.rb
323
340
  - lib/simple_a2a/server/resume_context.rb
341
+ - lib/simple_a2a/server/task_broadcast.rb
324
342
  - lib/simple_a2a/storage/base.rb
325
343
  - lib/simple_a2a/storage/memory.rb
326
344
  - lib/simple_a2a/version.rb
@@ -1,50 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "typed_bus"
4
-
5
- module A2A
6
- module Server
7
- class EventRouter
8
- def initialize
9
- @bus = TypedBus::MessageBus.new
10
- end
11
-
12
- def open(task_id)
13
- return if @bus.channel?(task_id.to_sym)
14
- @bus.add_channel(task_id.to_sym, type: nil, timeout: nil)
15
- end
16
-
17
- def close(task_id)
18
- @bus.remove_channel(task_id.to_sym)
19
- end
20
-
21
- def publish(task_id, event)
22
- sym = task_id.to_sym
23
- open(task_id) unless @bus.channel?(sym)
24
- @bus.publish(sym, event)
25
- rescue ArgumentError
26
- nil
27
- end
28
-
29
- def subscribe(task_id, &block)
30
- sym = task_id.to_sym
31
- open(task_id) unless @bus.channel?(sym)
32
- @bus.subscribe(sym) do |delivery|
33
- block.call(delivery.message)
34
- delivery.ack!
35
- end
36
- end
37
-
38
- def unsubscribe(task_id, id_or_block)
39
- return unless @bus.channel?(task_id.to_sym)
40
- @bus.unsubscribe(task_id.to_sym, id_or_block)
41
- rescue ArgumentError
42
- nil
43
- end
44
-
45
- def channel?(task_id)
46
- @bus.channel?(task_id.to_sym)
47
- end
48
- end
49
- end
50
- end