attp-client 0.0.1__tar.gz → 0.0.2__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 (35) hide show
  1. {attp_client-0.0.1 → attp_client-0.0.2}/PKG-INFO +1 -1
  2. {attp_client-0.0.1 → attp_client-0.0.2}/pyproject.toml +1 -1
  3. attp_client-0.0.2/src/attp_client/catalog.py +117 -0
  4. attp_client-0.0.2/src/attp_client/interfaces/catalogs/tools/envelope.py +13 -0
  5. {attp_client-0.0.1 → attp_client-0.0.2}/src/attp_client/tools.py +2 -0
  6. attp_client-0.0.1/src/attp_client/catalog.py +0 -59
  7. {attp_client-0.0.1 → attp_client-0.0.2}/README.md +0 -0
  8. {attp_client-0.0.1 → attp_client-0.0.2}/src/attp_client/client.py +0 -0
  9. {attp_client-0.0.1 → attp_client-0.0.2}/src/attp_client/consts.py +0 -0
  10. {attp_client-0.0.1 → attp_client-0.0.2}/src/attp_client/errors/attp_exception.py +0 -0
  11. {attp_client-0.0.1 → attp_client-0.0.2}/src/attp_client/errors/correlated_rpc_exception.py +0 -0
  12. {attp_client-0.0.1 → attp_client-0.0.2}/src/attp_client/errors/dead_session.py +0 -0
  13. {attp_client-0.0.1 → attp_client-0.0.2}/src/attp_client/errors/not_found.py +0 -0
  14. {attp_client-0.0.1 → attp_client-0.0.2}/src/attp_client/errors/serialization_error.py +0 -0
  15. {attp_client-0.0.1 → attp_client-0.0.2}/src/attp_client/errors/unauthenticated_error.py +0 -0
  16. {attp_client-0.0.1 → attp_client-0.0.2}/src/attp_client/inference.py +0 -0
  17. {attp_client-0.0.1 → attp_client-0.0.2}/src/attp_client/interfaces/catalogs/catalog.py +0 -0
  18. {attp_client-0.0.1 → attp_client-0.0.2}/src/attp_client/interfaces/error.py +0 -0
  19. {attp_client-0.0.1 → attp_client-0.0.2}/src/attp_client/interfaces/handshake/auth.py +0 -0
  20. {attp_client-0.0.1 → attp_client-0.0.2}/src/attp_client/interfaces/handshake/hello.py +0 -0
  21. {attp_client-0.0.1 → attp_client-0.0.2}/src/attp_client/interfaces/handshake/ready.py +0 -0
  22. {attp_client-0.0.1 → attp_client-0.0.2}/src/attp_client/interfaces/inference/enums/message_data_type.py +0 -0
  23. {attp_client-0.0.1 → attp_client-0.0.2}/src/attp_client/interfaces/inference/enums/message_emergency_type.py +0 -0
  24. {attp_client-0.0.1 → attp_client-0.0.2}/src/attp_client/interfaces/inference/enums/message_type.py +0 -0
  25. {attp_client-0.0.1 → attp_client-0.0.2}/src/attp_client/interfaces/inference/message.py +0 -0
  26. {attp_client-0.0.1 → attp_client-0.0.2}/src/attp_client/interfaces/inference/tool.py +0 -0
  27. {attp_client-0.0.1 → attp_client-0.0.2}/src/attp_client/interfaces/route_mappings.py +0 -0
  28. {attp_client-0.0.1 → attp_client-0.0.2}/src/attp_client/misc/fixed_basemodel.py +0 -0
  29. {attp_client-0.0.1 → attp_client-0.0.2}/src/attp_client/misc/serializable.py +0 -0
  30. {attp_client-0.0.1 → attp_client-0.0.2}/src/attp_client/router.py +0 -0
  31. {attp_client-0.0.1 → attp_client-0.0.2}/src/attp_client/session.py +0 -0
  32. {attp_client-0.0.1 → attp_client-0.0.2}/src/attp_client/types/route_mapping.py +0 -0
  33. {attp_client-0.0.1 → attp_client-0.0.2}/src/attp_client/utils/context_awaiter.py +0 -0
  34. {attp_client-0.0.1 → attp_client-0.0.2}/src/attp_client/utils/route_mapper.py +0 -0
  35. {attp_client-0.0.1 → attp_client-0.0.2}/src/attp_client/utils/serializer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: attp-client
