theprotocol-sdk 0.1.0__tar.gz → 0.2.0__tar.gz

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 (39) hide show
  1. theprotocol_sdk-0.2.0/LICENSE +176 -0
  2. theprotocol_sdk-0.2.0/PKG-INFO +136 -0
  3. theprotocol_sdk-0.2.0/README.md +95 -0
  4. {theprotocol_sdk-0.1.0 → theprotocol_sdk-0.2.0}/pyproject.toml +5 -2
  5. {theprotocol_sdk-0.1.0 → theprotocol_sdk-0.2.0}/src/theprotocol/agent/router.py +41 -15
  6. {theprotocol_sdk-0.1.0 → theprotocol_sdk-0.2.0}/src/theprotocol/agent/task_store.py +9 -7
  7. {theprotocol_sdk-0.1.0 → theprotocol_sdk-0.2.0}/src/theprotocol/bridges/acp.py +6 -6
  8. {theprotocol_sdk-0.1.0 → theprotocol_sdk-0.2.0}/src/theprotocol/bridges/google_a2a.py +8 -6
  9. {theprotocol_sdk-0.1.0 → theprotocol_sdk-0.2.0}/src/theprotocol/client/a2a_client.py +19 -8
  10. {theprotocol_sdk-0.1.0 → theprotocol_sdk-0.2.0}/src/theprotocol/models/a2a_protocol.py +11 -9
  11. {theprotocol_sdk-0.1.0 → theprotocol_sdk-0.2.0}/src/theprotocol/models/agent_card.py +2 -1
  12. theprotocol_sdk-0.2.0/src/theprotocol/models/v1_compat.py +230 -0
  13. {theprotocol_sdk-0.1.0 → theprotocol_sdk-0.2.0}/tests/test_bridge_anp.py +3 -3
  14. {theprotocol_sdk-0.1.0 → theprotocol_sdk-0.2.0}/tests/test_bridge_google_a2a.py +1 -1
  15. {theprotocol_sdk-0.1.0 → theprotocol_sdk-0.2.0}/tests/test_models.py +2 -2
  16. theprotocol_sdk-0.1.0/PKG-INFO +0 -96
  17. theprotocol_sdk-0.1.0/README.md +0 -61
  18. {theprotocol_sdk-0.1.0 → theprotocol_sdk-0.2.0}/.gitignore +0 -0
  19. {theprotocol_sdk-0.1.0 → theprotocol_sdk-0.2.0}/examples/simple_agent.py +0 -0
  20. {theprotocol_sdk-0.1.0 → theprotocol_sdk-0.2.0}/src/theprotocol/__init__.py +0 -0
  21. {theprotocol_sdk-0.1.0 → theprotocol_sdk-0.2.0}/src/theprotocol/agent/__init__.py +0 -0
  22. {theprotocol_sdk-0.1.0 → theprotocol_sdk-0.2.0}/src/theprotocol/agent/base.py +0 -0
  23. {theprotocol_sdk-0.1.0 → theprotocol_sdk-0.2.0}/src/theprotocol/agent/decorators.py +0 -0
  24. {theprotocol_sdk-0.1.0 → theprotocol_sdk-0.2.0}/src/theprotocol/bridges/__init__.py +0 -0
  25. {theprotocol_sdk-0.1.0 → theprotocol_sdk-0.2.0}/src/theprotocol/bridges/anp/__init__.py +0 -0
  26. {theprotocol_sdk-0.1.0 → theprotocol_sdk-0.2.0}/src/theprotocol/bridges/anp/auth.py +0 -0
  27. {theprotocol_sdk-0.1.0 → theprotocol_sdk-0.2.0}/src/theprotocol/bridges/anp/bridge.py +0 -0
  28. {theprotocol_sdk-0.1.0 → theprotocol_sdk-0.2.0}/src/theprotocol/bridges/anp/did_wba.py +0 -0
  29. {theprotocol_sdk-0.1.0 → theprotocol_sdk-0.2.0}/src/theprotocol/bridges/anp/translation.py +0 -0
  30. {theprotocol_sdk-0.1.0 → theprotocol_sdk-0.2.0}/src/theprotocol/bridges/base.py +0 -0
  31. {theprotocol_sdk-0.1.0 → theprotocol_sdk-0.2.0}/src/theprotocol/bridges/mcp.py +0 -0
  32. {theprotocol_sdk-0.1.0 → theprotocol_sdk-0.2.0}/src/theprotocol/client/__init__.py +0 -0
  33. {theprotocol_sdk-0.1.0 → theprotocol_sdk-0.2.0}/src/theprotocol/client/credentials.py +0 -0
  34. {theprotocol_sdk-0.1.0 → theprotocol_sdk-0.2.0}/src/theprotocol/exceptions.py +0 -0
  35. {theprotocol_sdk-0.1.0 → theprotocol_sdk-0.2.0}/src/theprotocol/models/__init__.py +0 -0
  36. {theprotocol_sdk-0.1.0 → theprotocol_sdk-0.2.0}/tests/test_agent.py +0 -0
  37. {theprotocol_sdk-0.1.0 → theprotocol_sdk-0.2.0}/tests/test_bridge_acp.py +0 -0
  38. {theprotocol_sdk-0.1.0 → theprotocol_sdk-0.2.0}/tests/test_bridge_mcp.py +0 -0
  39. {theprotocol_sdk-0.1.0 → theprotocol_sdk-0.2.0}/tests/test_credentials.py +0 -0
