smarta2a 0.3.1__py3-none-any.whl → 0.4.1__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.
Files changed (40) hide show
  1. smarta2a/agent/a2a_agent.py +25 -15
  2. smarta2a/agent/a2a_human.py +56 -0
  3. smarta2a/archive/smart_mcp_client.py +47 -0
  4. smarta2a/archive/subscription_service.py +85 -0
  5. smarta2a/{server → archive}/task_service.py +17 -8
  6. smarta2a/client/a2a_client.py +33 -6
  7. smarta2a/history_update_strategies/rolling_window_strategy.py +16 -0
  8. smarta2a/model_providers/__init__.py +1 -1
  9. smarta2a/model_providers/base_llm_provider.py +3 -3
  10. smarta2a/model_providers/openai_provider.py +126 -89
  11. smarta2a/server/json_rpc_request_processor.py +130 -0
  12. smarta2a/server/nats_client.py +49 -0
  13. smarta2a/server/request_handler.py +667 -0
  14. smarta2a/server/send_task_handler.py +174 -0
  15. smarta2a/server/server.py +124 -726
  16. smarta2a/server/state_manager.py +171 -20
  17. smarta2a/server/webhook_request_processor.py +112 -0
  18. smarta2a/state_stores/base_state_store.py +3 -3
  19. smarta2a/state_stores/inmemory_state_store.py +21 -7
  20. smarta2a/utils/agent_discovery_manager.py +121 -0
  21. smarta2a/utils/prompt_helpers.py +1 -1
  22. smarta2a/{client → utils}/tools_manager.py +39 -8
  23. smarta2a/utils/types.py +17 -3
  24. {smarta2a-0.3.1.dist-info → smarta2a-0.4.1.dist-info}/METADATA +7 -4
  25. smarta2a-0.4.1.dist-info/RECORD +40 -0
  26. smarta2a-0.4.1.dist-info/licenses/LICENSE +35 -0
  27. smarta2a/examples/__init__.py +0 -0
  28. smarta2a/examples/echo_server/__init__.py +0 -0
  29. smarta2a/examples/echo_server/curl.txt +0 -1
  30. smarta2a/examples/echo_server/main.py +0 -39
  31. smarta2a/examples/openai_airbnb_agent/__init__.py +0 -0
  32. smarta2a/examples/openai_airbnb_agent/main.py +0 -33
  33. smarta2a/examples/openai_delegator_agent/__init__.py +0 -0
  34. smarta2a/examples/openai_delegator_agent/main.py +0 -51
  35. smarta2a/examples/openai_weather_agent/__init__.py +0 -0
  36. smarta2a/examples/openai_weather_agent/main.py +0 -32
  37. smarta2a/server/subscription_service.py +0 -109
  38. smarta2a-0.3.1.dist-info/RECORD +0 -42
  39. smarta2a-0.3.1.dist-info/licenses/LICENSE +0 -21
  40. {smarta2a-0.3.1.dist-info → smarta2a-0.4.1.dist-info}/WHEEL +0 -0