3
- Version: 0.0.1
3
+ Version: 0.0.2
4
4
  Summary: A python-sdk client for interacting with AgentHub's ATTP protocol (Agent Tool Transport Protocol)
5
5
  License: MIT
6
6
  Author: Ascender Team
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "attp-client"
3
- version = "0.0.1"
3
+ version = "0.0.2"
4
4
  description = "A python-sdk client for interacting with AgentHub's ATTP protocol (Agent Tool Transport Protocol)"
5
5
  authors = [
6
6
  {name = "Ascender Team"}
@@ -0,0 +1,117 @@
1
+ import asyncio
2
+ from typing import Any, Callable, MutableMapping
3
+
4
+ from attp_client.errors.attp_exception import AttpException
5
+ from attp_client.errors.not_found import NotFoundError
6
+ from attp_client.interfaces.catalogs.tools.envelope import IEnvelope
7
+ from attp_client.interfaces.error import IErr
8
+ from attp_client.tools import ToolsManager
9
+
10
+ from reactivex import operators as ops
11
+ from reactivex.scheduler.eventloop import AsyncIOScheduler
12
+
13
+ from attp_core.rs_api import PyAttpMessage
14
+
15
+
16
+ class AttpCatalog:
17
+ attached_tools: MutableMapping[str, Callable[..., Any]] # id, callback
18
+ tool_name_to_id_symlink: MutableMapping[str, str] # name, id
19
+
20
+ def __init__(
21
+ self,
22
+ id: int,
23
+ catalog_name: str,
24
+ manager: ToolsManager
25
+ ) -> None:
26
+ self.id = id
27
+ self.catalog_name = catalog_name
28
+ self.tool_manager = manager
29
+ self.attached_tools = {}
30
+ self.tool_name_to_id_symlink = {}
31
+
32
+ self.responder = self.tool_manager.router.responder
33
+
34
+ async def start_tool_listener(self):
35
+ scheduler = AsyncIOScheduler(asyncio.get_event_loop())
36
+
37
+ def handle_call(item: IEnvelope):
38
+ asyncio.create_task(self.handle_call(item))
39
+
40
+ def send_err(err: AttpException):
41
+ asyncio.create_task(self.tool_manager.router.session.send_error(err=err.to_ierr(), correlation_id=None, route=1))
42
+
43
+ def envelopize(item: PyAttpMessage):
44
+ if not item.payload:
45
+ raise AttpException("EmptyPayload", detail={"message": "Payload was empty."})
46
+ try:
47
+ return IEnvelope.mps(item.payload)
48
+ except Exception as e:
49
+ raise AttpException("InvalidPayload", detail={"message": f"Payload was invalid: {str(e)}"})
50
+
51
+ def catch_handler(err: Any, _: Any):
52
+ # Convert any exception to AttpException if needed
53
+ if not isinstance(err, AttpException):
54
+ attp_err = AttpException("UnhandledException", detail={"message": str(err)})
55
+ else:
56
+ attp_err = err
57
+ send_err(attp_err)
58
+ # Return an empty observable to terminate the stream after error
59
+ from reactivex import empty
60
+ return empty()
61
+
62
+ self.responder.pipe(
63
+ ops.filter(lambda item: item.payload is not None and item.correlation_id == 1),
64
+ ops.map(lambda item: envelopize(item)),
65
+ ops.catch(catch_handler),
66
+ ops.filter(lambda item: item.catalog == self.catalog_name and item.tool_id in self.attached_tools),
67
+ ops.observe_on(scheduler),
68
+ ).subscribe(lambda item: handle_call(item))
69
+
70
+ async def attach_tool(
71
+ self,
72
+ callback: Callable[[IEnvelope], Any],
73
+ name: str,
74
+ description: str | None = None,
75
+ schema: dict | None = None,
76
+ schema_id: str | None = None,
77
+ *,
78
+ return_direct: bool = False,
79
+ schema_ver: str = "1.0",
80
+ timeout_ms: float = 20000,
81
+ idempotent: bool = False
82
+ ):
83
+ assigned_id = await self.tool_manager.register(
84
+ self.catalog_name,
85
+ name=name,
86
+ description=description,
87
+ schema_id=schema_id,
88
+ return_direct=return_direct,
89
+ schema=schema,
90
+ schema_ver=schema_ver,
91
+ timeout_ms=timeout_ms,
92
+ idempotent=idempotent
93
+ )
94
+
95
+ self.attached_tools[str(assigned_id)] = callback
96
+ self.tool_name_to_id_symlink[name] = str(assigned_id)
97
+ return assigned_id
98
+
99
+ async def detatch_tool(
100
+ self,
101
+ name: str
102
+ ):
103
+ tool_id = self.tool_name_to_id_symlink.get(name)
104
+
105
+ if not tool_id:
106
+ raise NotFoundError(f"Tool {name} not marked as registered and wasn't found in the catalog {self.catalog_name}.")
107
+
108
+ await self.tool_manager.unregister(self.catalog_name, tool_id)
109
+ return tool_id
110
+
111
+ async def handle_call(self, envelope: IEnvelope) -> Any:
112
+ tool = self.attached_tools.get(envelope.tool_id)
113
+
114
+ if not tool:
115
+ raise NotFoundError(f"Tool {envelope.tool_id} not marked as registered and wasn't found in the catalog {self.catalog_name}.")
116
+
117
+ return await tool(envelope)
@@ -0,0 +1,13 @@
1
+ from typing import Annotated, Any, Mapping
2
+ from pydantic import Field
3
+ from typing_extensions import Doc
4
+ from attp_client.misc.fixed_basemodel import FixedBaseModel
5
+
6
+
7
+ class IEnvelope(FixedBaseModel):
8
+ """Uniform tool output"""
9
+ type: Annotated[str, Doc("Envelop discriminator; For tools only 'tool-call'")] = Field("tool-call")
10
+ catalog: Annotated[str, Doc("Tool catalog name where tool is registered and being called from")]
11
+ tool_id: Annotated[str, Doc("Tool ID being called")]
12
+ metadata: Annotated[Mapping[str, Any], Doc("AgentHub/trace metadata")] = Field(default_factory=dict)
13
+ data: Annotated[Mapping[str, Any], Doc("Tool output JSON matching provided tool arg schema")]
@@ -15,6 +15,7 @@ class ToolsManager:
15
15
  name: str,
16
16
  description: str | None = None,
17
17
  schema_id: str | None = None,
18
+ schema: dict | None = None,
18
19
  *,
19
20
  return_direct: bool = False,
20
21
  schema_ver: str = "1.0",
@@ -31,6 +32,7 @@ class ToolsManager:
31
32
  "schema_id": schema_id,
32
33
  "return_direct": return_direct,
33
34
  "schema_ver": schema_ver,
35
+ "schema": schema,
34
36
  "timeout_ms": timeout_ms,
35
37
  "idempotent": idempotent
36
38
  }
