llm.rb 10.0.0 → 11.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6ba756238fa72e58ba774567a0c8e2a6d7351cb6313f9c8c08cbdeec8ec9cfa4
4
- data.tar.gz: cba8295670dab2843cec902ae97b7ae14e775359380ba401ca5a0066eb60ad0e
3
+ metadata.gz: 24c3c2930dd3ab321999075b34ef2e5c6d445fec5c873b00ef071caeef3c1406
4
+ data.tar.gz: 0d6921f20dc327f7c424f7282ff3c76f5073ad3eec7f21d483ee7623e4c782f7
5
5
  SHA512:
6
- metadata.gz: b8347b2adfe05a4700ec42e0ed5992a1332355bd20330590d8b3de214d980476a490855ff7e69b5b36c75f3684304c4ee61bdff9ecbcf8001f0b477b8010d064
7
- data.tar.gz: a41512ffbc52b3665118161251441152389ca9daba1a6f4e010303490938dc33393da62f5e821521b2a9f4b45d85fd219b558fa7d2e185c24f43777d26e36a14
6
+ metadata.gz: 3b96ea3336114822ccb2defee4da43089df6e004cebdf27987562f7f339bc2a733cc169d64f9752cc51d0346a069ba3921e99db69c2de1f795abfa69f260a730
7
+ data.tar.gz: b4135fad5bd1c5499b1177c27d6eb9c2da5a84b50a5492e82fe6396821c2b4a5e0891e55cb557d07e5ee4ec912b7ecdd8c7df223ebe76109aa74b7ede5227195
data/CHANGELOG.md CHANGED
@@ -2,20 +2,126 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
- ## v10.0.0
5
+ ## v11.0.0
6
6
 
7
- Changes since `v9.0.0`.
7
+ Changes since `v10.0.0`.
8
+
9
+ This release removes several deprecated or unused APIs, including the `#chat`
10
+ alias from contexts and agents, the `LLM::Function#register` alias, and the
11
+ unused positional `llm` argument from MCP constructors. Generated MCP and A2A
12
+ tools are no longer added to the global tool registry by default.
13
+
14
+ On the additions side, it introduces the A2A (Agent2Agent) protocol client,
15
+ a new `#ask` convenience interface on contexts and agents, one-shot stdio MCP
16
+ requests outside `#session`, `LLM::Function#def` as a short alias for
17
+ `LLM::Function#define`, `LLM::File#exist?`, and `LLM::Tool.a2a?`.
18
+
19
+ ### Breaking
8
20
 
9
- This release unifies context turns under `#talk`, removes the
10
- deprecated `LLM::Bot` alias, and adds shared option resolution
11
- through `LLM::Utils`.
21
+ * **Remove the unused `llm` argument from MCP clients** <br>
22
+ Remove the unused positional `llm` argument from `LLM::MCP.new`,
23
+ `LLM::MCP.stdio`, `LLM::MCP.http`, and `LLM.mcp`.
24
+
25
+ * **Stop globally registering generated MCP and A2A tools** <br>
26
+ Generated tools returned by `LLM::Tool.mcp(...)` and
27
+ `LLM::Tool.a2a(...)` are no longer added to the global
28
+ `LLM::Tool.registry` or `LLM::Function.registry`. They still work
29
+ when passed directly to a context or agent, but registry-based lookup
30
+ now only sees normal loaded `LLM::Tool` subclasses.
31
+
32
+ * **Remove `LLM::Function#register`** <br>
33
+ Remove the `LLM::Function#register` alias and prefer
34
+ `LLM::Function#define` or `LLM::Function#def` when binding a
35
+ function to its implementation. The `register` alias was too easy to
36
+ confuse with the class-level `LLM::Tool.register` and
37
+ `LLM::Function.register` registry APIs.
38
+
39
+ * **Remove the `#chat` alias from contexts and agents** <br>
40
+ Remove the `LLM::Context#chat` and `LLM::Agent#chat` aliases. Prefer
41
+ `#talk` for all context and agent turns.
42
+
43
+ ### Add
12
44
 