@@ -0,0 +1,176 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
@@ -0,0 +1,136 @@
1
+ Metadata-Version: 2.4
2
+ Name: theprotocol-sdk
3
+ Version: 0.2.0
4
+ Summary: TheProtocol SDK — Build and call A2A agents. Protocol bridges for ACP, ADK, and more.
5
+ Project-URL: Homepage, https://theprotocol.cloud
6
+ Project-URL: Documentation, https://api.theprotocol.cloud/docs
7
+ Project-URL: Repository, https://github.com/theprotocol/theprotocol-sdk
8
+ Author-email: TheProtocol <sdk@theprotocol.cloud>
9
+ License: Apache-2.0
10
+ License-File: LICENSE
11
+ Keywords: a2a,agents,ai,mcp,protocol,theprotocol
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: Apache Software License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Software Development :: Libraries
20
+ Requires-Python: >=3.10
21
+ Requires-Dist: httpx[http2]>=0.27
22
+ Requires-Dist: pydantic<3.0,>=2.0
23
+ Requires-Dist: python-dotenv>=1.0
24
+ Provides-Extra: all
25
+ Requires-Dist: cryptography>=42.0; extra == 'all'
26
+ Requires-Dist: fastapi>=0.111; extra == 'all'
27
+ Requires-Dist: keyring>=24; extra == 'all'
28
+ Provides-Extra: anp
29
+ Requires-Dist: cryptography>=42.0; extra == 'anp'
30
+ Provides-Extra: keyring
31
+ Requires-Dist: keyring>=24; extra == 'keyring'
32
+ Provides-Extra: server
33
+ Requires-Dist: fastapi>=0.111; extra == 'server'
34
+ Provides-Extra: test
35
+ Requires-Dist: cryptography>=42.0; extra == 'test'
36
+ Requires-Dist: httpx>=0.27; extra == 'test'
37
+ Requires-Dist: pytest-asyncio>=0.21; extra == 'test'
38
+ Requires-Dist: pytest>=7.0; extra == 'test'
39
+ Requires-Dist: respx>=0.20; extra == 'test'
40
+ Description-Content-Type: text/markdown
41
+
42
+ # theprotocol-sdk
43
+
44
+ **A2A v1.0** — Build and call AI agents on [TheProtocol](https://theprotocol.cloud). Native protocol bridges for Google A2A, ANP, and MCP.
45
+
46
+ ## Install
47
+
48
+ ```bash
49
+ pip install theprotocol-sdk # Client only (call agents)
50
+ pip install theprotocol-sdk[server] # + FastAPI router (build agents)
51
+ pip install theprotocol-sdk[anp] # + Ed25519 crypto for ANP DID:WBA
52
+ pip install theprotocol-sdk[all] # Everything
53
+ ```
54
+
55
+ ## Build an Agent
56
+
57
+ ```python
58
+ from theprotocol.agent import BaseA2AAgent, create_a2a_router
59
+ from fastapi import FastAPI
60
+
61
+ class MyAgent(BaseA2AAgent):
62
+ async def handle_task_send(self, task_id, message):
63
+ return "task-1"
64
+ async def handle_task_get(self, task_id): ...
65
+ async def handle_task_cancel(self, task_id): return True
66
+ async def handle_subscribe_request(self, task_id): yield
67
+
68
+ app = FastAPI()
69
+ app.include_router(create_a2a_router(MyAgent()))
70
+ ```
71
+
72
+ Your agent speaks A2A v1.0 out of the box. It accepts both `message/send` (v1.0) and `tasks/send` (v0.3) for backward compatibility.
73
+
74
+ ## Call a Remote Agent
75
+
76
+ ```python
77
+ from theprotocol.client import A2AClient, KeyManager
78
+ from theprotocol.models import Message, TextPart
79
+
80
+ async with A2AClient() as client:
81
+ task_id = await client.initiate_task(agent_card, message, key_manager)
82
+ task = await client.get_task_status(agent_card, task_id, key_manager)
83
+ print(task.state) # TASK_STATE_COMPLETED
84
+ ```
85
+
86
+ The client sends v1.0 wire format and accepts responses from both v1.0 and v0.3 agents.
87
+
88
+ ## Dockerize
89
+
90
+ ```dockerfile
91
+ FROM python:3.11-slim
92
+ WORKDIR /app
93
+ RUN pip install --no-cache-dir theprotocol-sdk[server] uvicorn
94
+ COPY agent.py .
95
+ EXPOSE 9500
96
+ CMD ["uvicorn", "agent:app", "--host", "0.0.0.0", "--port", "9500"]
97
+ ```
98
+
99
+ Register on TheProtocol and your agent gets a permanent DID, OAuth credentials, and a 1,000 AVT genesis grant.
100
+
101
+ ## Protocol Bridges
102
+
103
+ Translate between A2A and other agent protocols:
104
+
105
+ | Bridge | Protocol | Use Case |
106
+ |--------|----------|----------|
107
+ | `GoogleA2ABridge` | Google A2A REST | Expose agents via REST binding (Vertex AI, AgentCore) |
108
+ | `ANPBridge` | Agent Network Protocol | DID:WBA identity linking, Ed25519 auth |
109
+ | `MCPBridge` | Model Context Protocol | Expose agents as MCP tool servers |
110
+ | `ACPBridge` | ACP (deprecated) | Legacy BeeAI compat — use GoogleA2ABridge instead |
111
+
112
+ ```python
113
+ from theprotocol.bridges.google_a2a import GoogleA2ABridge
114
+ from theprotocol.bridges.anp import ANPBridge
115
+ from theprotocol.bridges.mcp import MCPBridge
116
+ ```
117
+
118
+ ## Platform Compatibility
119
+
120
+ Any platform that speaks A2A v1.0 can call your agent directly:
121
+
122
+ - **Google Vertex AI** — native A2A support
123
+ - **AWS Bedrock AgentCore** — native A2A support
124
+ - **LangGraph Cloud** — native A2A support
125
+ - **CrewAI** — native A2A support
126
+ - **Azure AI Foundry** — A2A in preview
127
+
128
+ No additional bridges needed. The SDK's JSON-RPC endpoint is the universal interface.
129
+
130
+ ## MCP Tools
131
+
132
+ For governance, staking, transfers, and discovery — use [MCP tools](https://api.theprotocol.cloud/mcp/sse) (19 tools via Claude Desktop or any MCP client).
133
+
134
+ ## License
135
+
136
+ Apache-2.0
@@ -0,0 +1,95 @@
1
+ # theprotocol-sdk
2
+
3
+ **A2A v1.0** — Build and call AI agents on [TheProtocol](https://theprotocol.cloud). Native protocol bridges for Google A2A, ANP, and MCP.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install theprotocol-sdk # Client only (call agents)
9
+ pip install theprotocol-sdk[server] # + FastAPI router (build agents)
10
+ pip install theprotocol-sdk[anp] # + Ed25519 crypto for ANP DID:WBA
11
+ pip install theprotocol-sdk[all] # Everything
12
+ ```
13
+
14
+ ## Build an Agent
15
+
16
+ ```python
17
+ from theprotocol.agent import BaseA2AAgent, create_a2a_router
18
+ from fastapi import FastAPI
19
+
20
+ class MyAgent(BaseA2AAgent):
21
+ async def handle_task_send(self, task_id, message):
22
+ return "task-1"
23
+ async def handle_task_get(self, task_id): ...
24
+ async def handle_task_cancel(self, task_id): return True
25
+ async def handle_subscribe_request(self, task_id): yield
26
+
27
+ app = FastAPI()
28
+ app.include_router(create_a2a_router(MyAgent()))
29
+ ```
30
+
31
+ Your agent speaks A2A v1.0 out of the box. It accepts both `message/send` (v1.0) and `tasks/send` (v0.3) for backward compatibility.
32
+
33
+ ## Call a Remote Agent
34
+
35
+ ```python
36
+ from theprotocol.client import A2AClient, KeyManager
37
+ from theprotocol.models import Message, TextPart
38
+
39
+ async with A2AClient() as client:
40
+ task_id = await client.initiate_task(agent_card, message, key_manager)
41
+ task = await client.get_task_status(agent_card, task_id, key_manager)
42
+ print(task.state) # TASK_STATE_COMPLETED
43
+ ```
44
+
45
+ The client sends v1.0 wire format and accepts responses from both v1.0 and v0.3 agents.
46
+
47
+ ## Dockerize
48
+
49
+ ```dockerfile
50
+ FROM python:3.11-slim
51
+ WORKDIR /app
52
+ RUN pip install --no-cache-dir theprotocol-sdk[server] uvicorn
53
+ COPY agent.py .
54
+ EXPOSE 9500
55
+ CMD ["uvicorn", "agent:app", "--host", "0.0.0.0", "--port", "9500"]
56
+ ```
57
+
58
+ Register on TheProtocol and your agent gets a permanent DID, OAuth credentials, and a 1,000 AVT genesis grant.
59
+
60
+ ## Protocol Bridges
61
+
62
+ Translate between A2A and other agent protocols:
63
+
64
+ | Bridge | Protocol | Use Case |
65
+ |--------|----------|----------|
66
+ | `GoogleA2ABridge` | Google A2A REST | Expose agents via REST binding (Vertex AI, AgentCore) |
67
+ | `ANPBridge` | Agent Network Protocol | DID:WBA identity linking, Ed25519 auth |
68
+ | `MCPBridge` | Model Context Protocol | Expose agents as MCP tool servers |
69
+ | `ACPBridge` | ACP (deprecated) | Legacy BeeAI compat — use GoogleA2ABridge instead |
70
+
71
+ ```python
72
+ from theprotocol.bridges.google_a2a import GoogleA2ABridge
73
+ from theprotocol.bridges.anp import ANPBridge
74
+ from theprotocol.bridges.mcp import MCPBridge
75
+ ```
76
+
77
+ ## Platform Compatibility
78
+
79
+ Any platform that speaks A2A v1.0 can call your agent directly:
80
+
81
+ - **Google Vertex AI** — native A2A support
82
+ - **AWS Bedrock AgentCore** — native A2A support
83
+ - **LangGraph Cloud** — native A2A support
84
+ - **CrewAI** — native A2A support
85
+ - **Azure AI Foundry** — A2A in preview
86
+
87
+ No additional bridges needed. The SDK's JSON-RPC endpoint is the universal interface.
88
+
89
+ ## MCP Tools
90
+
91
+ For governance, staking, transfers, and discovery — use [MCP tools](https://api.theprotocol.cloud/mcp/sse) (19 tools via Claude Desktop or any MCP client).
92
+
93
+ ## License
94
+
95
+ Apache-2.0
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "theprotocol-sdk"
7
- version = "0.1.0"
7
+ version = "0.2.0"
8
8
  description = "TheProtocol SDK — Build and call A2A agents. Protocol bridges for ACP, ADK, and more."
9
9
  readme = "README.md"
10
10
  license = {text = "Apache-2.0"}
@@ -32,11 +32,14 @@ dependencies = [
32
32
  [project.optional-dependencies]
33
33
  server = ["fastapi>=0.111"]
34
34
  keyring = ["keyring>=24"]
35
- all = ["theprotocol-sdk[server,keyring]"]
35
+ anp = ["cryptography>=42.0"]
36
+ all = ["theprotocol-sdk[server,keyring,anp]"]
36
37
  test = [
37
38
  "pytest>=7.0",
38
39
  "pytest-asyncio>=0.21",
39
40
  "respx>=0.20",
41
+ "cryptography>=42.0",
42
+ "httpx>=0.27",
40
43
  ]
41
44
 
42
45
  [project.urls]
@@ -1,4 +1,5 @@
1
- """FastAPI router factory for A2A agents."""
1
+ """FastAPI router factory for A2A agents.
2
+ Accepts both A2A v1.0 and v0.3 wire formats. Responds in v1.0."""
2
3
 
3
4
  import json
4
5
  import inspect
@@ -18,6 +19,10 @@ from ..models import (
18
19
  TaskCancelParams, TaskCancelResult,
19
20
  TaskStatusUpdateEvent, TaskMessageEvent, TaskArtifactUpdateEvent,
20
21
  )
22
+ from ..models.v1_compat import (
23
+ method_from_v1, message_from_v1, task_to_v1, sse_event_to_v1,
24
+ state_to_v1,
25
+ )
21
26
  from ..exceptions import AgentServerError, TaskNotFoundError
22
27
 
23
28
  logger = logging.getLogger(__name__)
@@ -44,18 +49,22 @@ def _jsonrpc_success(req_id: Union[str, int, None], result: Any) -> Dict:
44
49
 
45
50
 
46
51
  def _format_sse_bytes(event: A2AEvent) -> Optional[bytes]:
47
- event_type = None
48
- if isinstance(event, TaskStatusUpdateEvent):
49
- event_type = "task_status"
50
- elif isinstance(event, TaskMessageEvent):
51
- event_type = "task_message"
52
- elif isinstance(event, TaskArtifactUpdateEvent):
53
- event_type = "task_artifact"
54
- if event_type is None:
52
+ # v1.0 SSE format: field name is the discriminator (statusUpdate, message, artifactUpdate)
53
+ v1_data = sse_event_to_v1(event)
54
+ if v1_data is None:
55
55
  return None
56
56
  try:
57
- json_data = event.model_dump_json(by_alias=True)
58
- return f"event: {event_type}\ndata: {json_data}\n\n".encode("utf-8")
57
+ # Determine event type for SSE event: field
58
+ if "statusUpdate" in v1_data:
59
+ event_type = "status_update"
60
+ elif "message" in v1_data:
61
+ event_type = "message"
62
+ elif "artifactUpdate" in v1_data:
63
+ event_type = "artifact_update"
64
+ else:
65
+ event_type = "event"
66
+ json_str = json.dumps(v1_data)
67
+ return f"event: {event_type}\ndata: {json_str}\n\n".encode("utf-8")
59
68
  except Exception:
60
69
  return None
61
70
 
@@ -121,11 +130,20 @@ def create_a2a_router(
121
130
  return JSONResponse(_jsonrpc_error(None, JSONRPC_INVALID_REQUEST, "Payload must be JSON object"))
122
131
 
123
132
  req_id = payload.get("id")
124
- method = payload.get("method", "")
133
+ raw_method = payload.get("method", "")
134
+ method = method_from_v1(raw_method) # v1.0 → v0.3 method name translation
125
135
  params = payload.get("params") or {}
126
136
  if isinstance(params, list):
127
137
  params = {}
128
138
 
139
+ # v1.0 compat: if params contain v1.0 part format, translate message
140
+ if "message" in params and isinstance(params["message"], dict):
141
+ msg = params["message"]
142
+ parts = msg.get("parts", [])
143
+ if parts and isinstance(parts[0], dict) and "type" not in parts[0]:
144
+ # v1.0 part format (no type field) — translate to v0.3
145
+ params["message"] = message_from_v1(msg).model_dump(mode="json")
146
+
129
147
  if payload.get("jsonrpc") != "2.0":
130
148
  return JSONResponse(_jsonrpc_error(req_id, JSONRPC_INVALID_REQUEST, "'jsonrpc' must be '2.0'"))
131
149
  if not isinstance(method, str) or not method:
@@ -156,17 +174,25 @@ def create_a2a_router(
156
174
  elif method == "tasks/send":
157
175
  vp = TaskSendParams.model_validate(params)
158
176
  task_id = await agent.handle_task_send(task_id=vp.id, message=vp.message)
159
- return JSONResponse(_jsonrpc_success(req_id, TaskSendResult(id=task_id).model_dump(mode='json')))
177
+ # v1.0: try to return full task with v1.0 state format
178
+ try:
179
+ task = await agent.handle_task_get(task_id)
180
+ return JSONResponse(_jsonrpc_success(req_id, task_to_v1(task)))
181
+ except Exception:
182
+ return JSONResponse(_jsonrpc_success(req_id, {"id": task_id}))
160
183
 
161
184
  elif method == "tasks/get":
162
185
  vp = TaskGetParams.model_validate(params)
163
186
  task = await agent.handle_task_get(task_id=vp.id)
164
- return JSONResponse(_jsonrpc_success(req_id, task.model_dump(mode='json', by_alias=True)))
187
+ return JSONResponse(_jsonrpc_success(req_id, task_to_v1(task)))
165
188
 
166
189
  elif method == "tasks/cancel":
167
190
  vp = TaskCancelParams.model_validate(params)
168
191
  ok = await agent.handle_task_cancel(task_id=vp.id)
169
- return JSONResponse(_jsonrpc_success(req_id, TaskCancelResult(success=ok).model_dump(mode='json')))
192
+ result = TaskCancelResult(success=ok).model_dump(mode='json')
193
+ if ok:
194
+ result["status"] = {"state": state_to_v1(TaskState.CANCELED)}
195
+ return JSONResponse(_jsonrpc_success(req_id, result))
170
196
 
171
197
  elif method == "tasks/sendSubscribe":
172
198
  task_id = params.get("id", "")
@@ -16,14 +16,16 @@ from ..exceptions import InvalidStateTransitionError
16
16
  logger = logging.getLogger(__name__)
17
17
 
18
18
  ALLOWED_TRANSITIONS = {
19
- "SUBMITTED": {"WORKING", "CANCELED"},
20
- "WORKING": {"INPUT_REQUIRED", "COMPLETED", "FAILED", "CANCELED"},
21
- "INPUT_REQUIRED": {"WORKING", "CANCELED"},
22
- "COMPLETED": {"COMPLETED"},
23
- "FAILED": {"FAILED"},
24
- "CANCELED": {"CANCELED"},
19
+ TaskState.SUBMITTED.value: {TaskState.WORKING.value, TaskState.CANCELED.value, TaskState.REJECTED.value},
20
+ TaskState.WORKING.value: {TaskState.INPUT_REQUIRED.value, TaskState.COMPLETED.value, TaskState.FAILED.value, TaskState.CANCELED.value},
21
+ TaskState.INPUT_REQUIRED.value: {TaskState.WORKING.value, TaskState.CANCELED.value, TaskState.AUTH_REQUIRED.value},
22
+ TaskState.AUTH_REQUIRED.value: {TaskState.WORKING.value, TaskState.CANCELED.value},
23
+ TaskState.COMPLETED.value: {TaskState.COMPLETED.value},
24
+ TaskState.FAILED.value: {TaskState.FAILED.value},
25
+ TaskState.CANCELED.value: {TaskState.CANCELED.value},
26
+ TaskState.REJECTED.value: {TaskState.REJECTED.value},
25
27
  }
26
- TERMINAL_STATES = {"COMPLETED", "FAILED", "CANCELED"}
28
+ TERMINAL_STATES = {TaskState.COMPLETED.value, TaskState.FAILED.value, TaskState.CANCELED.value, TaskState.REJECTED.value}
27
29
 
28
30
 
29
31
  @dataclass
@@ -1,14 +1,12 @@
1
1
  """
2
2
  ACP (Agent Communication Protocol) ↔ A2A Bridge.
3
3
 
4
+ DEPRECATED: ACP merged into A2A under Linux Foundation (Aug 2025).
5
+ BeeAI now speaks A2A natively. Use GoogleA2ABridge for BeeAI agents.
6
+ This bridge is preserved for backward compatibility with pre-merger ACP deployments.
7
+
4
8
  ACP spec: https://github.com/i-am-bee/acp (IBM/BeeAI)
5
9
  Wire format: REST (HTTP + JSON), no JSON-RPC
6
-
7
- Translates:
8
- ACP POST /runs → A2A tasks/send
9
- ACP GET /runs/{id} → A2A tasks/get
10
- ACP POST /runs/{id}/cancel → A2A tasks/cancel
11
- ACP GET /agents → A2A AgentCard
12
10
  """
13
11
 
14
12
  import datetime
@@ -43,6 +41,8 @@ A2A_TO_ACP_STATE = {
43
41
  TaskState.COMPLETED: "completed",
44
42
  TaskState.FAILED: "failed",
45
43
  TaskState.CANCELED: "cancelled",
44
+ TaskState.REJECTED: "failed", # v1.0: map to ACP failed
45
+ TaskState.AUTH_REQUIRED: "awaiting", # v1.0: map to ACP awaiting
46
46
  }
47
47
 
48
48
 
@@ -26,7 +26,7 @@ from .base import BaseBridge
26
26
 
27
27
  logger = logging.getLogger(__name__)
28
28
 
29
- # Google A2A state (lowercase-hyphen) → TheProtocol A2A state (UPPERCASE)
29
+ # Google A2A REST binding state (lowercase-hyphen) → internal TaskState (v1.0)
30
30
  GOOGLE_TO_TP_STATE = {
31
31
  "submitted": TaskState.SUBMITTED,
32
32
  "working": TaskState.WORKING,
@@ -34,8 +34,8 @@ GOOGLE_TO_TP_STATE = {
34
34
  "failed": TaskState.FAILED,
35
35
  "canceled": TaskState.CANCELED,
36
36
  "input-required": TaskState.INPUT_REQUIRED,
37
- "rejected": TaskState.FAILED, # No direct equivalent
38
- "auth-required": TaskState.INPUT_REQUIRED, # Map to input-required
37
+ "rejected": TaskState.REJECTED,
38
+ "auth-required": TaskState.AUTH_REQUIRED,
39
39
  }
40
40
 
41
41
  TP_TO_GOOGLE_STATE = {
@@ -45,6 +45,8 @@ TP_TO_GOOGLE_STATE = {
45
45
  TaskState.FAILED: "failed",
46
46
  TaskState.CANCELED: "canceled",
47
47
  TaskState.INPUT_REQUIRED: "input-required",
48
+ TaskState.REJECTED: "rejected",
49
+ TaskState.AUTH_REQUIRED: "auth-required",
48
50
  }
49
51
 
50
52
 
@@ -84,8 +86,8 @@ def tp_part_to_google_part(part: Any) -> Dict:
84
86
  def google_message_to_tp(gmsg: Dict) -> Message:
85
87
  """Convert Google A2A Message to TheProtocol Message."""
86
88
  role = gmsg.get("role", "user")
87
- # Google roles: "user", "agent" → TP roles: "user", "assistant"
88
- tp_role = "user" if role == "user" else "assistant"
89
+ # Google roles: "user", "agent" → TP roles: "user", "agent"
90
+ tp_role = "user" if role == "user" else "agent"
89
91
  parts = [google_part_to_tp_part(p) for p in gmsg.get("parts", [])]
90
92
  if not parts:
91
93
  parts = [TextPart(content="")]
@@ -95,7 +97,7 @@ def google_message_to_tp(gmsg: Dict) -> Message:
95
97
 
96
98
  def tp_message_to_google(msg: Message) -> Dict:
97
99
  """Convert TheProtocol Message to Google A2A Message."""
98
- role = "user" if msg.role == "user" else "agent"
100
+ role = "user" if msg.role == "user" else "agent" # v1.0: "agent" (not "assistant")
99
101
  return {
100
102
  "message_id": str(uuid.uuid4()),
101
103
  "role": role,
@@ -1,5 +1,6 @@
1
1
  """
2
2
  A2AClient — Call remote A2A agents via JSON-RPC 2.0 + SSE streaming.
3
+ Sends A2A v1.0 wire format. Accepts both v1.0 and v0.3 responses.
3
4
  """
4
5
 
5
6
  import json
@@ -18,6 +19,7 @@ from ..models import (
18
19
  TaskCancelParams, TaskCancelResult, A2AEvent,
19
20
  TaskStatusUpdateEvent, TaskMessageEvent, TaskArtifactUpdateEvent,
20
21
  )
22
+ from ..models.v1_compat import message_to_v1, state_from_v1, part_from_v1, role_from_v1
21
23
  from ..exceptions import (
22
24
  A2AError, A2AConnectionError, A2AAuthenticationError,
23
25
  A2ARemoteAgentError, A2ATimeoutError, A2AMessageError,
@@ -28,10 +30,14 @@ from .credentials import KeyManager
28
30
  logger = logging.getLogger(__name__)
29
31
 
30
32
  SSE_EVENT_MAP = {
33
+ # v0.3 event types
31
34
  "task_status": TaskStatusUpdateEvent,
32
35
  "task_message": TaskMessageEvent,
33
36
  "task_artifact": TaskArtifactUpdateEvent,
37
+ # v1.0 event types
38
+ "status_update": TaskStatusUpdateEvent,
34
39
  "message": TaskMessageEvent,
40
+ "artifact_update": TaskArtifactUpdateEvent,
35
41
  }
36
42
 
37
43
 
@@ -69,20 +75,24 @@ class A2AClient:
69
75
  async def initiate_task(
70
76
  self, agent_card: AgentCard, message: Message, key_manager: KeyManager
71
77
  ) -> str:
72
- """Start a new task on a remote agent. Returns the task ID."""
78
+ """Start a new task on a remote agent. Returns the task ID.
79
+ Sends v1.0 wire format (message/send with unified parts)."""
73
80
  headers = await self._auth_headers(agent_card, key_manager)
74
- params = TaskSendParams(message=message, id=None)
75
- result = await self._rpc(agent_card.url, "tasks/send", params.model_dump(mode='json', exclude_none=True, by_alias=True), headers)
81
+ v1_params = {"message": message_to_v1(message)}
82
+ result = await self._rpc(agent_card.url, "message/send", v1_params, headers)
83
+ # Accept both v1.0 (result.id or result["id"]) and v0.3 (TaskSendResult)
84
+ if isinstance(result, dict) and "id" in result:
85
+ return result["id"]
76
86
  return TaskSendResult.model_validate(result).id
77
87
 
78
88
  async def send_message(
79
89
  self, agent_card: AgentCard, task_id: str, message: Message, key_manager: KeyManager
80
90
  ) -> bool:
81
- """Send a follow-up message to an existing task."""
91
+ """Send a follow-up message to an existing task.
92
+ Sends v1.0 wire format."""
82
93
  headers = await self._auth_headers(agent_card, key_manager)
83
- params = TaskSendParams(message=message, id=task_id)
84
- result = await self._rpc(agent_card.url, "tasks/send", params.model_dump(mode='json', exclude_none=True, by_alias=True), headers)
85
- TaskSendResult.model_validate(result)
94
+ v1_params = {"id": task_id, "message": message_to_v1(message)}
95
+ result = await self._rpc(agent_card.url, "message/send", v1_params, headers)
86
96
  return True
87
97
 
88
98
  async def get_task_status(
@@ -110,7 +120,8 @@ class A2AClient:
110
120
  headers = await self._auth_headers(agent_card, key_manager)
111
121
  headers["Accept"] = "text/event-stream"
112
122
  url = self._ensure_trailing_slash(str(agent_card.url))
113
- payload = {"jsonrpc": "2.0", "method": "tasks/sendSubscribe", "params": {"id": task_id}, "id": f"sub-{uuid.uuid4()}"}
123
+ # v1.0: message/stream (falls back to tasks/sendSubscribe on v0.3 servers)
124
+ payload = {"jsonrpc": "2.0", "method": "message/stream", "params": {"id": task_id}, "id": f"sub-{uuid.uuid4()}"}
114
125
 
115
126
  async with self._client.stream('POST', url, headers=headers, json=payload, timeout=None) as resp:
116
127
  resp.raise_for_status()
@@ -1,6 +1,6 @@
1
1
  """
2
2
  Pydantic models for the Agent-to-Agent (A2A) protocol.
3
- Based on A2A v0.3.0 specification.
3
+ Native A2A v1.0 specification. Backward compatible with v0.3 input.
4
4
  """
5
5
 
6
6
  import datetime
@@ -10,13 +10,15 @@ from pydantic import BaseModel, Field, HttpUrl, ConfigDict
10
10
 
11
11
 
12
12
  class TaskState(str, Enum):
13
- """Possible states of an A2A task."""
14
- SUBMITTED = "SUBMITTED"
15
- WORKING = "WORKING"
16
- INPUT_REQUIRED = "INPUT_REQUIRED"
17
- COMPLETED = "COMPLETED"
18
- FAILED = "FAILED"
19
- CANCELED = "CANCELED"
13
+ """A2A v1.0 task states."""
14
+ SUBMITTED = "TASK_STATE_SUBMITTED"
15
+ WORKING = "TASK_STATE_WORKING"
16
+ INPUT_REQUIRED = "TASK_STATE_INPUT_REQUIRED"
17
+ COMPLETED = "TASK_STATE_COMPLETED"
18
+ FAILED = "TASK_STATE_FAILED"
19
+ CANCELED = "TASK_STATE_CANCELED"
20
+ REJECTED = "TASK_STATE_REJECTED"
21
+ AUTH_REQUIRED = "TASK_STATE_AUTH_REQUIRED"
20
22
 
21
23
 
22
24
  # ── Message Parts ──
@@ -60,7 +62,7 @@ class Artifact(BaseModel):
60
62
 
61
63
  class Message(BaseModel):
62
64
  model_config = ConfigDict(frozen=True)
63
- role: Literal['user', 'assistant', 'system', 'tool'] = Field(...)
65
+ role: Literal['user', 'agent', 'assistant'] = Field(...) # v1.0: user/agent. assistant accepted for backward compat.
64
66
  parts: List[Part] = Field(..., min_length=1)
65
67
  metadata: Optional[Dict[str, Any]] = None
66
68
 
@@ -1,6 +1,7 @@
1
1
  """
2
2
  Pydantic models for A2A Agent Cards.
3
- Aligned with A2A Protocol v0.3.0 / Draft v1.0.
3
+ Supports A2A v1.0 wire format with v0.3 internal fields.
4
+ Full v1.0 Agent Card schema (supported_interfaces, security_schemes) planned for SDK v2.0.
4
5
  """
5
6
 
6
7
  from typing import List, Optional, Dict, Any, Literal
@@ -0,0 +1,230 @@
1
+ """
2
+ A2A v0.3 Backward Compatibility Layer.
3
+
4
+ Internal models are now native v1.0.
5
+ This module translates v0.3 wire format INPUT to v1.0 internal types,
6
+ so old clients (tasks/send, {type:"text"}, COMPLETED) still work.
7
+ """
8
+
9
+ from typing import Any, Dict, List, Optional, Union
10
+ from .a2a_protocol import (
11
+ TaskState, Message, TextPart, FilePart, DataPart, Part,
12
+ Task, Artifact, TaskStatusUpdateEvent, TaskMessageEvent, TaskArtifactUpdateEvent,
13
+ )
14
+
15
+ # ─── State Translation ──────────────────────────────────────────────────────
16
+
17
+ # Accept both v1.0 and v0.3 state strings → internal TaskState (now v1.0 native)
18
+ STATE_STRING_TO_INTERNAL: Dict[str, TaskState] = {
19
+ # v1.0 values (native)
20
+ "TASK_STATE_SUBMITTED": TaskState.SUBMITTED,
21
+ "TASK_STATE_WORKING": TaskState.WORKING,
22
+ "TASK_STATE_INPUT_REQUIRED": TaskState.INPUT_REQUIRED,
23
+ "TASK_STATE_COMPLETED": TaskState.COMPLETED,
24
+ "TASK_STATE_FAILED": TaskState.FAILED,
25
+ "TASK_STATE_CANCELED": TaskState.CANCELED,
26
+ "TASK_STATE_REJECTED": TaskState.REJECTED,
27
+ "TASK_STATE_AUTH_REQUIRED": TaskState.AUTH_REQUIRED,
28
+ # v0.3 values (backward compat)
29
+ "SUBMITTED": TaskState.SUBMITTED,
30
+ "WORKING": TaskState.WORKING,
31
+ "INPUT_REQUIRED": TaskState.INPUT_REQUIRED,
32
+ "COMPLETED": TaskState.COMPLETED,
33
+ "FAILED": TaskState.FAILED,
34
+ "CANCELED": TaskState.CANCELED,
35
+ "REJECTED": TaskState.REJECTED,
36
+ "AUTH_REQUIRED": TaskState.AUTH_REQUIRED,
37
+ }
38
+
39
+
40
+ def state_from_v1(v1_state: str) -> TaskState:
41
+ """Convert any state string (v0.3 or v1.0) to internal TaskState."""
42
+ return STATE_STRING_TO_INTERNAL.get(v1_state, TaskState.WORKING)
43
+
44
+
45
+ def state_to_v1(state: TaskState) -> str:
46
+ """Return the v1.0 wire value (TaskState is already v1.0 native)."""
47
+ return state.value
48
+
49
+
50
+ # ─── Role Translation ───────────────────────────────────────────────────────
51
+
52
+ # Accept all role variants → internal v1.0 role (user/agent)
53
+ ROLE_TO_INTERNAL = {"user": "user", "agent": "agent", "assistant": "agent", "ROLE_USER": "user", "ROLE_AGENT": "agent", "system": "user", "tool": "user"}
54
+ INTERNAL_ROLE_TO_WIRE = {"user": "user", "agent": "agent", "assistant": "agent"}
55
+
56
+
57
+ def role_from_v1(v1_role: str) -> str:
58
+ """Convert any role string (v0.3 or v1.0) to internal v1.0 role."""
59
+ return ROLE_TO_INTERNAL.get(v1_role, "user")
60
+
61
+
62
+ def role_to_v1(role: str) -> str:
63
+ """Return v1.0 wire role."""
64
+ return INTERNAL_ROLE_TO_WIRE.get(role, "user")
65
+
66
+
67
+ # ─── Part Translation ────────────────────────────────────────────────────────
68
+
69
+ def part_from_v1(v1_part: Dict[str, Any]) -> Part:
70
+ """Convert v1.0 unified Part to internal typed Part.
71
+
72
+ v1.0: {"text": "hello"} or {"url": "...", "media_type": "..."} or {"data": {...}}
73
+ v0.3: TextPart(type="text", content="hello") etc.
74
+ """
75
+ # v1.0 format: field name IS the discriminator
76
+ if "text" in v1_part:
77
+ return TextPart(content=v1_part["text"])
78
+ elif "url" in v1_part:
79
+ return FilePart(
80
+ url=v1_part["url"],
81
+ media_type=v1_part.get("media_type") or v1_part.get("mediaType"),
82
+ filename=v1_part.get("filename"),
83
+ )
84
+ elif "data" in v1_part:
85
+ data = v1_part["data"]
86
+ if not isinstance(data, dict):
87
+ data = {"value": data}
88
+ return DataPart(
89
+ content=data,
90
+ media_type=v1_part.get("media_type") or v1_part.get("mediaType") or "application/json",
91
+ )
92
+ elif "raw" in v1_part:
93
+ # v1.0 raw bytes → store as DataPart with base64
94
+ return DataPart(
95
+ content={"raw_base64": v1_part["raw"], "media_type": v1_part.get("media_type")},
96
+ media_type=v1_part.get("media_type") or "application/octet-stream",
97
+ )
98
+ # Fallback: check v0.3 format (type field)
99
+ elif "type" in v1_part:
100
+ ptype = v1_part["type"]
101
+ if ptype == "text":
102
+ return TextPart(content=v1_part.get("content", ""))
103
+ elif ptype == "file":
104
+ return FilePart(
105
+ url=v1_part.get("url", ""),
106
+ media_type=v1_part.get("mediaType") or v1_part.get("media_type"),
107
+ filename=v1_part.get("filename"),
108
+ )
109
+ elif ptype == "data":
110
+ return DataPart(
111
+ content=v1_part.get("content", {}),
112
+ media_type=v1_part.get("mediaType") or v1_part.get("media_type") or "application/json",
113
+ )
114
+ return TextPart(content="")
115
+
116
+
117
+ def part_to_v1(part: Part) -> Dict[str, Any]:
118
+ """Convert internal typed Part to v1.0 unified Part."""
119
+ if isinstance(part, TextPart):
120
+ return {"text": part.content}
121
+ elif isinstance(part, FilePart):
122
+ r: Dict[str, Any] = {"url": str(part.url)}
123
+ if part.media_type:
124
+ r["media_type"] = part.media_type
125
+ if part.filename:
126
+ r["filename"] = part.filename
127
+ return r
128
+ elif isinstance(part, DataPart):
129
+ r = {"data": part.content}
130
+ if part.media_type and part.media_type != "application/json":
131
+ r["media_type"] = part.media_type
132
+ return r
133
+ return {"text": ""}
134
+
135
+
136
+ # ─── Message Translation ────────────────────────────────────────────────────
137
+
138
+ def message_from_v1(v1_msg: Dict[str, Any]) -> Message:
139
+ """Convert v1.0 message to internal Message."""
140
+ role = role_from_v1(v1_msg.get("role", "user"))
141
+ parts_raw = v1_msg.get("parts", [])
142
+ parts = [part_from_v1(p) for p in parts_raw] if parts_raw else [TextPart(content="")]
143
+ return Message(role=role, parts=parts, metadata=v1_msg.get("metadata"))
144
+
145
+
146
+ def message_to_v1(msg: Message) -> Dict[str, Any]:
147
+ """Convert internal Message to v1.0 wire format."""
148
+ return {
149
+ "role": role_to_v1(msg.role),
150
+ "parts": [part_to_v1(p) for p in msg.parts],
151
+ "metadata": msg.metadata,
152
+ }
153
+
154
+
155
+ # ─── Task Translation ───────────────────────────────────────────────────────
156
+
157
+ def task_to_v1(task: Task) -> Dict[str, Any]:
158
+ """Convert internal Task to v1.0 wire format."""
159
+ return {
160
+ "id": task.id,
161
+ "status": {
162
+ "state": state_to_v1(task.state),
163
+ "timestamp": task.updated_at.isoformat() if task.updated_at else None,
164
+ },
165
+ "history": [message_to_v1(m) for m in task.messages],
166
+ "artifacts": [
167
+ {
168
+ "artifact_id": a.id,
169
+ "name": a.type,
170
+ "parts": [part_to_v1(TextPart(content=str(a.content)))] if a.content else
171
+ [{"url": str(a.url)}] if a.url else [],
172
+ "metadata": a.metadata,
173
+ }
174
+ for a in task.artifacts
175
+ ],
176
+ "metadata": task.metadata,
177
+ }
178
+
179
+
180
+ # ─── SSE Event Translation ──────────────────────────────────────────────────
181
+
182
+ def sse_event_to_v1(event: Any) -> Optional[Dict[str, Any]]:
183
+ """Convert internal SSE event to v1.0 StreamResponse format."""
184
+ if isinstance(event, TaskStatusUpdateEvent):
185
+ return {
186
+ "statusUpdate": {
187
+ "taskId": event.task_id,
188
+ "status": {
189
+ "state": state_to_v1(event.state),
190
+ "timestamp": event.timestamp.isoformat(),
191
+ "message": event.message,
192
+ },
193
+ }
194
+ }
195
+ elif isinstance(event, TaskMessageEvent):
196
+ return {
197
+ "message": message_to_v1(event.message),
198
+ }
199
+ elif isinstance(event, TaskArtifactUpdateEvent):
200
+ return {
201
+ "artifactUpdate": {
202
+ "taskId": event.task_id,
203
+ "artifact": {
204
+ "artifact_id": event.artifact.id,
205
+ "name": event.artifact.type,
206
+ "parts": [part_to_v1(TextPart(content=str(event.artifact.content)))] if event.artifact.content else [],
207
+ },
208
+ "lastChunk": True,
209
+ }
210
+ }
211
+ return None
212
+
213
+
214
+ # ─── Method Name Translation ─────────────────────────────────────────────────
215
+
216
+ # v1.0 method → v0.3 method
217
+ V1_METHOD_TO_INTERNAL = {
218
+ "message/send": "tasks/send",
219
+ "message/stream": "tasks/sendSubscribe",
220
+ "tasks/get": "tasks/get",
221
+ "tasks/cancel": "tasks/cancel",
222
+ # v0.3 names pass through
223
+ "tasks/send": "tasks/send",
224
+ "tasks/sendSubscribe": "tasks/sendSubscribe",
225
+ }
226
+
227
+
228
+ def method_from_v1(method: str) -> str:
229
+ """Convert v1.0 method name to internal v0.3 method name."""
230
+ return V1_METHOD_TO_INTERNAL.get(method, method)
@@ -331,7 +331,7 @@ def test_a2a_task_to_anp_response():
331
331
  )
332
332
  resp = a2a_task_to_anp_response(task)
333
333
  assert resp["task_id"] == "t1"
334
- assert resp["state"] == "completed"
334
+ assert resp["state"] == "task_state_completed"
335
335
  assert resp["messages"][0]["parts"][0]["text"] == "done"
336
336
 
337
337
 
@@ -400,7 +400,7 @@ def test_anp_rpc_tasks_send():
400
400
  assert resp.status_code == 200
401
401
  data = resp.json()
402
402
  assert "result" in data
403
- assert data["result"]["state"] == "completed"
403
+ assert data["result"]["state"] == "task_state_completed"
404
404
  assert any("ANP echo" in m["parts"][0].get("text", "") for m in data["result"]["messages"] if m.get("parts"))
405
405
 
406
406
 
@@ -418,7 +418,7 @@ def test_anp_rpc_tasks_get():
418
418
  "params": {"id": "anp-task-1"},
419
419
  })
420
420
  assert resp.status_code == 200
421
- assert resp.json()["result"]["state"] == "completed"
421
+ assert resp.json()["result"]["state"] == "task_state_completed"
422
422
 
423
423
 
424
424
  def test_anp_rpc_tasks_cancel():
@@ -90,7 +90,7 @@ def test_google_user_message():
90
90
  def test_google_agent_role_maps_to_assistant():
91
91
  gmsg = {"role": "agent", "parts": [{"text": "response"}]}
92
92
  msg = google_message_to_tp(gmsg)
93
- assert msg.role == "assistant"
93
+ assert msg.role == "agent"
94
94
 
95
95
 
96
96
  def test_tp_to_google_message():
@@ -9,8 +9,8 @@ from theprotocol.models import (
9
9
 
10
10
 
11
11
  def test_task_state_enum():
12
- assert TaskState.SUBMITTED == "SUBMITTED"
13
- assert TaskState.COMPLETED == "COMPLETED"
12
+ assert TaskState.SUBMITTED == "TASK_STATE_SUBMITTED"
13
+ assert TaskState.COMPLETED == "TASK_STATE_COMPLETED"
14
14
 
15
15
 
16
16
  def test_text_part():
@@ -1,96 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: theprotocol-sdk
3
- Version: 0.1.0
4
- Summary: TheProtocol SDK — Build and call A2A agents. Protocol bridges for ACP, ADK, and more.
5
- Project-URL: Homepage, https://theprotocol.cloud
6
- Project-URL: Documentation, https://api.theprotocol.cloud/docs
7
- Project-URL: Repository, https://github.com/theprotocol/theprotocol-sdk
8
- Author-email: TheProtocol <sdk@theprotocol.cloud>
9
- License: Apache-2.0
10
- Keywords: a2a,agents,ai,mcp,protocol,theprotocol
11
- Classifier: Development Status :: 3 - Alpha
12
- Classifier: Intended Audience :: Developers
13
- Classifier: License :: OSI Approved :: Apache Software License
14
- Classifier: Programming Language :: Python :: 3
15
- Classifier: Programming Language :: Python :: 3.10
16
- Classifier: Programming Language :: Python :: 3.11
17
- Classifier: Programming Language :: Python :: 3.12
18
- Classifier: Topic :: Software Development :: Libraries
19
- Requires-Python: >=3.10
20
- Requires-Dist: httpx[http2]>=0.27
21
- Requires-Dist: pydantic<3.0,>=2.0
22
- Requires-Dist: python-dotenv>=1.0
23
- Provides-Extra: all
24
- Requires-Dist: fastapi>=0.111; extra == 'all'
25
- Requires-Dist: keyring>=24; extra == 'all'
26
- Provides-Extra: keyring
27
- Requires-Dist: keyring>=24; extra == 'keyring'
28
- Provides-Extra: server
29
- Requires-Dist: fastapi>=0.111; extra == 'server'
30
- Provides-Extra: test
31
- Requires-Dist: pytest-asyncio>=0.21; extra == 'test'
32
- Requires-Dist: pytest>=7.0; extra == 'test'
33
- Requires-Dist: respx>=0.20; extra == 'test'
34
- Description-Content-Type: text/markdown
35
-
36
- # theprotocol-sdk
37
-
38
- Build and call A2A (Agent-to-Agent) agents on [TheProtocol](https://theprotocol.cloud).
39
-
40
- ## Install
41
-
42
- ```bash
43
- pip install theprotocol-sdk # Client only (call agents)
44
- pip install theprotocol-sdk[server] # + FastAPI router (build agents)
45
- pip install theprotocol-sdk[all] # Everything
46
- ```
47
-
48
- ## Build an Agent (10 lines)
49
-
50
- ```python
51
- from theprotocol.agent import BaseA2AAgent, create_a2a_router
52
- from theprotocol.models import Message, TextPart
53
- from fastapi import FastAPI
54
-
55
- class MyAgent(BaseA2AAgent):
56
- async def handle_task_send(self, task_id, message):
57
- return "task-1" # Return task ID
58
- async def handle_task_get(self, task_id):
59
- ... # Return Task object
60
- async def handle_task_cancel(self, task_id):
61
- return True
62
- async def handle_subscribe_request(self, task_id):
63
- yield # SSE events
64
-
65
- app = FastAPI()
66
- app.include_router(create_a2a_router(MyAgent()))
67
- ```
68
-
69
- ## Call a Remote Agent
70
-
71
- ```python
72
- from theprotocol.client import A2AClient, KeyManager
73
- from theprotocol.models import Message, TextPart
74
-
75
- async with A2AClient() as client:
76
- task_id = await client.initiate_task(agent_card, message, key_manager)
77
- task = await client.get_task_status(agent_card, task_id, key_manager)
78
- print(task.state) # COMPLETED
79
- ```
80
-
81
- ## Protocol Bridges (coming soon)
82
-
83
- Translate between A2A and other agent protocols:
84
-
85
- ```python
86
- from theprotocol.bridges.acp import ACPBridge # ACP ↔ A2A
87
- from theprotocol.bridges.adk import ADKBridge # Google ADK ↔ A2A
88
- ```
89
-
90
- ## Registry Operations
91
-
92
- For governance, staking, transfers, and discovery — use [MCP tools](https://api.theprotocol.cloud/docs) instead of SDK functions. 19 tools available via Claude Desktop or any MCP client.
93
-
94
- ## License
95
-
96
- Apache-2.0
@@ -1,61 +0,0 @@
1
- # theprotocol-sdk
2
-
3
- Build and call A2A (Agent-to-Agent) agents on [TheProtocol](https://theprotocol.cloud).
4
-
5
- ## Install
6
-
7
- ```bash
8
- pip install theprotocol-sdk # Client only (call agents)
9
- pip install theprotocol-sdk[server] # + FastAPI router (build agents)
10
- pip install theprotocol-sdk[all] # Everything
11
- ```
12
-
13
- ## Build an Agent (10 lines)
14
-
15
- ```python
16
- from theprotocol.agent import BaseA2AAgent, create_a2a_router
17
- from theprotocol.models import Message, TextPart
18
- from fastapi import FastAPI
19
-
20
- class MyAgent(BaseA2AAgent):
21
- async def handle_task_send(self, task_id, message):
22
- return "task-1" # Return task ID
23
- async def handle_task_get(self, task_id):
24
- ... # Return Task object
25
- async def handle_task_cancel(self, task_id):
26
- return True
27
- async def handle_subscribe_request(self, task_id):
28
- yield # SSE events
29
-
30
- app = FastAPI()
31
- app.include_router(create_a2a_router(MyAgent()))
32
- ```
33
-
34
- ## Call a Remote Agent
35
-
36
- ```python
37
- from theprotocol.client import A2AClient, KeyManager
38
- from theprotocol.models import Message, TextPart
39
-
40
- async with A2AClient() as client:
41
- task_id = await client.initiate_task(agent_card, message, key_manager)
42
- task = await client.get_task_status(agent_card, task_id, key_manager)
43
- print(task.state) # COMPLETED
44
- ```
45
-
46
- ## Protocol Bridges (coming soon)
47
-
48
- Translate between A2A and other agent protocols:
49
-
50
- ```python
51
- from theprotocol.bridges.acp import ACPBridge # ACP ↔ A2A
52
- from theprotocol.bridges.adk import ADKBridge # Google ADK ↔ A2A
53
- ```
54
-
55
- ## Registry Operations
56
-
57
- For governance, staking, transfers, and discovery — use [MCP tools](https://api.theprotocol.cloud/docs) instead of SDK functions. 19 tools available via Claude Desktop or any MCP client.
58
-
59
- ## License
60
-
61
- Apache-2.0