hivemind-a2a-agent-plugin 0.1.0a2__py3-none-any.whl

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.
@@ -0,0 +1,157 @@
1
+ """HiveMind A2A agent protocol plugin.
2
+
3
+ Bridges HiveMind natural-language queries to external A2A (Agent-to-Agent)
4
+ agents. Any utterance arriving from hive satellites is forwarded to a
5
+ configured A2A server via JSON-RPC 2.0 (``tasks/send`` / ``tasks/sendSubscribe``),
6
+ and the response is streamed back through the HiveMind session.
7
+
8
+ The plugin is registered under the ``hivemind.agent.protocol`` entry-point
9
+ group so hivemind-core discovers it automatically.
10
+ """
11
+ from __future__ import annotations
12
+
13
+ import dataclasses
14
+ from typing import Any, Dict, Iterator, Optional
15
+
16
+ from ovos_utils.log import LOG
17
+
18
+ from hivemind_plugin_manager.protocols import AgentProtocol
19
+
20
+ from hivemind_a2a_agent_plugin._client import A2AClient
21
+ from hivemind_a2a_agent_plugin.version import __version__
22
+
23
+ # Default configuration keys (all live under hivemind → a2a_agent in
24
+ # the OVOS config tree, but can also be supplied as plain kwargs).
25
+ _CFG_URL = "agent_url"
26
+ _CFG_AUTH = "auth_header"
27
+ _CFG_TIMEOUT = "timeout"
28
+ _CFG_STREAMING = "streaming"
29
+
30
+ _DEFAULT_TIMEOUT = 60.0
31
+
32
+
33
+ @dataclasses.dataclass()
34
+ class A2AAgentProtocol(AgentProtocol):
35
+ """HiveMind agent protocol that delegates NL queries to an A2A agent.
36
+
37
+ Configuration (passed via ``config`` dict or the OVOS ``Configuration``
38
+ key ``"hivemind" → "a2a_agent"``):
39
+
40
+ .. code-block:: yaml
41
+
42
+ hivemind:
43
+ a2a_agent:
44
+ agent_url: "http://localhost:9999"
45
+ auth_header: "Bearer secret" # optional
46
+ timeout: 60 # seconds, optional
47
+ streaming: false # prefer SSE when true, optional
48
+
49
+ The plugin is stateless with respect to the HiveMind client transport —
50
+ it only owns the outbound HTTP connection to the A2A server and maps
51
+ HiveMind session IDs to A2A ``sessionId`` values so conversation context
52
+ is preserved across multi-turn exchanges.
53
+ """
54
+
55
+ config: Dict[str, Any] = dataclasses.field(default_factory=dict)
56
+
57
+ # Internal state — not part of the public interface.
58
+ _client: Optional[A2AClient] = dataclasses.field(
59
+ default=None, init=False, repr=False, compare=False
60
+ )
61
+
62
+ def __post_init__(self) -> None:
63
+ # Merge OVOS global config if available, but explicit kwargs win.
64
+ try:
65
+ from ovos_config import Configuration
66
+ cfg_root = Configuration()
67
+ ovos_a2a = cfg_root.get("hivemind", {}).get("a2a_agent", {})
68
+ except Exception: # pragma: no cover
69
+ ovos_a2a = {}
70
+
71
+ merged: Dict[str, Any] = {**ovos_a2a, **self.config}
72
+
73
+ url: Optional[str] = merged.get(_CFG_URL)
74
+ if not url:
75
+ LOG.warning(
76
+ "A2AAgentProtocol: no agent_url configured — "
77
+ "natural_language_query will return error answers"
78
+ )
79
+ else:
80
+ auth = merged.get(_CFG_AUTH)
81
+ timeout = float(merged.get(_CFG_TIMEOUT, _DEFAULT_TIMEOUT))
82
+ streaming = bool(merged.get(_CFG_STREAMING, False))
83
+ self._client = A2AClient(
84
+ base_url=url,
85
+ auth_header=auth,
86
+ timeout=timeout,
87
+ streaming=streaming,
88
+ )
89
+ LOG.info(f"A2AAgentProtocol: connected to A2A agent at {url}")
90
+
91
+ # ------------------------------------------------------------------
92
+ # AgentProtocol implementation
93
+ # ------------------------------------------------------------------
94
+
95
+ def natural_language_query(
96
+ self,
97
+ utterance: str,
98
+ lang: str,
99
+ session_id: Optional[str] = None,
100
+ ) -> "Iterator[Optional[str]]":
101
+ """Forward *utterance* to the A2A agent and yield response chunks.
102
+
103
+ Args:
104
+ utterance: The user's text, as received from a hive satellite.
105
+ lang: BCP-47 language tag (e.g. ``"en-us"``). Forwarded
106
+ as context metadata so A2A agents can apply
107
+ language-specific processing.
108
+ session_id: HiveMind session identifier; mapped 1-to-1 to the
109
+ A2A ``sessionId`` so multi-turn context is preserved.
110
+
111
+ Yields:
112
+ Non-empty text chunks from the agent response, terminated by
113
+ ``None`` (the AgentProtocol sentinel). On any error a
114
+ human-readable error string is yielded before the sentinel so
115
+ callers always get at least one answer.
116
+ """
117
+ if self._client is None:
118
+ yield "A2A agent not configured — no agent_url set."
119
+ yield None
120
+ return
121
+
122
+ LOG.debug(
123
+ f"A2AAgentProtocol: query lang={lang!r} session={session_id!r} "
124
+ f"utterance={utterance!r}"
125
+ )
126
+
127
+ try:
128
+ if self._client.streaming:
129
+ yielded_any = False
130
+ for chunk in self._client.stream_task(
131
+ message_text=utterance,
132
+ session_id=session_id,
133
+ lang=lang,
134
+ ):
135
+ if chunk:
136
+ yielded_any = True
137
+ yield chunk
138
+ if not yielded_any:
139
+ yield "The A2A agent returned an empty streaming response."
140
+ else:
141
+ text = self._client.send_task(
142
+ message_text=utterance,
143
+ session_id=session_id,
144
+ lang=lang,
145
+ )
146
+ if text:
147
+ yield text
148
+ else:
149
+ yield "The A2A agent returned an empty response."
150
+ except Exception as exc:
151
+ LOG.error(f"A2AAgentProtocol: error querying A2A agent: {exc}", exc_info=True)
152
+ yield f"Error contacting A2A agent: {exc}"
153
+
154
+ yield None
155
+
156
+
157
+ __all__ = ["A2AAgentProtocol", "__version__"]
@@ -0,0 +1,230 @@
1
+ """Thin A2A JSON-RPC 2.0 client.
2
+
3
+ Vendored subset of ovos-a2a-solver-plugin's A2AClient so the HiveMind
4
+ plugin has zero extra dependencies beyond ``httpx``. If ovos-a2a-solver-plugin
5
+ is importable its richer implementation is preferred at import time via the
6
+ ``__init__`` module; this copy is the fallback.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import uuid
12
+ from dataclasses import dataclass, field
13
+ from typing import Any, Dict, Generator, List, Optional
14
+ from urllib.parse import urljoin
15
+
16
+ import httpx
17
+
18
+ from ovos_utils.log import LOG
19
+
20
+
21
+ @dataclass
22
+ class AgentSkill:
23
+ """A capability advertised in an agent card."""
24
+ id: str
25
+ name: str
26
+ description: str = ""
27
+ tags: List[str] = field(default_factory=list)
28
+ examples: List[str] = field(default_factory=list)
29
+
30
+ @classmethod
31
+ def from_dict(cls, d: Dict[str, Any]) -> "AgentSkill":
32
+ return cls(
33
+ id=d.get("id", ""),
34
+ name=d.get("name", ""),
35
+ description=d.get("description", ""),
36
+ tags=d.get("tags", []),
37
+ examples=d.get("examples", []),
38
+ )
39
+
40
+
41
+ @dataclass
42
+ class AgentCard:
43
+ """Parsed A2A agent card (``/.well-known/agent.json``)."""
44
+ name: str
45
+ description: str
46
+ url: str
47
+ version: str = "1.0"
48
+ skills: List[AgentSkill] = field(default_factory=list)
49
+ streaming: bool = False
50
+ raw: Dict[str, Any] = field(default_factory=dict)
51
+
52
+ @classmethod
53
+ def from_dict(cls, d: Dict[str, Any]) -> "AgentCard":
54
+ skills = [AgentSkill.from_dict(s) for s in d.get("skills", [])]
55
+ caps = d.get("capabilities", {})
56
+ return cls(
57
+ name=d.get("name", ""),
58
+ description=d.get("description", ""),
59
+ url=d.get("url", ""),
60
+ version=d.get("version", "1.0"),
61
+ skills=skills,
62
+ streaming=caps.get("streaming", False),
63
+ raw=d,
64
+ )
65
+
66
+
67
+ class A2AClient:
68
+ """Minimal A2A JSON-RPC 2.0 client.
69
+
70
+ Handles:
71
+ - Agent-card discovery (``GET /.well-known/agent.json``)
72
+ - Task submission via ``tasks/send`` (blocking)
73
+ - Streaming via ``tasks/sendSubscribe`` (SSE)
74
+
75
+ Args:
76
+ base_url: Root URL of the A2A server.
77
+ auth_header: Optional ``Authorization`` header value.
78
+ timeout: HTTP timeout in seconds (default 60).
79
+ streaming: Prefer SSE streaming when True.
80
+ """
81
+
82
+ AGENT_CARD_PATH = "/.well-known/agent.json"
83
+ JSONRPC_VERSION = "2.0"
84
+
85
+ def __init__(
86
+ self,
87
+ base_url: str,
88
+ auth_header: Optional[str] = None,
89
+ timeout: float = 60.0,
90
+ streaming: bool = False,
91
+ ) -> None:
92
+ self.base_url = base_url.rstrip("/")
93
+ self.timeout = timeout
94
+ self.streaming = streaming
95
+ headers: Dict[str, str] = {"Content-Type": "application/json"}
96
+ if auth_header:
97
+ headers["Authorization"] = auth_header
98
+ self._http = httpx.Client(headers=headers, timeout=timeout)
99
+
100
+ def fetch_agent_card(self) -> AgentCard:
101
+ """Fetch and parse ``/.well-known/agent.json``."""
102
+ url = self.base_url + self.AGENT_CARD_PATH
103
+ LOG.debug(f"A2AClient: fetching agent card from {url}")
104
+ resp = self._http.get(url)
105
+ resp.raise_for_status()
106
+ card = AgentCard.from_dict(resp.json())
107
+ LOG.debug(f"A2AClient: discovered agent '{card.name}' at {card.url}")
108
+ return card
109
+
110
+ def send_task(
111
+ self,
112
+ message_text: str,
113
+ session_id: Optional[str] = None,
114
+ lang: Optional[str] = None,
115
+ history: Optional[List[Dict[str, str]]] = None,
116
+ ) -> str:
117
+ """Submit a task (blocking) and return the final text response."""
118
+ rpc_id = str(uuid.uuid4())
119
+ parts: List[Dict[str, Any]] = [{"type": "text", "text": message_text}]
120
+ msg: Dict[str, Any] = {"role": "user", "parts": parts}
121
+ if lang:
122
+ msg["metadata"] = {"lang": lang}
123
+ params: Dict[str, Any] = {"id": rpc_id, "message": msg}
124
+ if session_id:
125
+ params["sessionId"] = session_id
126
+ if history:
127
+ params["history"] = [
128
+ {"role": t["role"],
129
+ "parts": [{"type": "text", "text": t["content"]}]}
130
+ for t in history
131
+ ]
132
+
133
+ payload = {
134
+ "jsonrpc": self.JSONRPC_VERSION,
135
+ "id": rpc_id,
136
+ "method": "tasks/send",
137
+ "params": params,
138
+ }
139
+ resp = self._http.post(self.base_url, content=json.dumps(payload))
140
+ resp.raise_for_status()
141
+ return self._extract_text(resp.json(), rpc_id)
142
+
143
+ def stream_task(
144
+ self,
145
+ message_text: str,
146
+ session_id: Optional[str] = None,
147
+ lang: Optional[str] = None,
148
+ history: Optional[List[Dict[str, str]]] = None,
149
+ ) -> Generator[str, None, None]:
150
+ """Submit a task and yield text chunks via SSE (``tasks/sendSubscribe``)."""
151
+ rpc_id = str(uuid.uuid4())
152
+ parts: List[Dict[str, Any]] = [{"type": "text", "text": message_text}]
153
+ msg: Dict[str, Any] = {"role": "user", "parts": parts}
154
+ if lang:
155
+ msg["metadata"] = {"lang": lang}
156
+ params: Dict[str, Any] = {"id": rpc_id, "message": msg}
157
+ if session_id:
158
+ params["sessionId"] = session_id
159
+ if history:
160
+ params["history"] = [
161
+ {"role": t["role"],
162
+ "parts": [{"type": "text", "text": t["content"]}]}
163
+ for t in history
164
+ ]
165
+
166
+ payload = {
167
+ "jsonrpc": self.JSONRPC_VERSION,
168
+ "id": rpc_id,
169
+ "method": "tasks/sendSubscribe",
170
+ "params": params,
171
+ }
172
+
173
+ with self._http.stream(
174
+ "POST", self.base_url,
175
+ content=json.dumps(payload),
176
+ headers={"Accept": "text/event-stream"},
177
+ ) as resp:
178
+ resp.raise_for_status()
179
+ for line in resp.iter_lines():
180
+ line = line.strip()
181
+ if not line or not line.startswith("data:"):
182
+ continue
183
+ data_str = line[len("data:"):].strip()
184
+ if data_str == "[DONE]":
185
+ break
186
+ try:
187
+ event = json.loads(data_str)
188
+ except json.JSONDecodeError:
189
+ continue
190
+ chunk = self._extract_stream_chunk(event)
191
+ if chunk:
192
+ yield chunk
193
+
194
+ @staticmethod
195
+ def _extract_text(body: Dict[str, Any], rpc_id: str) -> str:
196
+ if "error" in body:
197
+ err = body["error"]
198
+ raise RuntimeError(
199
+ f"A2A RPC error {err.get('code')}: {err.get('message')}"
200
+ )
201
+ result = body.get("result", {})
202
+ for artifact in result.get("artifacts", []):
203
+ for part in artifact.get("parts", []):
204
+ if part.get("type") == "text":
205
+ return part["text"]
206
+ msg = result.get("message", {})
207
+ for part in msg.get("parts", []):
208
+ if part.get("type") == "text":
209
+ return part["text"]
210
+ LOG.warning(f"A2AClient: could not extract text from response: {body}")
211
+ return ""
212
+
213
+ @staticmethod
214
+ def _extract_stream_chunk(event: Dict[str, Any]) -> str:
215
+ result = event.get("result", {})
216
+ for key in ("delta", "artifact"):
217
+ artifact = result.get(key, {})
218
+ for part in artifact.get("parts", []):
219
+ if part.get("type") == "text":
220
+ return part["text"]
221
+ return ""
222
+
223
+ def close(self) -> None:
224
+ self._http.close()
225
+
226
+ def __enter__(self) -> "A2AClient":
227
+ return self
228
+
229
+ def __exit__(self, *_: Any) -> None:
230
+ self.close()
@@ -0,0 +1,8 @@
1
+ # START_VERSION_BLOCK
2
+ VERSION_MAJOR = 0
3
+ VERSION_MINOR = 1
4
+ VERSION_BUILD = 0
5
+ VERSION_ALPHA = 2
6
+ # END_VERSION_BLOCK
7
+
8
+ __version__ = f"{VERSION_MAJOR}.{VERSION_MINOR}.{VERSION_BUILD}" + (f"a{VERSION_ALPHA}" if VERSION_ALPHA else "")
@@ -0,0 +1,154 @@
1
+ Metadata-Version: 2.4
2
+ Name: hivemind-a2a-agent-plugin
3
+ Version: 0.1.0a2
4
+ Summary: A2A agent protocol plugin for HiveMind-core
5
+ Author-email: JarbasAi <jarbasai@mailfence.com>
6
+ License: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/TigreGotico/hivemind-a2a-agent-plugin
8
+ Project-URL: Issues, https://github.com/TigreGotico/hivemind-a2a-agent-plugin/issues
9
+ Requires-Python: >=3.10
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Requires-Dist: ovos-utils<1.0.0,>=0.8.2
13
+ Requires-Dist: hivemind-plugin-manager<1.0.0,>=0.5.0
14
+ Requires-Dist: httpx>=0.25.0
15
+ Provides-Extra: test
16
+ Requires-Dist: pytest; extra == "test"
17
+ Requires-Dist: pytest-cov; extra == "test"
18
+ Requires-Dist: fastapi; extra == "test"
19
+ Requires-Dist: uvicorn; extra == "test"
20
+ Requires-Dist: httpx; extra == "test"
21
+ Provides-Extra: dev
22
+ Requires-Dist: pytest; extra == "dev"
23
+ Requires-Dist: pytest-cov; extra == "dev"
24
+ Requires-Dist: fastapi; extra == "dev"
25
+ Requires-Dist: uvicorn; extra == "dev"
26
+ Requires-Dist: httpx; extra == "dev"
27
+ Dynamic: license-file
28
+
29
+ # hivemind-a2a-agent-plugin
30
+
31
+ A [HiveMind](https://github.com/JarbasHiveMind/HiveMind-core) agent-protocol plugin
32
+ that bridges the hive to external
33
+ [A2A (Agent-to-Agent)](https://google.github.io/A2A/) agents.
34
+
35
+ Natural-language queries arriving from hive satellites are forwarded to a configured
36
+ A2A server via JSON-RPC 2.0 (`tasks/send` or `tasks/sendSubscribe`), and the
37
+ response is streamed back to the originating satellite.
38
+
39
+ ---
40
+
41
+ ## What is A2A?
42
+
43
+ [A2A](https://google.github.io/A2A/) is an open protocol for agent interoperability.
44
+ An A2A server:
45
+
46
+ 1. Publishes an **agent card** at `GET /.well-known/agent.json` describing its
47
+ capabilities, skills, and the URL that accepts tasks.
48
+ 2. Accepts **task** requests as JSON-RPC 2.0 at its root URL — either a blocking
49
+ `tasks/send` or a streaming `tasks/sendSubscribe` (SSE).
50
+
51
+ Any compliant A2A server (LangChain agents, Google ADK, CrewAI, custom FastAPI
52
+ services, …) works as a backend for this plugin.
53
+
54
+ ---
55
+
56
+ ## Installation
57
+
58
+ ```bash
59
+ pip install hivemind-a2a-agent-plugin
60
+ ```
61
+
62
+ The plugin registers itself under the `hivemind.agent.protocol` entry-point group
63
+ so HiveMind-core discovers it automatically when it is installed.
64
+
65
+ ---
66
+
67
+ ## Configuration
68
+
69
+ Add the following to your OVOS / HiveMind config (typically
70
+ `~/.config/hivemind/hivemind.conf`):
71
+
72
+ ```json
73
+ {
74
+ "hivemind": {
75
+ "agent_protocol": "hivemind-a2a-agent-plugin",
76
+ "a2a_agent": {
77
+ "agent_url": "http://localhost:9999",
78
+ "auth_header": "Bearer secret",
79
+ "timeout": 60,
80
+ "streaming": false
81
+ }
82
+ }
83
+ }
84
+ ```
85
+
86
+ | Key | Default | Description |
87
+ |--------------|---------|------------------------------------------------------|
88
+ | `agent_url` | — | **Required.** Root URL of the A2A server. |
89
+ | `auth_header`| — | Optional `Authorization` header (e.g. `Bearer …`). |
90
+ | `timeout` | `60` | HTTP timeout in seconds. |
91
+ | `streaming` | `false` | Prefer `tasks/sendSubscribe` (SSE) when `true`. |
92
+
93
+ ---
94
+
95
+ ## Hive wiring example
96
+
97
+ ```
98
+ HiveMind master
99
+ └── hivemind-a2a-agent-plugin ← loaded as agent_protocol
100
+ └── A2A server (http://localhost:9999)
101
+ └── your LLM / agent / tool backend
102
+
103
+ Satellite (voice client, phone, …)
104
+ → "what's the capital of France?"
105
+ → HiveMind master receives utterance
106
+ → plugin forwards to A2A server via tasks/send
107
+ → A2A server responds: "Paris is the capital of France."
108
+ → HiveMind master streams answer back to satellite
109
+ ```
110
+
111
+ **Start a minimal FastAPI A2A server and the HiveMind master:**
112
+
113
+ ```bash
114
+ # 1. run the example mock A2A server (also used by e2e tests)
115
+ uvicorn tests.e2e.mock_a2a_server:app --port 9999
116
+
117
+ # 2. configure hivemind-core to use this plugin (see above)
118
+ # 3. start hivemind-core
119
+ hivemind-core listen
120
+ ```
121
+
122
+ ---
123
+
124
+ ## Session / context mapping
125
+
126
+ The plugin maps the HiveMind `session_id` directly to the A2A `sessionId` parameter
127
+ so multi-turn conversations are kept in context on the A2A server side. No extra
128
+ state is stored in the plugin; the A2A server owns conversation history.
129
+
130
+ ---
131
+
132
+ ## Error handling
133
+
134
+ The plugin never silences errors. If the A2A server is unreachable, returns an
135
+ empty response, or returns a JSON-RPC error object, a human-readable error string
136
+ is yielded to the satellite so the user always receives a reply.
137
+
138
+ ---
139
+
140
+ ## Development
141
+
142
+ ```bash
143
+ git clone https://github.com/TigreGotico/hivemind-a2a-agent-plugin
144
+ cd hivemind-a2a-agent-plugin
145
+ pip install -e ".[dev]"
146
+ pytest tests/ # unit tests (no server needed)
147
+ pytest tests/e2e/ # e2e tests (spins up a FastAPI mock server in-process)
148
+ ```
149
+
150
+ ## Credits
151
+
152
+ Funded by [NGI0 Commons Fund](https://nlnet.nl/project/OpenVoiceOS) / [NLnet](https://nlnet.nl)
153
+ under grant agreement No [101135429](https://cordis.europa.eu/project/id/101135429),
154
+ through the European Commission's [Next Generation Internet](https://ngi.eu) programme.
@@ -0,0 +1,9 @@
1
+ hivemind_a2a_agent_plugin/__init__.py,sha256=ua_J2a-6DOWpJFOje5-FkVF2tOCSJexQ57U0YkZ-MUE,5795
2
+ hivemind_a2a_agent_plugin/_client.py,sha256=f62WCe1uPXgON8sgVO__difY54cLUFOBgpeipQkRpto,7698
3
+ hivemind_a2a_agent_plugin/version.py,sha256=7c51Az-oGaEN_y0AvP9KO0mPZdqtsC6jApogoAQ2ya8,229
4
+ hivemind_a2a_agent_plugin-0.1.0a2.dist-info/licenses/LICENSE,sha256=CCh66Pv-YGCzi6ysUq5rGBsPZ2Vew0zEq-d-xDJZd-M,737
5
+ hivemind_a2a_agent_plugin-0.1.0a2.dist-info/METADATA,sha256=mTUpAXl5whu0AHREVH5yUpiOTIgBZ-RmigiCodzGf_g,4979
6
+ hivemind_a2a_agent_plugin-0.1.0a2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
7
+ hivemind_a2a_agent_plugin-0.1.0a2.dist-info/entry_points.txt,sha256=DFhcZN-HxWF2l9UaaGVHlZjkwbrSqIT7JqLSSIAzBnY,97
8
+ hivemind_a2a_agent_plugin-0.1.0a2.dist-info/top_level.txt,sha256=P0_ME7kyTGv9vWK3whGMgtuJtXQHtzM2Nmtm1mJonY4,26
9
+ hivemind_a2a_agent_plugin-0.1.0a2.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [hivemind.agent.protocol]
2
+ hivemind-a2a-agent-plugin = hivemind_a2a_agent_plugin:A2AAgentProtocol
@@ -0,0 +1,17 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ Copyright 2024 JarbasAi
6
+
7
+ Licensed under the Apache License, Version 2.0 (the "License");
8
+ you may not use this file except in compliance with the License.
9
+ You may obtain a copy of the License at
10
+
11
+ http://www.apache.org/licenses/LICENSE-2.0
12
+
13
+ Unless required by applicable law or agreed to in writing, software
14
+ distributed under the License is distributed on an "AS IS" BASIS,
15
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ See the License for the specific language governing permissions and
17
+ limitations under the License.
@@ -0,0 +1 @@
1
+ hivemind_a2a_agent_plugin