13
- Class-level agent tunables can now be resolved lazily via Proc,
14
- `Array[...]` schema/tool param types are supported, and a `key?`
15
- method has been added on providers.
45
+ * **Add `LLM::Function#def`** <br>
46
+ Add `LLM::Function#def` as a short alias for
47
+ `LLM::Function#define` when binding a function instance to its
48
+ implementation.
49
+
50
+ * **Add `LLM::MCP#session`** <br>
51
+ Add `LLM::MCP#session` as an alias for `LLM::MCP#run`, and prefer it
52
+ in examples for scoped stdio MCP sessions that should stay alive
53
+ across discovery and tool calls.
54
+
55
+ * **Add `#ask` to contexts and agents** <br>
56
+ Add `LLM::Context#ask` and `LLM::Agent#ask` as a RubyLLM-compatible
57
+ convenience interface over `#talk`. `#ask` accepts a prompt, optional
58
+ `with:` attachments, an optional `stream:` target, and an optional
59
+ block for streamed chunks, and returns an `LLM::Response`.
60
+
61
+ * **Add `LLM::File#exist?`** <br>
62
+ Add `LLM::File#exist?` as a small convenience wrapper for checking
63
+ whether a local file exists on disk.
64
+
65
+ * **Allow one-shot stdio MCP requests outside `#session`** <br>
66
+ Allow `mcp.tools`, `mcp.prompts`, `mcp.find_prompt(...)`, and
67
+ `mcp.call_tool(...)` to work outside `mcp.session` by starting and
68
+ stopping a stdio transport on demand when needed. This makes stdio
69
+ MCP usable without an explicit session block, while keeping
70
+ `mcp.session` as the preferred pattern for efficient, stateful
71
+ stdio workflows.
72
+
73
+ * **Add A2A client support** <br>
74
+ Add `LLM::A2A`, a client for the Agent2Agent (A2A) protocol with
75
+ REST and JSON-RPC bindings. Remote agent skills can be exposed as
76
+ `LLM::Tool` classes and used through `LLM::Context` or `LLM::Agent`,
77
+ and the client also supports direct messaging, streaming, task
78
+ operations, push notification configuration, extended agent cards,
79
+ persistent HTTP transport selection, and optional REST `base_path`
80
+ prefixing.
81
+
82
+ Refactor shared MCP/A2A HTTP transport setup into
83
+ `LLM::Transport::Utils`, and extend
84
+ `LLM::Transport::StreamDecoder` to accept a callback block directly.
85
+
86
+ * **Add `LLM::Tool.a2a?`** <br>
87
+ Add `LLM::Tool.a2a?` and mark generated A2A-backed tool classes so
88
+ callers can distinguish them from local or MCP tools.
89
+
90
+ ### Fix
91
+
92
+ * **Fix context and agent JSON serialization through `LLM.json`** <br>
93
+ Fix `LLM::Context#to_json` and `LLM::Agent#to_json` to serialize
94
+ through `LLM.json.dump(...)` instead of plain `to_json`.
95
+
96
+ * **Fix block-form ORM agent DSL forwarding** <br>
97
+ Fix block-form `model { ... }`, `tools { ... }`, and
98
+ `schema { ... }` declarations in the ActiveRecord and Sequel agent
99
+ wrappers so persisted agent models configure the internal agent class
100
+ the same way as `LLM::Agent`.
101
+
102
+ * **Fix missing `skills` in ORM agent wrappers** <br>
103
+ Fix the ActiveRecord and Sequel agent wrappers to expose `skills`, so
104
+ persisted agent models can declare skills the same way as
105
+ `LLM::Agent`.
106
+
107
+ * **Fix `acts_as_agent#ctx` return type** <br>
108
+ Fix the ActiveRecord `acts_as_agent` wrapper so its `ctx` helper
109
+ returns the wrapped `LLM::Agent` instead of returning the underlying
110
+ `LLM::Context` directly.
111
+
112
+ ## v10.0.0
113
+
114
+ Changes since `v9.0.0`.
16
115
 
