simple_a2a 0.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.
Files changed (73) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/deploy-github-pages.yml +52 -0
  3. data/CHANGELOG.md +5 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +192 -0
  6. data/Rakefile +13 -0
  7. data/docs/api/client/index.md +124 -0
  8. data/docs/api/index.md +27 -0
  9. data/docs/api/models/index.md +233 -0
  10. data/docs/api/server/index.md +162 -0
  11. data/docs/api/storage/index.md +84 -0
  12. data/docs/architecture/index.md +63 -0
  13. data/docs/architecture/protocol.md +112 -0
  14. data/docs/assets/css/custom.css +6 -0
  15. data/docs/examples/basic-usage.md +77 -0
  16. data/docs/examples/index.md +92 -0
  17. data/docs/examples/llm-research.md +92 -0
  18. data/docs/examples/streaming.md +81 -0
  19. data/docs/getting-started/installation.md +48 -0
  20. data/docs/getting-started/quick-start.md +100 -0
  21. data/docs/guides/custom-storage.md +69 -0
  22. data/docs/guides/push-notifications.md +104 -0
  23. data/docs/guides/streaming.md +75 -0
  24. data/docs/index.md +98 -0
  25. data/examples/01_basic_usage/client.rb +75 -0
  26. data/examples/01_basic_usage/server.rb +57 -0
  27. data/examples/02_streaming/client.rb +70 -0
  28. data/examples/02_streaming/server.rb +177 -0
  29. data/examples/03_llm_research/client.rb +138 -0
  30. data/examples/03_llm_research/run +82 -0
  31. data/examples/03_llm_research/server.rb +203 -0
  32. data/examples/03_llm_research/web_client.rb +501 -0
  33. data/examples/common_config.rb +4 -0
  34. data/examples/run +108 -0
  35. data/lib/simple_a2a/client/base.rb +101 -0
  36. data/lib/simple_a2a/client/sse.rb +58 -0
  37. data/lib/simple_a2a/errors.rb +15 -0
  38. data/lib/simple_a2a/json_rpc.rb +89 -0
  39. data/lib/simple_a2a/models/agent_capabilities.rb +11 -0
  40. data/lib/simple_a2a/models/agent_card.rb +23 -0
  41. data/lib/simple_a2a/models/agent_interface.rb +11 -0
  42. data/lib/simple_a2a/models/agent_provider.rb +11 -0
  43. data/lib/simple_a2a/models/agent_skill.rb +12 -0
  44. data/lib/simple_a2a/models/artifact.rb +23 -0
  45. data/lib/simple_a2a/models/authentication_info.rb +11 -0
  46. data/lib/simple_a2a/models/base.rb +111 -0
  47. data/lib/simple_a2a/models/message.rb +45 -0
  48. data/lib/simple_a2a/models/part.rb +45 -0
  49. data/lib/simple_a2a/models/push_notification_config.rb +17 -0
  50. data/lib/simple_a2a/models/security_scheme.rb +16 -0
  51. data/lib/simple_a2a/models/send_message_configuration.rb +12 -0
  52. data/lib/simple_a2a/models/stream_response.rb +32 -0
  53. data/lib/simple_a2a/models/task.rb +57 -0
  54. data/lib/simple_a2a/models/task_artifact_update_event.rb +21 -0
  55. data/lib/simple_a2a/models/task_status.rb +20 -0
  56. data/lib/simple_a2a/models/task_status_update_event.rb +19 -0
  57. data/lib/simple_a2a/models/types.rb +39 -0
  58. data/lib/simple_a2a/server/agent_executor.rb +16 -0
  59. data/lib/simple_a2a/server/app.rb +227 -0
  60. data/lib/simple_a2a/server/base.rb +43 -0
  61. data/lib/simple_a2a/server/context.rb +44 -0
  62. data/lib/simple_a2a/server/event_router.rb +50 -0
  63. data/lib/simple_a2a/server/falcon_runner.rb +31 -0
  64. data/lib/simple_a2a/server/multi_agent.rb +50 -0
  65. data/lib/simple_a2a/server/push_sender.rb +80 -0
  66. data/lib/simple_a2a/server/resume_context.rb +14 -0
  67. data/lib/simple_a2a/storage/base.rb +12 -0
  68. data/lib/simple_a2a/storage/memory.rb +41 -0
  69. data/lib/simple_a2a/version.rb +5 -0
  70. data/lib/simple_a2a.rb +49 -0
  71. data/mkdocs.yml +143 -0
  72. data/sig/simple_a2a.rbs +4 -0
  73. metadata +353 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d1b4e2d504e01c9511e9334010acfb9e1ea99314990586789aff6db82f7c31fd