@@ -0,0 +1,35 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Siddharth Sameer Ambegaonkar
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the “Software”), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ ---
24
+
25
+ **Commons Clause License Condition v1.0**
26
+
27
+ The Software is provided under the MIT License, as supplemented by the following condition:
28
+
29
+ The grant of rights under the MIT License does not include, and the following condition is added to the MIT License:
30
+
31
+ *Notwithstanding any other provision of the License, the License does not grant the right to Sell the Software.*
32
+
33
+ For purposes of the foregoing, “Sell” means practicing any or all of the rights granted to you under the License to provide to third parties, for a fee or other consideration (including without limitation fees for hosting or consulting/support services related to the Software), a product or service whose value derives, entirely or substantially, from the functionality of the Software. Any licensee who provides any such product or service is deemed to be in violation of this condition and may not exercise any of the rights granted under the License.
34
+
35
+ This condition is intended to supplement, not replace, the License.
File without changes
File without changes
@@ -1 +0,0 @@
1
- curl -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": “tasks/send”, "params": {"id": "test-task-1","message": {"role": "user","parts": [{"type": "text", "text": "Test message"}]}} }' http://localhost:8000/
@@ -1,39 +0,0 @@
1
- from smarta2a.server import SmartA2A
2
- from smarta2a.utils.types import A2AResponse, TaskStatus, TaskState, TextPart, FileContent, FilePart
3
- from smarta2a.state_stores.inmemory_state_store import InMemoryStateStore
4
-
5
- state_store = InMemoryStateStore()
6
- app = SmartA2A("EchoServer", state_store=state_store)
7
-
8
- @app.on_send_task()
9
- async def handle_task(request, state):
10
- """Echo the input text back as a completed task"""
11
- input_text = request.content[0].text
12
- #return f"Response to task: {input_text}"
13
- return A2AResponse(
14
- content=[TextPart(type="text", text="Response to task: " + input_text), FilePart(type="file", file=FileContent(name="test.txt", bytes="test"))],
15
- status="working"
16
- )
17
-
18
- @app.on_send_subscribe_task()
19
- async def handle_subscribe_task(request, state):
20
- """Subscribe to the task"""
21
- input_text = request.content[0].text
22
- yield f"First response to the task: {input_text}"
23
- yield f"Second response to the task: {input_text}"
24
- yield f"Third response to the task: {input_text}"
25
-
26
- @app.task_get()
27
- def handle_get_task(request):
28
- """Get the task"""
29
- return f"Task: {request.id}"
30
-
31
- @app.task_cancel()
32
- def handle_cancel_task(request):
33
- """Cancel the task"""
34
- return f"Task cancelled: {request.id}"
35
-
36
-
37
-
38
-
39
-
File without changes
@@ -1,33 +0,0 @@
1
- # Imports
2
- from dotenv import load_dotenv
3
- import os
4
- import uvicorn
5
- from smarta2a.agent.a2a_agent import A2AAgent
6
- from smarta2a.model_providers.openai_provider import OpenAIProvider
7
-
8
-
9
-
10
- # Load environment variables from the .env file
11
- load_dotenv()
12
-
13
- # Fetch the value using os.getenv
14
- api_key = os.getenv("OPENAI_API_KEY")
15
-
16
-
17
- openai_provider = OpenAIProvider(
18
- api_key=api_key,
19
- model="gpt-4o-mini",
20
- base_system_prompt="You are a cheerful assistant that specialises in helping with airbnb related queries",
21
- mcp_server_urls_or_paths=["@openbnb/mcp-server-airbnb --ignore-robots-txt"]
22
- )
23
- # /Users/apple/.npm/_npx/1629930a2e066932/node_modules/@openbnb/mcp-server-airbnb/dist/index.js
24
-
25
- # Create the agent
26
- agent = A2AAgent(
27
- name="openai_agent",
28
- model_provider=openai_provider,
29
- )
30
-
31
- # Entry point
32
- if __name__ == "__main__":
33
- uvicorn.run(agent.get_app(), host="0.0.0.0", port=8002)
File without changes
@@ -1,51 +0,0 @@
1
- # Imports
2
- from dotenv import load_dotenv
3
- import os
4
- import uvicorn
5
- from smarta2a.agent.a2a_agent import A2AAgent
6
- from smarta2a.model_providers.openai_provider import OpenAIProvider
7
- from smarta2a.utils.types import AgentCard, AgentCapabilities, AgentSkill
8
-
9
-
10
- # Load environment variables from the .env file
11
- load_dotenv()
12
-
13
- # Fetch the value using os.getenv
14
- api_key = os.getenv("OPENAI_API_KEY")
15
-
16
- weather_agent_card = AgentCard(
17
- name="weather_agent",
18
- description="A weather agent that can help with weather related queries",
19
- version="0.1.0",
20
- url="http://localhost:8000",
21
- capabilities=AgentCapabilities(),
22
- skills=[AgentSkill(id="weather_forecasting", name="Weather Forecasting", description="Can get weather forecast for a given latitude and longitude"),
23
- AgentSkill(id="weather_alerts", name="Weather Alerts", description="Can get weather alerts for a US state")]
24
- )
25
-
26
- airbnb_agent_card = AgentCard(
27
- name="airbnb_agent",
28
- description="An airbnb agent that can help with airbnb related queries",
29
- version="0.1.0",
30
- url="http://localhost:8002",
31
- capabilities=AgentCapabilities(),
32
- skills=[AgentSkill(id="search_listings", name="Search listings", description="Search for Airbnb listings by location, dates, guests, and more"),
33
- AgentSkill(id="get_listing_details", name="Get listing details", description="Get detailed information about a specific Airbnb listing by listing id, dates, guests, and more")]
34
- )
35
-
36
-
37
- openai_provider = OpenAIProvider(
38
- api_key=api_key,
39
- model="gpt-4o-mini",
40
- agent_cards=[weather_agent_card, airbnb_agent_card]
41
- )
42
-
43
- # Create the agent
44
- agent = A2AAgent(
45
- name="openai_agent",
46
- model_provider=openai_provider,
47
- )
48
-
49
- # Entry point
50
- if __name__ == "__main__":
51
- uvicorn.run(agent.get_app(), host="0.0.0.0", port=8001)
File without changes
@@ -1,32 +0,0 @@
1
- # Imports
2
- from dotenv import load_dotenv
3
- import os
4
- import uvicorn
5
- from smarta2a.agent.a2a_agent import A2AAgent
6
- from smarta2a.model_providers.openai_provider import OpenAIProvider
7
-
8
-
9
-
10
- # Load environment variables from the .env file
11
- load_dotenv()
12
-
13
- # Fetch the value using os.getenv
14
- api_key = os.getenv("OPENAI_API_KEY")
15
-
16
-
17
- openai_provider = OpenAIProvider(
18
- api_key=api_key,
19
- model="gpt-4o-mini",
20
- base_system_prompt="You are a cheerful assistant that specialises in helping with weather related queries",
21
- mcp_server_urls_or_paths=["/Users/apple/Desktop/Code/weather/weather.py"],
22
- )
23
-
24
- # Create the agent
25
- agent = A2AAgent(
26
- name="openai_agent",
27
- model_provider=openai_provider,
28
- )
29
-
30
- # Entry point
31
- if __name__ == "__main__":
32
- uvicorn.run(agent.get_app(), host="0.0.0.0", port=8000)
@@ -1,109 +0,0 @@
1
- # Library imports
2
- from typing import Optional, List, Dict, Any, AsyncGenerator, Union
3
- from datetime import datetime
4
- from collections import defaultdict
5
- from uuid import uuid4
6
- from fastapi.responses import StreamingResponse
7
- from sse_starlette.sse import EventSourceResponse
8
-
9
- # Local imports
10
- from smarta2a.server.handler_registry import HandlerRegistry
11
- from smarta2a.server.state_manager import StateManager
12
- from smarta2a.utils.types import (
13
- Message, StateData, SendTaskStreamingRequest, SendTaskStreamingResponse,
14
- TaskSendParams, A2AStatus, A2AStreamResponse, TaskStatusUpdateEvent,
15
- TaskStatus, TaskState, TaskArtifactUpdateEvent, Artifact, TextPart,
16
- FilePart, DataPart, FileContent, MethodNotFoundError, TaskNotFoundError,
17
- InternalError
18
- )
19
-
20
- class SubscriptionService:
21
- def __init__(self, registry: HandlerRegistry, state_mgr: StateManager):
22
- self.registry = registry
23
- self.state_mgr = state_mgr
24
-
25
- async def subscribe(self, request: SendTaskStreamingRequest, state: Optional[StateData]) -> StreamingResponse:
26
- handler = self.registry.get_subscription("tasks/sendSubscribe")
27
- if not handler:
28
- err = SendTaskStreamingResponse(jsonrpc="2.0", id=request.id, error=MethodNotFoundError()).model_dump_json()
29
- return EventSourceResponse(err)
30
-
31
- session_id = state.sessionId if state else request.params.sessionId or str(uuid4())
32
- history = state.history.copy() if state else [request.params.message]
33
- metadata = state.metadata.copy() if state else (request.params.metadata or {})
34
-
35
- async def event_stream():
36
- try:
37
- events = handler(request, state) if state else handler(request)
38
- async for ev in self._normalize(request.params, events, history.copy(), metadata.copy(), session_id):
39
- yield f"data: {ev}\n\n"
40
- except Exception as e:
41
- err = TaskNotFoundError() if 'not found' in str(e).lower() else InternalError(data=str(e))
42
- msg = SendTaskStreamingResponse(jsonrpc="2.0", id=request.id, error=err).model_dump_json()
43
- yield f"data: {msg}\n\n"
44
-
45
- return StreamingResponse(event_stream(), media_type="text/event-stream; charset=utf-8")
46
-
47
- async def _normalize(
48
- self,
49
- params: TaskSendParams,
50
- events: AsyncGenerator,
51
- history: List[Message],
52
- metadata: Dict[str, Any],
53
- session_id: str
54
- ) -> AsyncGenerator[str, None]:
55
- artifact_state = defaultdict(lambda: {"index": 0, "last_chunk": False})
56
- async for item in events:
57
- if isinstance(item, SendTaskStreamingResponse):
58
- yield item.model_dump_json()
59
- continue
60
-
61
- if isinstance(item, A2AStatus):
62
- te = TaskStatusUpdateEvent(
63
- id=params.id,
64
- status=TaskStatus(state=TaskState(item.status), timestamp=datetime.now()),
65
- final=item.final or (item.status.lower() == TaskState.COMPLETED),
66
- metadata=item.metadata
67
- )
68
- yield SendTaskStreamingResponse(jsonrpc="2.0", id=params.id, result=te).model_dump_json()
69
- continue
70
-
71
- content_item = item
72
- if not isinstance(item, A2AStreamResponse):
73
- content_item = A2AStreamResponse(content=item)
74
-
75
- parts: List[Union[TextPart, FilePart, DataPart]] = []
76
- cont = content_item.content
77
- if isinstance(cont, str): parts.append(TextPart(text=cont))
78
- elif isinstance(cont, bytes): parts.append(FilePart(file=FileContent(bytes=cont)))
79
- elif isinstance(cont, (TextPart, FilePart, DataPart)): parts.append(cont)
80
- elif isinstance(cont, Artifact): parts.extend(cont.parts)
81
- elif isinstance(cont, list):
82
- for elem in cont:
83
- if isinstance(elem, str): parts.append(TextPart(text=elem))
84
- elif isinstance(elem, (TextPart, FilePart, DataPart)): parts.append(elem)
85
- elif isinstance(elem, Artifact): parts.extend(elem.parts)
86
-
87
- idx = content_item.index
88
- state = artifact_state[idx]
89
- evt = TaskArtifactUpdateEvent(
90
- id=params.id,
91
- artifact=Artifact(
92
- parts=parts,
93
- index=idx,
94
- append=content_item.append or (state["index"] == idx),
95
- lastChunk=content_item.final or state["last_chunk"],
96
- metadata=content_item.metadata
97
- )
98
- )
99
- if content_item.final:
100
- state["last_chunk"] = True
101
- state["index"] += 1
102
-
103
- agent_msg = Message(role="agent", parts=evt.artifact.parts, metadata=evt.artifact.metadata)
104
- new_hist = self.state_mgr.strategy.update_history(history, [agent_msg])
105
- metadata = {**metadata, **(evt.artifact.metadata or {})}
106
- self.state_mgr.update(StateData(session_id, new_hist, metadata))
107
- history = new_hist
108
-
109
- yield SendTaskStreamingResponse(jsonrpc="2.0", id=params.id, result=evt).model_dump_json()
@@ -1,42 +0,0 @@
1
- smarta2a/__init__.py,sha256=T_EECYqWrxshix0FbgUv22zlKRX22HFU-HKXcYTOb3w,175
2
- smarta2a/agent/a2a_agent.py,sha256=zk6ZZtq4HrnKMDuZKnQPeKploV1IGXSZ1WmVPdIZAeY,1586
3
- smarta2a/agent/a2a_mcp_server.py,sha256=X_mxkgYgCA_dSNtCvs0rSlOoWYc-8d3Qyxv0e-a7NKY,1015
4
- smarta2a/archive/smart_mcp_client.py,sha256=NjyMR8xvrhy0-iFamj2XQnKp5qzkkFya7EQY4Kzl5dw,2574
5
- smarta2a/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- smarta2a/client/a2a_client.py,sha256=5WIincf_6Nqh61_x3f0NYUSLXlGztlnzUpo0m6ch_j0,11038
7
- smarta2a/client/mcp_client.py,sha256=PM4D1CgOycxK5kJEJTGpKq0eXFjZ69-2720TuRUkyGc,3627
8
- smarta2a/client/tools_manager.py,sha256=JDD2lqcDe70JVHlPoRU5oHzUYcZUkH5QzjtEkWYfrrA,2970
9
- smarta2a/examples/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- smarta2a/examples/echo_server/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
- smarta2a/examples/echo_server/curl.txt,sha256=MYBbAgrU3PeCsksrrBc0xgaTD5Dro7xiTsCUz8Ocvt8,247
12
- smarta2a/examples/echo_server/main.py,sha256=i_XLQbRAv6XxkJX0vLV3CxHNANG41yAxgP-V-b4Iysc,1261
13
- smarta2a/examples/openai_airbnb_agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
- smarta2a/examples/openai_airbnb_agent/main.py,sha256=CaIYi-LQD32Rm_BsXI7sLfFqx_kMpl0k8zCayFB4068,892
15
- smarta2a/examples/openai_delegator_agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
- smarta2a/examples/openai_delegator_agent/main.py,sha256=zP6YOMxTBLfMtrZOPwPLQhSVodULbDkCCAy4pB4ltto,1805
17
- smarta2a/examples/openai_weather_agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
- smarta2a/examples/openai_weather_agent/main.py,sha256=o8QqLu9JC3zRXRPGWhYdd7mVl3ooys5UwkPCPv2JHmQ,796
19
- smarta2a/history_update_strategies/__init__.py,sha256=x5WtiE9rG5ze8d8hA6E6wJOciBhWHa_ZgGgoIAZcXEQ,213
20
- smarta2a/history_update_strategies/append_strategy.py,sha256=j7Qbhs69Wwr-HBLB8GJ3-mEPaBSHiBV2xz9ZZi86k2w,312
21
- smarta2a/history_update_strategies/history_update_strategy.py,sha256=n2sfIGu8ztKI7gJTwRX26m4tZr28B8Xdhrk6RlBFlI8,373
22
- smarta2a/model_providers/__init__.py,sha256=2FhblAUiwG9Xv27yEpuuz0VrnIZ-rlpgIuPwm-UIX5U,147
23
- smarta2a/model_providers/base_llm_provider.py,sha256=6QjTUjYEnvHZji4_VWZz6CvLYKLyutxRUfIeH3seQg4,424
24
- smarta2a/model_providers/openai_provider.py,sha256=yTiBVJfEFOTSQsVj8mNx5qVZWvOvslYvw7TQ0tLLf7U,10870
25
- smarta2a/server/__init__.py,sha256=f2X454Ll4vJc02V4JLJHTN-h8u0TBm4d_FkiO4t686U,53
26
- smarta2a/server/handler_registry.py,sha256=OVRG5dTvxB7qUNXgsqWxVNxIyRljUShSYxb1gtbi5XM,820
27
- smarta2a/server/server.py,sha256=WJGQVOprF2Hh_036eW4GJrRr3fLC0S_B3dAfONCgZrs,32180
28
- smarta2a/server/state_manager.py,sha256=Uc4BNr2kQvi7MAEh3CmHsKV2bP-Q8bYbGADQ35iHmZo,1350
29
- smarta2a/server/subscription_service.py,sha256=fWqNNY0xmRksc_SZl4xt5fOPtZTQacfzou1-RMyaEd4,5188
30
- smarta2a/server/task_service.py,sha256=TXVnFeS9ofAqH2z_7BOfk5uDmoZKv9irHHQSIuurI70,7650
31
- smarta2a/state_stores/__init__.py,sha256=vafxAqpwvag_cYFH2XKGk3DPmJIWJr4Ioey30yLFkVQ,220
32
- smarta2a/state_stores/base_state_store.py,sha256=LFI-LThPLf7M9z_CcXWCswajxMAtMx9tMFFVhZU0fM8,521
33
- smarta2a/state_stores/inmemory_state_store.py,sha256=MgFGc7HxccrBxEqhVqKJ3bV-RnV1koU6iJd5m3rhhjA,682
34
- smarta2a/utils/__init__.py,sha256=5db5VgDGgbMUGEF-xuyaC3qrgRQkUE9WAITkFSiNqSA,702
35
- smarta2a/utils/prompt_helpers.py,sha256=jLETieoeBJLQXcGzwFeoKT5b2pS4tJ5770lPLImtKLo,1439
36
- smarta2a/utils/task_builder.py,sha256=wqSyfVHNTaXuGESu09dhlaDi7D007gcN3-8tH-nPQ40,5159
37
- smarta2a/utils/task_request_builder.py,sha256=6cOGOqj2Rg43xWM03GRJQzlIZHBptsMCJRp7oD-TDAQ,3362
38
- smarta2a/utils/types.py,sha256=h4bQw_RmeJCDq022aEd9XJZhNpJOydhV40tqERvLeQE,13190
39
- smarta2a-0.3.1.dist-info/METADATA,sha256=Z2Z6w1tr9o6bkmxplJ4OeG5CZ7RAk9RIjkf1CYnuwfY,13005
40
- smarta2a-0.3.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
41
- smarta2a-0.3.1.dist-info/licenses/LICENSE,sha256=ECMEVHuFkvpEmH-_A9HSxs_UnnsUqpCkiAYNHPCf2z0,1078
42
- smarta2a-0.3.1.dist-info/RECORD,,
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2025 Siddharth Ambegaonkar
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.