dais-sdk 0.6.1__py3-none-any.whl → 0.6.3__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.
@@ -1,5 +1,5 @@
1
1
  from contextlib import AsyncExitStack
2
- from typing import Any
2
+ from typing import Any, override
3
3
  from mcp import ClientSession, StdioServerParameters as StdioServerParams
4
4
  from mcp.client.stdio import stdio_client
5
5
  from .base_mcp_client import McpClient, Tool, ToolResult, McpSessionNotEstablishedError
@@ -14,9 +14,11 @@ class LocalMcpClient(McpClient):
14
14
  self._exit_stack: AsyncExitStack | None = None
15
15
 
16
16
  @property
17
+ @override
17
18
  def name(self) -> str:
18
19
  return self._name
19
20
 
21
+ @override
20
22
  async def connect(self):
21
23
  self._exit_stack = AsyncExitStack()
22
24
 
@@ -32,6 +34,7 @@ class LocalMcpClient(McpClient):
32
34
  await self.disconnect()
33
35
  raise
34
36
 
37
+ @override
35
38
  async def list_tools(self) -> list[Tool]:
36
39
  if not self._session:
37
40
  raise McpSessionNotEstablishedError()
@@ -39,6 +42,7 @@ class LocalMcpClient(McpClient):
39
42
  result = await self._session.list_tools()
40
43
  return result.tools
41
44
 
45
+ @override
42
46
  async def call_tool(
43
47
  self, tool_name: str, arguments: dict[str, Any] | None = None
44
48
  ) -> ToolResult:
@@ -48,6 +52,7 @@ class LocalMcpClient(McpClient):
48
52
  response = await self._session.call_tool(tool_name, arguments)
49
53
  return ToolResult(response.isError, response.content)
50
54
 
55
+ @override
51
56
  async def disconnect(self) -> None:
52
57
  if self._exit_stack:
53
58
  await self._exit_stack.aclose()
@@ -2,7 +2,7 @@ import httpx
2
2
  import webbrowser
3
3
  from dataclasses import dataclass
4
4
  from contextlib import AsyncExitStack
5
- from typing import Any, NamedTuple
5
+ from typing import Any, NamedTuple, override
6
6
  from mcp import ClientSession
7
7
  from mcp.client.auth import OAuthClientProvider
8
8
  from mcp.client.streamable_http import streamable_http_client
@@ -49,6 +49,7 @@ class RemoteMcpClient(McpClient):
49
49
  self._params.oauth_params.oauth_token_storage = storage
50
50
 
51
51
  @property
52
+ @override
52
53
  def name(self) -> str:
53
54
  return self._name
54
55
 
@@ -105,6 +106,7 @@ class RemoteMcpClient(McpClient):
105
106
  raise ValueError("OAuth context not initialized")
106
107
  return await self._oauth_context.server.wait_for_code()
107
108
 
109
+ @override
108
110
  async def connect(self):
109
111
  self._exit_stack = AsyncExitStack()
110
112
  if self._oauth_context:
@@ -126,6 +128,7 @@ class RemoteMcpClient(McpClient):
126
128
  await self.disconnect()
127
129
  raise
128
130
 
131
+ @override
129
132
  async def list_tools(self) -> list[Tool]:
130
133
  if not self._session:
131
134
  raise McpSessionNotEstablishedError()
@@ -133,6 +136,7 @@ class RemoteMcpClient(McpClient):
133
136
  result = await self._session.list_tools()
134
137
  return result.tools
135
138
 
139
+ @override
136
140
  async def call_tool(
137
141
  self, tool_name: str, arguments: dict[str, Any] | None = None
138
142
  ) -> ToolResult:
@@ -142,6 +146,7 @@ class RemoteMcpClient(McpClient):
142
146
  response = await self._session.call_tool(tool_name, arguments)
143
147
  return ToolResult(response.isError, response.content)
144
148
 