@@ -1,59 +0,0 @@
1
- from typing import Any, Callable, MutableMapping
2
- from attp_client.errors.not_found import NotFoundError
3
- from attp_client.tools import ToolsManager
4
-
5
-
6
- class AttpCatalog:
7
- attached_tools: MutableMapping[str, Callable[..., Any]]
8
- tool_name_to_id_symlink: MutableMapping[str, str]
9
-
10
- def __init__(
11
- self,
12
- id: int,
13
- catalog_name: str,
14
- manager: ToolsManager
15
- ) -> None:
16
- self.id = id
17
- self.catalog_name = catalog_name
18
- self.tool_manager = manager
19
- self.attached_tools = {}
20
- self.tool_name_to_id_symlink = {}
21
-
22
- async def attach_tool(
23
- self,
24
- callback: Callable[..., Any],
25
- name: str,
26
- description: str | None = None,
27
- schema_id: str | None = None,
28
- *,
29
- return_direct: bool = False,
30
- schema_ver: str = "1.0",
31
- timeout_ms: float = 20000,
32
- idempotent: bool = False
33
- ):
34
- assigned_id = await self.tool_manager.register(
35
- self.catalog_name,
36
- name=name,
37
- description=description,
38
- schema_id=schema_id,
39
- return_direct=return_direct,
40
- schema_ver=schema_ver,
41
- timeout_ms=timeout_ms,
42
- idempotent=idempotent
43
- )
44
-
45
- self.attached_tools[str(assigned_id)] = callback
46
- self.tool_name_to_id_symlink[name] = str(assigned_id)
47
- return assigned_id
48
-
49
- async def detatch_tool(
50
- self,
51
- name: str
52
- ):
53
- tool_id = self.tool_name_to_id_symlink.get(name)
54
-
55
- if not tool_id:
56
- raise NotFoundError(f"Tool {name} not marked as registered and wasn't found in the catalog {self.catalog_name}.")
57
-
58
- await self.tool_manager.unregister(self.catalog_name, tool_id)
59
- return tool_id
File without changes