17
- Agent tool confirmation hooks let selected tools be approved or
18
- cancelled before execution. Keep reading to learn more.
116
+ This release removes the `LLM::Context#respond` method, and
117
+ also removes the deprecated `LLM::Bot` alias. **All** class-level
118
+ agent tunables can now be resolved lazily via a Symbol (method name),
119
+ or a Proc. The `LLM::Agent` class can now confirm a tool call
120
+ before it happens, and the `LLM::Schema` class has been extended
121
+ to support `Array[String,Integer]` as a shorthand for
122
+ `Array[AnyOf[String, Integer]]`. The `LLM::Stream` class has
123
+ had its public method surface reduced to help avoid accidental
124
+ collisions.
19
125
 
20
126
  ### Breaking
21
127
 
data/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <a href="llm.rb">
2
+ <a href="https://github.com/llmrb/llm.rb">
3
3
  <img src="https://github.com/llmrb/llm.rb/raw/main/llm.png" width="200" height="200" border="0" alt="llm.rb">
4
4
  </a>
5
5
  </p>
@@ -11,7 +11,7 @@
11
11
  <img src="https://img.shields.io/badge/License-0BSD-orange.svg?" alt="License">
12
12
  </a>
13
13
  <a href="https://github.com/llmrb/llm.rb/tags">
14
- <img src="https://img.shields.io/badge/version-10.0.0-green.svg?" alt="Version">
14
+ <img src="https://img.shields.io/badge/version-11.0.0-green.svg?" alt="Version">
15
15
  </a>
16
16
  </p>
17
17
 
@@ -21,8 +21,9 @@ llm.rb is Ruby's most capable AI runtime.
21
21
 
22
22
  It runs on Ruby's standard library by default. loads optional pieces
23
23
  only when needed, and offers a single runtime for providers, agents,