149
+ @override
145
150
  async def disconnect(self):
146
151
  try:
147
152
  if self._exit_stack:
dais_sdk/param_parser.py CHANGED
@@ -1,4 +1,3 @@
1
- from __future__ import annotations
2
1
  from typing import Any, TYPE_CHECKING
3
2
  from litellm.types.utils import LlmProviders
4
3
  from .tool.prepare import prepare_tools
dais_sdk/tool/execute.py CHANGED
@@ -1,36 +1,41 @@
1
1
  import asyncio
2
2
  import json
3
+ import inspect
3
4
  from functools import singledispatch
4
5
  from typing import Any, assert_never, Callable, cast
5
6
  from types import FunctionType, MethodType
6
7
  from ..types.tool import ToolDef, ToolLike
7
- from ..types.exceptions import ToolDoesNotExistError, ToolArgumentDecodeError, ToolExecutionError
8
+ from ..types.exceptions import LlmToolException
8
9
  from ..logger import logger
9
10
 
10
- class ToolExceptionHandlerManager:
11
- ToolException = ToolDoesNotExistError | ToolArgumentDecodeError | ToolExecutionError
12
- Handler = Callable[[ToolException], str]
11
+ type ExceptionHandler[E: LlmToolException] = Callable[[E], str]
13
12
 
13
+ class ToolExceptionHandlerManager:
14
14
  def __init__(self):
15
- self._handlers: dict[
16
- type[ToolExceptionHandlerManager.ToolException],
17
- ToolExceptionHandlerManager.Handler
18
- ] = {}
15
+ self._handlers: dict[type[LlmToolException], ExceptionHandler[Any]] = {}
19
16
 
20
- def register(self, exception_type: type[ToolException]):
21
- def decorator(handler: ToolExceptionHandlerManager.Handler) -> ToolExceptionHandlerManager.Handler:
17
+ def register[E: LlmToolException](self, exception_type: type[E]):
18
+ def decorator(handler: ExceptionHandler[E]) -> ExceptionHandler[E]:
22
19
  self.set_handler(exception_type, handler)
23
20
  return handler
24
21
  return decorator
25
22
 
26
- def set_handler(self, exception_type: type[ToolException], handler: Handler):
23
+ def set_handler[E: LlmToolException](self, exception_type: type[E], handler: ExceptionHandler[E]):
27
24
  self._handlers[exception_type] = handler
28
25
 
29
- def get_handler(self, exception_type: type[ToolException]) -> Handler | None:
26
+ def get_handler[E: LlmToolException](self, exception_type: type[E]) -> ExceptionHandler[E] | None:
30
27
  return self._handlers.get(exception_type)
31
28
 
32
- def handle(self, e: ToolException) -> str:
33
- handler = self.get_handler(type(e))
29
+ def handle(self, e: LlmToolException) -> str:
30
+ def find_best_handler[E: LlmToolException](exc_type: type[E]) -> ExceptionHandler[E] | None:
31
+ for cls in exc_type.__mro__:
32
+ if cls in self._handlers:
33
+ return self._handlers[cls]
34
+ return None
35
+
36
+ # Searches the MRO of the exception type to make sure the subclasses of
37
+ # the registered exception type can also be handled.
38
+ handler = find_best_handler(type(e))
34
39
  if handler is None:
35
40
  logger.warning(f"Unhandled tool exception: {type(e).__name__}", exc_info=e)
36
41
  return f"Unhandled tool exception | {type(e).__name__}: {e}"
@@ -66,7 +71,7 @@ def execute_tool_sync(tool: ToolLike, arguments: str | dict) -> str:
66
71
  def _(toolfn: Callable, arguments: str | dict) -> str:
67
72
  arguments = _arguments_normalizer(arguments)
68
73
  result = (asyncio.run(toolfn(**arguments))
69
- if asyncio.iscoroutinefunction(toolfn)
74
+ if inspect.iscoroutinefunction(toolfn)
70
75
  else toolfn(**arguments))