4
+ data.tar.gz: eda5925b5f758c25980af95081f214d652387d9cc61bbd37f0bb6f31d2aa7dbc
5
+ SHA512:
6
+ metadata.gz: 1074249a5196cd4f0c533ef12b4662f4897d03a64f9d626429f0e7cbbd1653c6822e566afa4467793fe3d934d5e9d2451079fdcd5e1ad00159b505af814f4e02
7
+ data.tar.gz: 003b13cec22786ee19363f1afa1bdeee064a79a0a600b0b73a3343df6167611dc5b93c8c45fc6522b637c5ae5fde6c322836d6b05743fd53a0a49275ce12786b
@@ -0,0 +1,52 @@
1
+ name: Deploy Documentation to GitHub Pages
2
+ on:
3
+ push:
4
+ branches:
5
+ - main
6
+ - develop
7
+ paths:
8
+ - "docs/**"
9
+ - "mkdocs.yml"
10
+ - ".github/workflows/deploy-github-pages.yml"
11
+ workflow_dispatch:
12
+
13
+ permissions:
14
+ contents: write
15
+ pages: write
16
+ id-token: write
17
+
18
+ jobs:
19
+ deploy:
20
+ runs-on: ubuntu-latest
21
+ steps:
22
+ - name: Checkout code
23
+ uses: actions/checkout@v4
24
+ with:
25
+ fetch-depth: 0
26
+
27
+ - name: Setup Python
28
+ uses: actions/setup-python@v5
29
+ with:
30
+ python-version: 3.x
31
+
32
+ - name: Install dependencies
33
+ run: |
34
+ pip install mkdocs
35
+ pip install mkdocs-material
36
+ pip install mkdocs-macros-plugin
37
+ pip install mike
38
+
39
+ - name: Configure Git
40
+ run: |
41
+ git config --local user.email "action@github.com"
42
+ git config --local user.name "GitHub Action"
43
+
44
+ - name: Build MkDocs site
45
+ run: mkdocs build
46
+
47
+ - name: Deploy to GitHub Pages
48
+ uses: peaceiris/actions-gh-pages@v4
49
+ with:
50
+ github_token: ${{ secrets.GITHUB_TOKEN }}
51
+ publish_dir: ./site
52
+ keep_files: true
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2026-05-07
4
+
5
+ - Initial release
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Dewayne VanHoozer
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,192 @@
1
+ # simple_a2a
2
+
3
+ A Ruby gem implementing the [Agent2Agent (A2A) protocol](https://a2a-protocol.org/latest/) — an open standard by Google and the Linux Foundation for interoperability between AI agents.
4
+
5
+ `simple_a2a` provides a complete A2A client and server in a single package, built on the async Ruby ecosystem with [Falcon](https://github.com/socketry/falcon) as the recommended HTTP server.
6
+
7
+ ## Documentation
8
+
9
+ The full documentation website is available at [https://madbomber.github.io/simple_a2a](https://madbomber.github.io/simple_a2a).
10
+
11
+ ## Lineage
12
+
13
+ `simple_a2a` is the successor to my earlier Ruby gem, `simple_acp`. That gem implemented the Agent Communication Protocol (ACP), which IBM Research introduced through the BeeAI project for interoperable agent communication. ACP later merged into A2A under the Linux Foundation, with the ACP team contributing its technology and expertise to the A2A effort.
14
+
15
+ A2A itself was created by Google and then donated to the Linux Foundation for neutral, open governance. The current A2A specification is maintained by the Linux Foundation-hosted [Agent2Agent project](https://github.com/a2aproject/A2A) and published at [a2a-protocol.org](https://a2a-protocol.org/latest/).
16
+
17
+ > My opinion: the A2A specification is still a little jagged in places. A simple example is that it does not clearly cover whether an A2A server is expected to host only one agent or may host multiple agents. That is a minor example, but it points to the kind of operational detail the specification still needs to tighten up.
18
+
19
+ References:
20
+
21
+ - IBM Research: [Agent Communication Protocol](https://research.ibm.com/projects/agent-communication-protocol)
22
+ - BeeAI announcement: [ACP Joins Forces with A2A Under the Linux Foundation](https://github.com/orgs/i-am-bee/discussions/5)
23
+ - Linux Foundation: [Launch of the Agent2Agent Protocol Project](https://www.linuxfoundation.org/press/linux-foundation-launches-the-agent2agent-protocol-project-to-enable-secure-intelligent-communication-between-ai-agents)
24
+
25
+ ## Protocol Reference
26
+
27
+ - **Official A2A Specification:** [https://a2a-protocol.org/latest/](https://a2a-protocol.org/latest/)
28
+ - **A2A Project on GitHub:** [https://github.com/a2aproject/A2A](https://github.com/a2aproject/A2A)
29
+
30
+ ## Features
31
+
32
+ - Full A2A v1.0 protocol support (backward compatible with v0.3)
33
+ - JSON-RPC 2.0 over HTTP(S) — primary binding
34
+ - Server-Sent Events (SSE) for streaming responses
35
+ - Push notifications via webhooks (RS256 JWT)
36
+ - Task lifecycle management (`submitted → working → completed/failed/canceled`)
37
+ - AgentCard discovery endpoint at `GET /agentCard`
38
+ - Multi-agent hosting with path-based routing via `A2A.multi_server`
39
+ - Async-first via the `async` gem ecosystem (Falcon + async-http)
40
+ - Rack-compatible server with Roda routing
41
+ - Zeitwerk autoloading — top-level module is `A2A`
42
+
43
+ ## Installation
44
+
45
+ Add to your Gemfile:
46
+
47
+ ```ruby
48
+ gem "simple_a2a"
49
+ ```
50
+
51
+ Or install directly:
52
+
53
+ ```bash
54
+ gem install simple_a2a
55
+ ```
56
+
57
+ ## Quick start
58
+
59
+ ```ruby
60
+ require "simple_a2a"
61
+
62
+ # Define your agent logic
63
+ class MyExecutor < A2A::Server::AgentExecutor
64
+ def call(ctx)
65
+ input = ctx.message.text_content
66
+ ctx.task.complete!(artifacts: [
67
+ A2A::Models::Artifact.new(
68
+ parts: [A2A::Models::Part.text("You said: #{input}")]
69
+ )
70
+ ])
71
+ end
72
+ end
73
+
74
+ # Describe your agent
75
+ card = A2A::Models::AgentCard.new(
76
+ name: "MyAgent",
77
+ version: "1.0",
78
+ capabilities: A2A::Models::AgentCapabilities.new,
79
+ skills: [A2A::Models::AgentSkill.new(name: "reply")],
80
+ interfaces: [A2A::Models::AgentInterface.new(
81
+ type: "json-rpc", url: "http://localhost:9292", version: "1.0"
82
+ )]
83
+ )
84
+
85
+ # Start the server (blocks — runs Falcon on port 9292)
86
+ A2A.server(agent_card: card, executor: MyExecutor.new).run
87
+ ```
88
+
89
+ ```ruby
90
+ # Client — talk to any A2A agent
91
+ client = A2A.client(url: "http://localhost:9292")
92
+
93
+ task = client.send_task(message: A2A::Models::Message.user("hello"))
94
+ puts task.status.state # => "completed"
95
+ puts task.artifacts.first.parts.first.text # => "You said: hello"
96
+ ```
97
+
98
+ ## Task lifecycle
99
+
100
+ ```
101
+ submitted → working → completed (terminal)
102
+ → failed (terminal)
103
+ → canceled (terminal)
104
+ → rejected (terminal)
105
+ → input_required (interrupted)
106
+ → auth_required (interrupted)
107
+ ```
108
+
109
+ Terminal tasks cannot be canceled. Interrupted tasks can be resumed by sending a new message.
110
+
111
+ ## Streaming
112
+
113
+ ```ruby
114
+ # Server — emit incremental events
115
+ class StreamingExecutor < A2A::Server::AgentExecutor
116
+ def call(ctx)
117
+ ctx.task.start!
118
+ ctx.emit_status
119
+
120
+ ctx.emit_artifact(A2A::Models::Artifact.new(
121
+ parts: [A2A::Models::Part.text("thinking… ")]
122
+ ))
123
+
124
+ ctx.task.complete!
125
+ ctx.emit_status(final: true)
126
+ end
127
+ end
128
+ ```
129
+
130
+ ```ruby
131
+ # Client — consume SSE events
132
+ client = A2A.sse_client(url: "http://localhost:9292")
133
+
134
+ client.send_subscribe(message: A2A::Models::Message.user("go")) do |event|
135
+ case event
136
+ when A2A::Models::TaskStatusUpdateEvent
137
+ puts "status: #{event.status.state}"
138
+ when A2A::Models::TaskArtifactUpdateEvent
139
+ print event.artifact.parts.map(&:text).join
140
+ end
141
+ end
142
+ ```
143
+
144
+ ## Multi-agent server
145
+
146
+ Use `A2A.multi_server` to host multiple A2A agents in one Falcon process, with each agent mounted at its own path:
147
+
148
+ ```ruby
149
+ A2A.multi_server(
150
+ agents: {
151
+ "/research" => { agent_card: research_card, executor: ResearchExecutor.new },
152
+ "/evaluator" => { agent_card: evaluator_card, executor: EvaluatorExecutor.new }
153
+ },
154
+ port: 9292
155
+ ).run
156
+ ```
157
+
158
+ Each mounted agent has its own AgentCard, executor, storage, and event router.
159
+
160
+ ## Examples
161
+
162
+ The repository includes three runnable demo apps:
163
+
164
+ | Demo | Shows |
165
+ |---|---|
166
+ | `01_basic_usage` | Agent discovery, `tasks/send`, task listing, task lookup, and error handling |
167
+ | `02_streaming` | `tasks/sendSubscribe` with Server-Sent Events and incremental artifact chunks |
168
+ | `03_llm_research` | Multi-agent routing, parallel streaming LLM calls, evaluator agent, and a Sinatra web client |
169
+
170
+ Run the basic and streaming demos end-to-end:
171
+
172
+ ```bash
173
+ bundle exec ruby examples/run 01_basic_usage
174
+ bundle exec ruby examples/run 02_streaming
175
+ ```
176
+
177
+ The LLM research demo requires `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, and demo-specific gems. See the full documentation for setup details.
178
+
179
+ ## Development
180
+
181
+ ```bash
182
+ bin/setup # install dependencies
183
+ bundle exec rake test # run the test suite
184
+ ```
185
+
186
+ ## Contributing
187
+
188
+ Bug reports and pull requests are welcome at [https://github.com/MadBomber/simple_a2a](https://github.com/MadBomber/simple_a2a).
189
+
190
+ ## License
191
+
192
+ Available as open source under the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "minitest/test_task"
5
+
6
+ Minitest::TestTask.create do |t|
7
+ # Load SimpleCov before minitest/autorun so its at_exit fires AFTER
8
+ # Minitest's (LIFO order), meaning coverage is reported after tests run.
9
+ t.test_prelude = %(require_relative "test/test_helper")
10
+ t.framework = "" # test_helper already requires minitest/autorun
11
+ end
12
+
13
+ task default: :test
@@ -0,0 +1,124 @@
1
+ # Client API
2
+
3
+ ## Client::Base
4
+
5
+ Synchronous JSON-RPC client backed by `async/http`. Works inside or outside an async reactor.
6
+
7
+ ```ruby
8
+ client = A2A.client(url: "http://localhost:9292")
9
+ # or
10
+ client = A2A::Client::Base.new(
11
+ url: "http://localhost:9292",
12
+ headers: { "Authorization" => "Bearer token" }
13
+ )
14
+ ```
15
+
16
+ ### Methods
17
+
18
+ #### `#agent_card` → `A2A::Models::AgentCard`
19
+
20
+ Fetches and parses the AgentCard from `GET /agentCard`.
21
+
22
+ ```ruby
23
+ card = client.agent_card
24
+ puts card.name
25
+ puts card.capabilities.streaming
26
+ ```
27
+
28
+ #### `#send_task(message:, **opts)` → `A2A::Models::Task`
29
+
30
+ Sends a `tasks/send` request and waits for the completed task.
31
+
32
+ ```ruby
33
+ task = client.send_task(
34
+ message: A2A::Models::Message.user("hello"),
35
+ task_id: "my-task-id", # optional — server assigns if omitted
36
+ context_id: "ctx-1", # optional
37
+ metadata: { source: "web" } # optional
38
+ )
39
+ puts task.status.state # => "completed"
40
+ ```
41
+
42
+ #### `#get_task(task_id)` → `A2A::Models::Task`
43
+
44
+ Retrieves a task by ID (`tasks/get`). Raises `A2A::Error` if not found.
45
+
46
+ ```ruby
47
+ task = client.get_task("abc-123")
48
+ ```
49
+
50
+ #### `#list_tasks` → `[A2A::Models::Task]`
51
+
52
+ Returns all tasks from the server (`tasks/list`).
53
+
54
+ ```ruby
55
+ tasks = client.list_tasks
56
+ tasks.each { |t| puts "#{t.id}: #{t.status.state}" }
57
+ ```
58
+
59
+ #### `#cancel_task(task_id)` → `A2A::Models::Task`
60
+
61
+ Cancels a non-terminal task (`tasks/cancel`). Raises `A2A::Error` if not cancelable.
62
+
63
+ ```ruby
64
+ task = client.cancel_task("abc-123")
65
+ puts task.status.state # => "canceled"
66
+ ```
67
+
68
+ ### Error handling
69
+
70
+ All RPC errors raise `A2A::Error` with the server's error message:
71
+
72
+ ```ruby
73
+ begin
74
+ client.get_task("no-such-id")
75
+ rescue A2A::Error => e
76
+ puts e.message # => "Task no-such-id not found"
77
+ end
78
+ ```
79
+
80
+ ---
81
+
82
+ ## Client::SSE
83
+
84
+ Extends `Client::Base` with streaming support via `tasks/sendSubscribe`.
85
+
86
+ ```ruby
87
+ client = A2A.sse_client(url: "http://localhost:9292")
88
+ # or
89
+ client = A2A::Client::SSE.new(url: "http://localhost:9292")
90
+ ```
91
+
92
+ ### `#send_subscribe(message:, **opts, &block)`
93
+
94
+ Opens an SSE connection and yields events as they arrive. Blocks until the stream closes.
95
+
96
+ ```ruby
97
+ client.send_subscribe(message: A2A::Models::Message.user("process this")) do |event|
98
+ case event
99
+ when A2A::Models::TaskStatusUpdateEvent
100
+ puts "Status: #{event.status.state} (final=#{event.final})"
101
+ when A2A::Models::TaskArtifactUpdateEvent
102
+ puts "Artifact chunk: #{event.artifact.parts.map(&:text).join}"
103
+ end
104
+ end
105
+ ```
106
+
107
+ Events are instances of:
108
+ - `A2A::Models::TaskStatusUpdateEvent`
109
+ - `A2A::Models::TaskArtifactUpdateEvent`
110
+ - `Hash` — for unrecognized event types
111
+
112
+ The stream ends when the server sends a `final: true` event. The block is not called for malformed or comment-only SSE frames.
113
+
114
+ ### Using inside an Async reactor
115
+
116
+ Both `Base` and `SSE` detect whether they're already inside an `Async` reactor via `Async::Task.current?`. Inside a reactor, they call the underlying `Async::HTTP::Internet` directly. Outside, they wrap the call in `Async { }.wait`.
117
+
118
+ ```ruby
119
+ Async do
120
+ client = A2A.client(url: "http://localhost:9292")
121
+ task = client.send_task(message: A2A::Models::Message.user("hi"))
122
+ puts task.id
123
+ end
124
+ ```
data/docs/api/index.md ADDED
@@ -0,0 +1,27 @@
1
+ # API Reference
2
+
3
+ ## Namespaces
4
+
5
+ | Namespace | Description |
6
+ |---|---|
7
+ | [`A2A::Models`](models/index.md) | Data classes — Task, Message, Part, Artifact, AgentCard, events |
8
+ | [`A2A::Server`](server/index.md) | Server bootstrap, routing, executor base, context, event routing |
9
+ | [`A2A::Client`](client/index.md) | HTTP client (sync JSON-RPC + SSE streaming) |
10
+ | [`A2A::Storage`](storage/index.md) | Task persistence — Memory backend, pluggable base |
11
+ | `A2A::JsonRpc` | JSON-RPC 2.0 request/response/error layer |
12
+
13
+ ## Top-level module
14
+
15
+ ```ruby
16
+ A2A.logger = Logger.new($stdout) # optional — logs internal warnings
17
+
18
+ A2A.server(**opts) # → A2A::Server::Base.new(**opts)
19
+ A2A.client(**opts) # → A2A::Client::Base.new(**opts)
20
+ A2A.sse_client(**opts) # → A2A::Client::SSE.new(**opts)
21
+ ```
22
+
23
+ ## Constants
24
+
25
+ ```ruby
26
+ A2A::VERSION # => "0.1.0"
27
+ ```
@@ -0,0 +1,233 @@
1
+ # Models
2
+
3
+ All model classes live under `A2A::Models` and inherit from `A2A::Models::Base`, which provides a lightweight attribute DSL with camelCase JSON serialization.
4
+
5
+ ## Part
6
+
7
+ The atomic content unit in a message or artifact.
8
+
9
+ ```ruby
10
+ A2A::Models::Part.text("hello") # text part
11
+ A2A::Models::Part.json({ key: "value" }) # JSON data part
12
+ A2A::Models::Part.from_url("https://…", media_type: "image/png") # URL reference
13
+ A2A::Models::Part.binary(File.binread("file.pdf"),
14
+ media_type: "application/pdf",
15
+ filename: "file.pdf") # base64-encoded binary
16
+ ```
17
+
18
+ | Attribute | Type | Description |
19
+ |---|---|---|
20
+ | `text` | String | Plain text content |
21
+ | `data` | Hash | Structured JSON data |
22
+ | `url` | String | URL reference |
23
+ | `raw` | String | Base64-encoded binary |
24
+ | `media_type` | String | MIME type |
25
+ | `filename` | String | Suggested filename |
26
+ | `metadata` | Hash | Arbitrary metadata |
27
+
28
+ **Predicates:** `#text?`, `#json?`, `#url?`, `#raw?`, `#valid?`
29
+ **Decoding:** `#decoded_bytes` → binary string (Base64 decode of `raw`)
30
+
31
+ ---
32
+
33
+ ## Message
34
+
35
+ A message from a user or agent, composed of one or more Parts.
36
+
37
+ ```ruby
38
+ A2A::Models::Message.user("hello") # user message with text part
39
+ A2A::Models::Message.user("prefix", Part.text("…")) # mixed content
40
+ A2A::Models::Message.agent("I understand") # agent message
41
+ ```
42
+
43
+ | Attribute | Type | Required | Description |
44
+ |---|---|---|---|
45
+ | `message_id` | String | auto | UUID, auto-generated |
46
+ | `role` | String | yes | `"user"` or `"agent"` |
47
+ | `parts` | [Part] | yes | Content parts |
48
+ | `context_id` | String | | Task context ID |
49
+ | `task_id` | String | | Task this message belongs to |
50
+ | `reference_task_ids` | [String] | | Related task IDs |
51
+ | `metadata` | Hash | | Arbitrary metadata |
52
+
53
+ **Predicates:** `#user?`, `#agent?`, `#valid?`
54
+ **Helper:** `#text_content` → concatenates all text parts with newlines
55
+
56
+ ---
57
+
58
+ ## Artifact
59
+
60
+ A structured output produced by an agent.
61
+
62
+ ```ruby
63
+ A2A::Models::Artifact.new(
64
+ name: "report",
65
+ parts: [A2A::Models::Part.text("The answer is 42")]
66
+ )
67
+ ```
68
+
69
+ | Attribute | Type | Description |
70
+ |---|---|---|
71
+ | `name` | String | Artifact identifier |
72
+ | `description` | String | Human-readable description |
73
+ | `parts` | [Part] | Content parts |
74
+ | `index` | Integer | Position in artifact sequence |
75
+ | `append` | Boolean | True if this is a streaming chunk appended to a prior artifact |
76
+ | `last_chunk` | Boolean | True if this is the final streaming chunk |
77
+ | `metadata` | Hash | Arbitrary metadata |
78
+
79
+ ---
80
+
81
+ ## Task
82
+
83
+ Represents a unit of work. Tasks are created by the server on each `tasks/send` call.
84
+
85
+ ```ruby
86
+ task = A2A::Models::Task.new(
87
+ status: A2A::Models::TaskStatus.new(state: "submitted")
88
+ )
89
+ ```
90
+
91
+ | Method | Description |
92
+ |---|---|
93
+ | `task.start!` | Transition to `working` |
94
+ | `task.complete!(artifacts: […])` | Transition to `completed`, attach artifacts |
95
+ | `task.fail!(message: "…")` | Transition to `failed` |
96
+ | `task.cancel!` | Transition to `canceled` |
97
+ | `task.reject!(message: "…")` | Transition to `rejected` |
98
+ | `task.require_input!` | Transition to `input_required` |
99
+ | `task.require_auth!` | Transition to `auth_required` |
100
+ | `task.terminal?` | True if state is completed/failed/canceled/rejected |
101
+ | `task.interrupted?` | True if state is input_required/auth_required |
102
+ | `task.state` | Current state string |
103
+
104
+ ---
105
+
106
+ ## TaskStatus
107
+
108
+ Attached to every Task. Created automatically on transition.
109
+
110
+ | Attribute | Type | Description |
111
+ |---|---|---|
112
+ | `state` | String | One of the `Types::TaskState` constants |
113
+ | `message` | Message | Optional status message |
114
+ | `timestamp` | String | ISO 8601, auto-set to `Time.now.iso8601` |
115
+
116
+ ---
117
+
118
+ ## Types
119
+
120
+ ```ruby
121
+ A2A::Models::Types::TaskState::SUBMITTED # => "submitted"
122
+ A2A::Models::Types::TaskState::WORKING # => "working"
123
+ A2A::Models::Types::TaskState::COMPLETED # => "completed"
124
+ A2A::Models::Types::TaskState::FAILED # => "failed"
125
+ A2A::Models::Types::TaskState::CANCELED # => "canceled"
126
+ A2A::Models::Types::TaskState::REJECTED # => "rejected"
127
+ A2A::Models::Types::TaskState::INPUT_REQUIRED # => "input_required"
128
+ A2A::Models::Types::TaskState::AUTH_REQUIRED # => "auth_required"
129
+
130
+ A2A::Models::Types::TaskState.terminal?(state) # => true/false
131
+ A2A::Models::Types::TaskState.interrupted?(state) # => true/false
132
+ A2A::Models::Types::TaskState.active?(state) # => true/false
133
+
134
+ A2A::Models::Types::Role::USER # => "user"
135
+ A2A::Models::Types::Role::AGENT # => "agent"
136
+ ```
137
+
138
+ ---
139
+
140
+ ## AgentCard
141
+
142
+ Describes an agent's identity and capabilities for discovery.
143
+
144
+ ```ruby
145
+ card = A2A::Models::AgentCard.new(
146
+ name: "MyAgent",
147
+ version: "1.0",
148
+ description: "An example agent",
149
+ capabilities: A2A::Models::AgentCapabilities.new(streaming: true),
150
+ skills: [A2A::Models::AgentSkill.new(name: "greet")],
151
+ interfaces: [A2A::Models::AgentInterface.new(
152
+ type: "json-rpc", url: "http://localhost:9292", version: "1.0"
153
+ )]
154
+ )
155
+ ```
156
+
157
+ ### AgentCapabilities
158
+
159
+ | Attribute | Type | Default | Description |
160
+ |---|---|---|---|
161
+ | `streaming` | Boolean | false | Supports `tasks/sendSubscribe` |
162
+ | `push_notifications` | Boolean | false | Supports webhook push |
163
+ | `state_transition_history` | Boolean | false | Preserves task history |
164
+
165
+ ### AgentSkill
166
+
167
+ | Attribute | Description |
168
+ |---|---|
169
+ | `name` | Skill identifier |
170
+ | `description` | Human-readable description |
171
+ | `tags` | Searchable tags |
172
+ | `examples` | Example prompts |
173
+
174
+ ### AgentInterface
175
+
176
+ | Attribute | Description |
177
+ |---|---|
178
+ | `type` | Binding type: `"json-rpc"`, `"http"`, `"grpc"` |
179
+ | `url` | Endpoint URL |
180
+ | `version` | Protocol version |
181
+
182
+ ---
183
+
184
+ ## Events
185
+
186
+ Events are emitted during streaming (`tasks/sendSubscribe`) and via push notifications.
187
+
188
+ ### TaskStatusUpdateEvent
189
+
190
+ ```ruby
191
+ A2A::Models::TaskStatusUpdateEvent.new(
192
+ task_id: task.id,
193
+ context_id: task.context_id,
194
+ status: task.status,
195
+ final: true
196
+ )
197
+ ```
198
+
199
+ ### TaskArtifactUpdateEvent
200
+
201
+ ```ruby
202
+ A2A::Models::TaskArtifactUpdateEvent.new(
203
+ task_id: task.id,
204
+ context_id: task.context_id,
205
+ artifact: artifact,
206
+ append: false,
207
+ last_chunk: false
208
+ )
209
+ ```
210
+
211
+ ---
212
+
213
+ ## Push notification models
214
+
215
+ ### PushNotificationConfig
216
+
217
+ ```ruby
218
+ config = A2A::Models::PushNotificationConfig.new(
219
+ webhook_url: "https://example.com/hook",
220
+ authentication_info: A2A::Models::AuthenticationInfo.new(
221
+ scheme: "bearer"
222
+ )
223
+ )
224
+ config.valid? # => true
225
+ ```
226
+
227
+ ### AuthenticationInfo
228
+
229
+ | Attribute | Description |
230
+ |---|---|
231
+ | `scheme` | `"bearer"` (JWT), `"token"` (static), or custom |
232
+ | `value` | Token value (used for `"token"` scheme) |
233
+ | `header_name` | Override header name (default: `"Authorization"`) |