24
- tools, skills, MCP, streaming, files, and persisted state. As a bonus,
25
- llm.rb is also [available for mruby](https://github.com/llmrb/mruby-llm).
24
+ tools, skills, MCP, A2A (Agent2Agent), RAG (vector stores & embeddings),
25
+ streaming, files, and persisted state. As a bonus, llm.rb is also
26
+ [available for mruby](https://github.com/llmrb/mruby-llm).
26
27
 
27
28
  It supports OpenAI, OpenAI-compatible endpoints, Anthropic, Google
28
29
  Gemini, DeepSeek, xAI, Z.ai, AWS Bedrock, Ollama, and llama.cpp. It
@@ -134,7 +135,9 @@ or
134
135
  [LLM::Agent](https://0x1eef.github.io/x/llm.rb/LLM/Agent.html).
135
136
  In this example, the MCP server runs over stdio and
136
137
  [LLM::Context](https://0x1eef.github.io/x/llm.rb/LLM/Context.html)
137
- uses the same tool loop as local tools:
138
+ uses the same tool loop as local tools. For **stdio**, `mcp.session`
139
+ is the preferred pattern because it keeps one MCP session alive across
140
+ discovery and tool calls:
138
141
 
139
142
  ```ruby
140
143
  require "llm"
@@ -142,20 +145,96 @@ require "llm"
142
145
  llm = LLM.openai(key: ENV["KEY"])
143
146
  mcp = LLM::MCP.stdio(argv: ["ruby", "server.rb"])
144
147
 
145
- mcp.run do
148
+ mcp.session do
146
149
  ctx = LLM::Context.new(llm, stream: $stdout, tools: mcp.tools)
147
150
  ctx.talk "Use the available tools to inspect the environment."
148
151
  ctx.talk(ctx.wait(:call)) while ctx.functions?
149
152
  end
150
153
  ```
151
154
 
155
+ MCP can also be used without `session`. Although it works it is generally
156
+ not recommended for the **stdio** transport because it is inefficient
157
+ to start and stop a fresh MCP process for tool discovery and each tool
158
+ call:
159
+
160
+ ```ruby
161
+ require "llm"
162
+
163
+ llm = LLM.openai(key: ENV["KEY"])
164
+ mcp = LLM::MCP.stdio(argv: ["ruby", "server.rb"])
165
+
166
+ ctx = LLM::Context.new(llm, tools: mcp.tools)
167
+ ctx.talk("Use the available tools to inspect the environment.")
168
+ ctx.talk(ctx.wait(:call)) while ctx.functions?
169
+ ```
170
+
171
+ The HTTP transport can be used with or without the `session` method,
172
+ and unlike the stdio transport it can remain efficient without the
173
+ `session` method through a persistent connection pool that is available
174
+ through the [LLM::Transport.net_http_persistent](https://0x1eef.github.io/x/llm.rb/LLM/Transport.html#method-c-net_http_persistent) transport:
175
+
176
+ ```ruby
177
+ require "llm"
178
+
179
+ llm = LLM.openai(key: ENV["KEY"])
180
+ mcp = LLM::MCP.http(
181
+ url: "https://remote-mcp.example.com",
182
+ transport: LLM::Transport.net_http_persistent
183
+ )
184
+
185
+ ctx = LLM::Context.new(llm, tools: mcp.tools)
186
+ ctx.talk("Use the available tools to inspect the environment.")
187
+ ctx.talk(ctx.wait(:call)) while ctx.functions?
188
+ ```
189
+
190
+ #### A2A (Agent 2 Agent)
191
+
192
+ The
193
+ [LLM::A2A](https://0x1eef.github.io/x/llm.rb/LLM/A2A.html)
194
+ object lets llm.rb use skills provided by a remote A2A agent. Those
195
+ skills are exposed through the same runtime as local tools, so you can
196
+ pass them to either
197
+ [LLM::Context](https://0x1eef.github.io/x/llm.rb/LLM/Context.html)
198
+ or
199
+ [LLM::Agent](https://0x1eef.github.io/x/llm.rb/LLM/Agent.html).
200
+
201
+ Use remote skills as local tools:
202
+
203
+ ```ruby
204
+ require "llm"
205
+
206
+ a2a = LLM::A2A.rest(
207
+ url: "https://remote-agent.example.com",
208
+ headers: {"Authorization" => "Bearer token"}
209
+ )
210
+ llm = LLM.openai(key: ENV["KEY"])
211
+ ctx = LLM::Context.new(llm, tools: a2a.skills)
212
+ ctx.talk "Analyze this CSV and summarize the trends."
213
+ ctx.talk(ctx.wait(:call)) while ctx.functions?
214
+ ```
215
+
216
+ Use persistent HTTP connections:
217
+
218
+ ```ruby
219
+ require "llm"
220
+
221
+ a2a = LLM::A2A.rest(
222
+ url: "https://remote-agent.example.com",
223
+ transport: LLM::Transport.net_http_persistent
224
+ )
225
+ ```
226
+
227
+ For more on direct messaging, task operations, push notification
228
+ configs, and JSON-RPC, see the
229
+ [LLM::A2A API docs](https://0x1eef.github.io/x/llm.rb/LLM/A2A.html).
230
+
152
231
  #### Skills
153
232
 
154
233
  Skills are reusable instructions loaded from a `SKILL.md` directory. They let
155
- you package behavior and tool access together, and they plug into the same
156
- runtime as tools, agents, and MCP. When a skill runs, llm.rb spawns a
157
- subagent with the skill instructions, access to only the tools listed in the
158
- skill, and recent conversation context:
234
+ you package behavior and tool access together, and they plug into the
235
+ same runtime as tools, agents, MCP, and A2A. When a skill runs, llm.rb
236
+ spawns a subagent with the skill instructions, access to only the tools
237
+ listed in the skill, and recent conversation context:
159
238
 
160
239
  ```yaml
161
240
  ---
@@ -277,6 +356,27 @@ ctx2.restore(string:)
277
356
  ctx2.talk "What is my favorite language?"
278
357
  ```
279
358
 
359
+ #### ask
360
+
361
+ [`LLM::Context`](https://0x1eef.github.io/x/llm.rb/LLM/Context.html)
362
+ also provides `ask`, a convenience interface that is compatible with
363
+ RubyLLM's `ask` method. It accepts a prompt, an optional `with:`
364
+ attachment path or paths, an optional `stream:` target, and an optional
365
+ block that chunks are yielded to. It returns an
366
+ [`LLM::Response`](https://0x1eef.github.io/x/llm.rb/LLM/Response.html),
367
+ so use `.content` when you want the text directly:
368
+
369
+ ```ruby
370
+ require "llm"
371
+
372
+ llm = LLM.openai(key: ENV["KEY"])
373
+ ctx = LLM::Context.new(llm)
374
+
375
+ puts ctx.ask("Hello world").content
376
+ puts ctx.ask("Summarize this document.", with: "README.md").content
377
+ ctx.ask("Stream this reply.") { $stdout << _1 }
378
+ ```
379
+
280
380
  ## Installation
281
381
 
282
382
  ```bash
@@ -315,17 +415,18 @@ or [`ctx.remote_file(...)`](https://0x1eef.github.io/x/llm.rb/LLM/Context.html#r
315
415
  Those tagged objects carry the metadata the provider adapter needs to turn one
316
416
  Ruby prompt into the provider-specific multimodal request schema.
317
417
 
318
- `ctx.local_file(path)` tags a local path as a `:local_file` object around
319
- `LLM.File(path)`. If the model understands that file type, you can include it
320
- directly in the prompt array instead of uploading it first through a provider
321
- Files API:
418
+ If the model understands that file type, you can attach a local file directly
419
+ with `ctx.ask(..., with: path)` instead of uploading it first through a
420
+ provider Files API. Under the hood, llm.rb tags the path as a
421
+ [`ctx.local_file(...)`](https://0x1eef.github.io/x/llm.rb/LLM/Context.html#local_file-instance_method)
422
+ object:
322
423
 
323
424
  ```ruby
324
425
  require "llm"
325
426
 
326
427
  llm = LLM.openai(key: ENV["KEY"])
327
428
  ctx = LLM::Context.new(llm)
328
- ctx.talk ["Summarize this document.", ctx.local_file("README.md")]
429
+ puts ctx.ask("Summarize this document.", with: "README.md").content
329
430
  ```
330
431
 
331
432
  #### Context Compaction
@@ -602,26 +703,9 @@ mcp = LLM::MCP.http(
602
703
  persistent: true
603
704
  )
604
705
 
605
- mcp.start
606
706
  ctx = LLM::Context.new(llm, stream: $stdout, tools: mcp.tools)
607
707
  ctx.talk("Pull information about my GitHub account.")
608
708
  ctx.talk(ctx.wait(:call)) while ctx.functions?
609
- mcp.stop
610
- ```
611
-
612
- For scoped work, `mcp.run do ... end` is shorter and handles cleanup for you:
613
-
614
- ```ruby
615
- mcp = LLM::MCP.http(
616
- url: "https://api.githubcopilot.com/mcp/",
617
- headers: {"Authorization" => "Bearer #{ENV["GITHUB_PAT"]}"},
618
- persistent: true
619
- )
620
- mcp.run do
621
- ctx = LLM::Context.new(llm, stream: $stdout, tools: mcp.tools)
622
- ctx.talk("Pull information about my GitHub account.")
623
- ctx.talk(ctx.wait(:call)) while ctx.functions?
624
- end
625
709
  ```
626
710
 
627
711
  ## Resources
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ class LLM::A2A::Card
4
+ ##
5
+ # Represents the agent's optional capabilities.
6
+ class Capabilities
7
+ ##
8
+ # @param [Hash] data
9
+ def initialize(data)
10
+ @data = LLM::Object.from(data)
11
+ end
12
+
13
+ ##
14
+ # Returns whether the agent supports streaming.
15
+ # @return [Boolean]
16
+ def streaming
17
+ @data.streaming == true
18
+ end
19
+
20
+ ##
21
+ # Returns whether the agent supports push notifications.
22
+ # @return [Boolean]
23
+ def push_notifications
24
+ @data.pushNotifications == true
25
+ end
26
+
27
+ ##
28
+ # Returns whether the agent exposes an extended card.
29
+ # @return [Boolean]
30
+ def extended_agent_card
31
+ @data.extendedAgentCard == true
32
+ end
33
+
34
+ ##
35
+ # Returns the declared agent extensions.
36
+ # @return [Array<LLM::Object>]
37
+ def extensions
38
+ @extensions ||= (@data.extensions || []).map { LLM::Object.from(_1) }
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ class LLM::A2A::Card
4
+ ##
5
+ # Represents a protocol interface supported by the agent.
6
+ class Interface
7
+ ##
8
+ # @param [Hash] data
9
+ def initialize(data)
10
+ @data = LLM::Object.from(data)
11
+ end
12
+
13
+ ##
14
+ # Returns the interface URL.
15
+ # @return [String]
16
+ def url
17
+ @data.url
18
+ end
19
+
20
+ ##
21
+ # Returns the protocol binding name.
22
+ # @return [String]
23
+ def protocol_binding
24
+ @data.protocolBinding || @data.protocol_binding
25
+ end
26
+
27
+ ##
28
+ # Returns the A2A protocol version.
29
+ # @return [String]
30
+ def protocol_version
31
+ @data.protocolVersion || @data.protocol_version
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ class LLM::A2A::Card
4
+ ##
5
+ # Represents the service provider of an agent.
6
+ class Provider
7
+ ##
8
+ # @param [Hash] data
9
+ def initialize(data)
10
+ @data = LLM::Object.from(data)
11
+ end
12
+
13
+ ##
14
+ # Returns the provider website URL.
15
+ # @return [String]
16
+ def url
17
+ @data.url
18
+ end
19
+
20
+ ##
21
+ # Returns the provider organization name.
22
+ # @return [String]
23
+ def organization
24
+ @data.organization
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ class LLM::A2A::Card
4
+ ##
5
+ # Represents a single skill/capability of an agent.
6
+ class Skill
7
+ ##
8
+ # @param [Hash] data
9
+ def initialize(data)
10
+ @data = LLM::Object.from(data)
11
+ end
12
+
13
+ ##
14
+ # Returns the unique identifier for the skill.
15
+ # @return [String]
16
+ def id
17
+ @data.id
18
+ end
19
+
20
+ ##
21
+ # Returns the human-readable skill name.
22
+ # @return [String]
23
+ def name
24
+ @data.name
25
+ end
26
+
27
+ ##
28
+ # Returns the detailed skill description.
29
+ # @return [String]
30
+ def description
31
+ @data.description
32
+ end
33
+
34
+ ##
35
+ # Returns capability tags for the skill.
36
+ # @return [Array<String>]
37
+ def tags
38
+ @data.tags || []
39
+ end
40
+
41
+ ##
42
+ # Returns example prompts for the skill.
43
+ # @return [Array<String>]
44
+ def examples
45
+ @data.examples || []
46
+ end
47
+
48
+ ##
49
+ # Returns the supported input media types.
50
+ # @return [Array<String>]
51
+ def input_modes
52
+ @data.inputModes || @data.input_modes || []
53
+ end
54
+
55
+ ##
56
+ # Returns the supported output media types.
57
+ # @return [Array<String>]
58
+ def output_modes
59
+ @data.outputModes || @data.output_modes || []
60
+ end
61
+
62
+ ##
63
+ # @return [String]
64
+ def inspect
65
+ "#<#{LLM::Utils.object_id(self)} @id=#{id.inspect} @name=#{name.inspect}>"
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ class LLM::A2A
4
+ ##
5
+ # Represents an A2A Agent Card — a self-describing manifest for an
6
+ # agent that provides metadata including the agent's identity,
7
+ # capabilities, skills, supported communication methods, and security
8
+ # requirements.
9
+ #
10
+ # Agent Cards are published at `/.well-known/agent-card.json` and
11
+ # allow clients to discover an agent's capabilities before interacting.
12
+ #
13
+ # @example
14
+ # a2a = LLM::A2A.rest(url: "https://agent.example.com")
15
+ # card = a2a.card
16
+ # puts card.name # => "GeoSpatial Route Planner Agent"
17
+ # puts card.description # => "Provides advanced route planning..."
18
+ # card.skills.each { |s| puts "#{s.name}: #{s.description}" }
19
+ class Card
20
+ require_relative "card/skill"
21
+ require_relative "card/interface"
22
+ require_relative "card/capabilities"
23
+ require_relative "card/provider"
24
+
25
+ ##
26
+ # @param [Hash] data The raw Agent Card JSON data
27
+ def initialize(data)
28
+ @data = LLM::Object.from(data)
29
+ end
30
+
31
+ ##
32
+ # Returns a human-readable name for the agent.
33
+ # @return [String]
34
+ def name
35
+ @data.name
36
+ end
37
+
38
+ ##
39
+ # Returns a human-readable description of the agent.
40
+ # @return [String]
41
+ def description
42
+ @data.description
43
+ end
44
+
45
+ ##
46
+ # Returns the agent version.
47
+ # @return [String]
48
+ def version
49
+ @data.version
50
+ end
51
+
52
+ ##
53
+ # Returns the advertised A2A protocol version.
54
+ # @return [String, nil]
55
+ def protocol_version
56
+ @data.protocolVersion || @data.protocol_version
57
+ end
58
+
59
+ ##
60
+ # Returns the documentation URL, when present.
61
+ # @return [String, nil]
62
+ def documentation_url
63
+ @data.documentationUrl || @data.documentation_url
64
+ end
65
+
66
+ ##
67
+ # Returns the icon URL, when present.
68
+ # @return [String, nil]
69
+ def icon_url
70
+ @data.iconUrl || @data.icon_url
71
+ end
72
+
73
+ ##
74
+ # Returns the skills provided by the agent.
75
+ # @return [Array<LLM::A2A::Card::Skill>]
76
+ def skills
77
+ @skills ||= (@data.skills || []).map { Skill.new(_1) }
78
+ end
79
+
80
+ ##
81
+ # Returns the interfaces supported by the agent.
82
+ # @return [Array<LLM::A2A::Card::Interface>]
83
+ def interfaces
84
+ @interfaces ||= (@data.supportedInterfaces || @data.supported_interfaces || []).map { Interface.new(_1) }
85
+ end
86
+
87
+ ##
88
+ # Returns the optional capabilities declaration.
89
+ # @return [LLM::A2A::Card::Capabilities, nil]
90
+ def capabilities
91
+ raw = @data.capabilities
92
+ raw ? Capabilities.new(raw) : nil
93
+ end
94
+
95
+ ##
96
+ # Returns the agent card signatures.
97
+ # @return [Array<LLM::Object>]
98
+ def signatures
99
+ @signatures ||= (@data.signatures || []).map { LLM::Object.from(_1) }
100
+ end
101
+
102
+ ##
103
+ # Returns the security scheme definitions.
104
+ # @return [Hash<String, Hash>, nil]
105
+ def security_schemes
106
+ @data.securitySchemes || @data.security_schemes
107
+ end
108
+
109
+ ##
110
+ # Returns the security requirements.
111
+ # @return [Array<Hash>, nil]
112
+ def security_requirements
113
+ @data.security || @data.security_requirements
114
+ end
115
+
116
+ ##
117
+ # Returns the declared provider information.
118
+ # @return [LLM::A2A::Card::Provider, nil]
119
+ def provider
120
+ raw = @data.provider
121
+ raw ? Provider.new(raw) : nil
122
+ end
123
+
124
+ ##
125
+ # Returns the default input media types.
126
+ # @return [Array<String>]
127
+ def default_input_modes
128
+ @data.defaultInputModes || @data.default_input_modes || []
129
+ end
130
+
131
+ ##
132
+ # Returns the default output media types.
133
+ # @return [Array<String>]
134
+ def default_output_modes
135
+ @data.defaultOutputModes || @data.default_output_modes || []
136
+ end
137
+
138
+ ##
139
+ # @return [String]
140
+ def inspect
141
+ "#<#{LLM::Utils.object_id(self)} @name=#{name.inspect} @skills=#{skills.size}>"
142
+ end
143
+ end
144
+ end