71
76
  return _result_normalizer(result)
72
77
 
@@ -74,7 +79,7 @@ def _(toolfn: Callable, arguments: str | dict) -> str:
74
79
  def _(tooldef: ToolDef, arguments: str | dict) -> str:
75
80
  arguments = _arguments_normalizer(arguments)
76
81
  result = (asyncio.run(tooldef.execute(**arguments))
77
- if asyncio.iscoroutinefunction(tooldef.execute)
82
+ if inspect.iscoroutinefunction(tooldef.execute)
78
83
  else tooldef.execute(**arguments))
79
84
  return _result_normalizer(result)
80
85
 
@@ -92,7 +97,7 @@ async def execute_tool(tool: ToolLike, arguments: str | dict) -> str:
92
97
  async def _(toolfn: Callable, arguments: str | dict) -> str:
93
98
  arguments = _arguments_normalizer(arguments)
94
99
  result = (await toolfn(**arguments)
95
- if asyncio.iscoroutinefunction(toolfn)
100
+ if inspect.iscoroutinefunction(toolfn)
96
101
  else toolfn(**arguments))
97
102
  return _result_normalizer(result)
98
103
 
@@ -100,6 +105,6 @@ async def _(toolfn: Callable, arguments: str | dict) -> str:
100
105
  async def _(tooldef: ToolDef, arguments: str | dict) -> str:
101
106
  arguments = _arguments_normalizer(arguments)
102
107
  result = (await tooldef.execute(**arguments)
103
- if asyncio.iscoroutinefunction(tooldef.execute)
108
+ if inspect.iscoroutinefunction(tooldef.execute)
104
109
  else tooldef.execute(**arguments))
105
110
  return _result_normalizer(result)
@@ -1,4 +1,5 @@
1
1
  from dataclasses import replace
2
+ from typing import override
2
3
  from mcp.types import TextContent, ImageContent, AudioContent, ResourceLink, EmbeddedResource, TextResourceContents, BlobResourceContents
3
4
  from .toolset import Toolset
4
5
  from ...mcp_client.base_mcp_client import McpClient, Tool, ToolResult
@@ -72,9 +73,11 @@ class McpToolset(Toolset):
72
73
  for tool in mcp_tools]
73
74
 
74
75
  @property
76
+ @override
75
77
  def name(self) -> str:
76
78
  return self._client.name
77
79
 
80
+ @override
78
81
  def get_tools(self, namespaced_tool_name: bool = True) -> list[ToolDef]:
79
82
  if self._tools_cache is None:
80
83
  raise RuntimeError(f"Not connected to MCP server. Call await {self.__class__.__name__}(...).connect() first")
@@ -1,17 +1,17 @@
1
1
  import inspect
2
- from typing import Any, Callable, TypeVar
2
+ from typing import Any, Callable, override
3
3
  from .toolset import Toolset
4
4
  from ...types.tool import ToolDef
5
5
 
6
- F = TypeVar("F", bound=Callable[..., Any])
7
6
  TOOL_FLAG = "__is_tool__"
8
7
 
9
- def python_tool(func: F) -> F:
8
+ def python_tool[F: Callable[..., Any]](func: F) -> F:
10
9
  setattr(func, TOOL_FLAG, True)
11
10
  return func
12
11
 
13
12
  class PythonToolset(Toolset):
14
13
  @property
14
+ @override
15
15
  def name(self) -> str:
16
16
  """
17
17
  Since the usage of PythonToolset is to inherit and define methods as tools,
@@ -19,6 +19,7 @@ class PythonToolset(Toolset):
19
19
  """
20
20
  return self.__class__.__name__
21
21
 
22
+ @override
22
23
  def get_tools(self, namespaced_tool_name: bool = True) -> list[ToolDef]:
23
24
  result = []
24
25
  for _, method in inspect.getmembers(self, predicate=inspect.ismethod):
@@ -1,7 +1,6 @@
1
- from __future__ import annotations
2
1
  import json
3
2
  from typing import TYPE_CHECKING
4
- from litellm.exceptions import (
3
+ from litellm.exceptions import (
5
4
  AuthenticationError,
6
5
  PermissionDeniedError,
7
6
  RateLimitError,
dais_sdk/types/message.py CHANGED
@@ -2,7 +2,7 @@ import json
2
2
  import dataclasses
3
3
  import uuid
4
4
  from abc import ABC, abstractmethod
5
- from typing import Any, Literal, NamedTuple, cast
5
+ from typing import Any, Literal, cast
6
6
  from pydantic import BaseModel, ConfigDict, Field, field_validator
7
7
  from litellm.types.utils import (
8
8
  Message as LiteLlmMessage,
@@ -186,7 +186,7 @@ class ToolCallChunk:
186
186
  arguments: str
187
187
  index: int
188
188
 
189
- MessageChunk = TextChunk | UsageChunk | ReasoningChunk | AudioChunk | ImageChunk | ToolCallChunk
189
+ type MessageChunk = TextChunk | UsageChunk | ReasoningChunk | AudioChunk | ImageChunk | ToolCallChunk
190
190
 
191
191
  def openai_chunk_normalizer(
192
192
  chunk: LiteLlmModelResponseStream
dais_sdk/types/tool.py CHANGED
@@ -3,7 +3,7 @@ from collections.abc import Callable
3
3
  from typing import Any, Awaitable
4
4
  from ..logger import logger
5
5
 
6
- ToolFn = Callable[..., Any] | Callable[..., Awaitable[Any]]
6
+ type ToolFn = Callable[..., Any] | Callable[..., Awaitable[Any]]
7
7
 
8
8
  """
9
9
  RawToolDef example:
@@ -23,7 +23,7 @@ RawToolDef example:
23
23
  }
24
24
  }
25
25
  """
26
- RawToolDef = dict[str, Any]
26
+ type RawToolDef = dict[str, Any]
27
27
 
28
28
  @dataclasses.dataclass
29
29
  class ToolDef:
@@ -44,4 +44,4 @@ class ToolDef:
44
44
  execute=tool_fn,
45
45
  )
46
46
 
47
- ToolLike = ToolDef | RawToolDef | ToolFn
47
+ type ToolLike = ToolDef | RawToolDef | ToolFn
@@ -1,34 +1,24 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dais-sdk
3
- Version: 0.6.1
3
+ Version: 0.6.3
4
4
  Summary: A wrapper of LiteLLM
5
5
  Author-email: BHznJNs <bhznjns@outlook.com>
6
- Requires-Python: >=3.10
6
+ Requires-Python: >=3.14
7
7
  Description-Content-Type: text/markdown
8
8
  Classifier: Development Status :: 3 - Alpha
9
9
  Classifier: Intended Audience :: Developers
10
10
  Classifier: License :: OSI Approved :: MIT License
11
11
  Classifier: Programming Language :: Python :: 3 :: Only
12
- Classifier: Programming Language :: Python :: 3.10
13
- Classifier: Programming Language :: Python :: 3.11
14
- Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Programming Language :: Python :: 3.14
15
13
  License-File: LICENSE
16
14
  Requires-Dist: litellm>=1.80.0
17
15
  Requires-Dist: pydantic>=2.0.0
18
16
  Requires-Dist: httpx==0.28.1
19
- Requires-Dist: mcp==1.25.0
17
+ Requires-Dist: mcp==1.26.0
20
18
  Requires-Dist: starlette==0.50.0
21
19
  Requires-Dist: uvicorn==0.40.0
22
- Requires-Dist: python-dotenv>=1.2.1 ; extra == "dev"
23
- Requires-Dist: pytest-cov ; extra == "test"
24
- Requires-Dist: pytest-mock ; extra == "test"
25
- Requires-Dist: pytest-runner ; extra == "test"
26
- Requires-Dist: pytest ; extra == "test"
27
- Requires-Dist: pytest-github-actions-annotate-failures ; extra == "test"
28
20
  Project-URL: Source, https://github.com/Dais-Project/Dais-SDK
29
21
  Project-URL: Tracker, https://github.com/Dais-Project/Dais-SDK/issues
30
- Provides-Extra: dev
31
- Provides-Extra: test
32
22
 
33
23
  # Dais-SDK
34
24
 
@@ -98,3 +88,20 @@ for message in messages:
98
88
  print("Tool: ", message.result)
99
89
  ```
100
90
 
91
+ ## Development
92
+
93
+ Create virtual environment
94
+ ```
95
+ uv venv
96
+ ```
97
+
98
+ Install all dependencies
99
+ ```
100
+ uv sync --all-groups
101
+ ```
102
+
103
+ Run test
104
+ ```
105
+ uv run pytest
106
+ ```
107
+
@@ -1,27 +1,27 @@
1
1
  dais_sdk/__init__.py,sha256=5QAoL8GyyFuKfe4HZ-TiRVj9VIpHc45i2xMMYORoWXU,11805
2
2
  dais_sdk/debug.py,sha256=T7qIy1BeeUGlF40l9JCMMVn8pvvMJAEQeG4adQbOydA,69
3
3
  dais_sdk/logger.py,sha256=99vJAQRKcu4CuHgZYAJ2zDQtGea6Bn3vJJrS-mtza7c,677
4
- dais_sdk/param_parser.py,sha256=gXRFoCi74ZA9xdisqMPgQmWR2i6aTlPEeot78y2vyhM,1909
4
+ dais_sdk/param_parser.py,sha256=QIxt1izv69r725pzU1qhq5bilcrGUgzlpiItHEWOrdc,1874
5
5
  dais_sdk/stream.py,sha256=yu9Zvr3CUrPD9sGsjqwNXy_233Tw25Hd72kwjrraMAM,3170
6
6
  dais_sdk/mcp_client/__init__.py,sha256=B86aC4nnGzwfjk7H0CZ38YlMDiEx3EIDEAgJKUnwqIU,405
7
7
  dais_sdk/mcp_client/base_mcp_client.py,sha256=jWAfinzY00aL-qdNgyzYXKM-LhPHkmdqL24Uw439v-0,1055
8
- dais_sdk/mcp_client/local_mcp_client.py,sha256=unuS-cp4zi0A2x2EYnDFzSpJUzOgVQbnEK0mLBFudy8,1871
8
+ dais_sdk/mcp_client/local_mcp_client.py,sha256=JI7jdFyyL4VkrUuzwUybHJ3Y1vgEH0iCG3WqAgyLO1g,1951
9
9
  dais_sdk/mcp_client/oauth_server.py,sha256=pELKmAjE1QoNpy3_6BPNoORwWYu0j2BYOnnVfMd0iOA,3361
10
- dais_sdk/mcp_client/remote_mcp_client.py,sha256=853jbgGmrTe6EgQNqZNIfZ53rQH2VpzO_8aNfH1l3Gg,5959
10
+ dais_sdk/mcp_client/remote_mcp_client.py,sha256=8iPX-lGMRCR0FzT8MFi2CvYJXUCWHI_jYGaI30NaBuc,6039
11
11
  dais_sdk/tool/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
- dais_sdk/tool/execute.py,sha256=OiL1CVPakqIW1o0ZHpWCWs0AooXovMwECLyXB9oXGBo,3978
12
+ dais_sdk/tool/execute.py,sha256=wqIlfVounvK-I4mJ48iDXXqalySNQUplPPauNqFfSls,4220
13
13
  dais_sdk/tool/prepare.py,sha256=5UZiQc64Ao30Gh3aHqeJGeyUq7ud9A--GUU5QxYPC0M,11572
14
14
  dais_sdk/tool/utils.py,sha256=jYv_U6BHvi5Hh7H21_9nDRB9gp_qM_UEle0xq2XwX4M,706
15
15
  dais_sdk/tool/toolset/__init__.py,sha256=uh8hGSl1KSn0JI45fCPJnnk31hflOI0mYxC8cdbH-OQ,309
16
- dais_sdk/tool/toolset/mcp_toolset.py,sha256=-sivM6EUiC3V3gcISnh4u5dosf-lnwVjd7YM3L0U3Ik,4056
17
- dais_sdk/tool/toolset/python_toolset.py,sha256=JlYw49LH9xDL6tk_82EogqxW2U71hhsygamrb-lNvcE,1071
16
+ dais_sdk/tool/toolset/mcp_toolset.py,sha256=J4gOsy8aR61Ui8tUj58AOG51fhKvLPWIL_SWIs-kmrE,4112
17
+ dais_sdk/tool/toolset/python_toolset.py,sha256=p4QeBY_yZpzpnDT5w1S4EECtPOwt8dF8T_rTQv7CtME,1080
18
18
  dais_sdk/tool/toolset/toolset.py,sha256=X1xqWiWov4fboWQowB_YgJ_Tc-fIDmxbP8GreTj_7ME,322
19
19
  dais_sdk/types/__init__.py,sha256=-i1MYWIlUfjQIX0xZJta6phQNL44vXPSIx1eGyIYZXc,710
20
- dais_sdk/types/exceptions.py,sha256=kWE9SBe72O6zXHqQo203WEIfSy7PNO3fRqq2XQDFvXY,1476
21
- dais_sdk/types/message.py,sha256=A0Sdt1a5fXLW6Z63RxLnuDS2qggxYC-h4p7Jv8pCxiI,7777
20
+ dais_sdk/types/exceptions.py,sha256=ZlQW6QfzPatbfHJi_1s_2XBAHK4bZaeSreI3SUAX_5c,1439
21
+ dais_sdk/types/message.py,sha256=a65Q6K3Hq1aVhsedUv2XjyJq2rQQJi9SkEAPPHcLtFw,7770
22
22
  dais_sdk/types/request_params.py,sha256=8Jq-aTeK933YENE-9ay_8q88hEr-oeZZGC7l52tOKEM,1635
23
- dais_sdk/types/tool.py,sha256=s0sPwXPl-BeijWgRxgXkXguz_quzmP92sVS2aT7n_nA,1362
24
- dais_sdk-0.6.1.dist-info/licenses/LICENSE,sha256=cTeVgQVJJcRdm1boa2P1FBnOeXfA_egV6s4PouyrCxg,1064
25
- dais_sdk-0.6.1.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
26
- dais_sdk-0.6.1.dist-info/METADATA,sha256=K7nvqeMSnozBD3sppaMEM4_nvzbn0z1wImehT7cii4A,2910
27
- dais_sdk-0.6.1.dist-info/RECORD,,
23
+ dais_sdk/types/tool.py,sha256=GT6gxjAGdnVzk6W84nPzZb24bLznzqilkHN6uvocf5M,1377
24
+ dais_sdk-0.6.3.dist-info/licenses/LICENSE,sha256=Qwd-hsctqFqJErH4OWNNttmd0jccbH0ZCsf-YtAFNdo,1064
25
+ dais_sdk-0.6.3.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
26
+ dais_sdk-0.6.3.dist-info/METADATA,sha256=Tm4_bgcDUtx7rp1rW82aLeA9VpvBnM-fctwQHiqyPgY,2612
27
+ dais_sdk-0.6.3.dist-info/RECORD,,
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025 BHznJNs
3
+ Copyright (c) 2026 BHznJNs
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal