unique_toolkit 1.1.8__py3-none-any.whl → 1.2.0__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.
@@ -16,10 +16,10 @@ class Postprocessor(ABC):
16
16
  def get_name(self) -> str:
17
17
  return self.name
18
18
 
19
- async def run(self, loop_response: LanguageModelStreamResponse) -> str:
19
+ async def run(self, loop_response: LanguageModelStreamResponse) -> None:
20
20
  raise NotImplementedError("Subclasses must implement this method.")
21
21
 
22
- async def apply_postprocessing_to_response(
22
+ def apply_postprocessing_to_response(
23
23
  self, loop_response: LanguageModelStreamResponse
24
24
  ) -> bool:
25
25
  raise NotImplementedError(
@@ -102,6 +102,7 @@ class PostprocessorManager:
102
102
  self._chat_service.modify_assistant_message(
103
103
  content=loop_response.message.text,
104
104
  message_id=loop_response.message.id,
105
+ references=loop_response.message.references,
105
106
  )
106
107
 
107
108
  async def execute_postprocessors(
@@ -1,3 +1,7 @@
1
+ from enum import StrEnum
2
+
3
+ from pydantic import BaseModel
4
+
1
5
  from unique_toolkit.agentic.tools.config import get_configuration_dict
2
6
  from unique_toolkit.agentic.tools.schemas import BaseToolConfig
3
7
 
@@ -6,22 +10,36 @@ This is the message that will be sent to the sub-agent.
6
10
  """.strip()
7
11
 
8
12
 
13
+ class ResponseDisplayMode(StrEnum):
14
+ HIDDEN = "hidden"
15
+ DETAILS_OPEN = "details_open"
16
+ DETAILS_CLOSED = "details_closed"
17
+
18
+
19
+ class SubAgentToolDisplayConfig(BaseModel):
20
+ model_config = get_configuration_dict()
21
+
22
+ mode: ResponseDisplayMode = ResponseDisplayMode.HIDDEN
23
+ remove_from_history: bool = True
24
+
25
+
9
26
  class SubAgentToolConfig(BaseToolConfig):
10
27
  model_config = get_configuration_dict()
11
28
 
12
- name: str = "default_name"
13
29
  assistant_id: str = ""
14
30
  chat_id: str | None = None
15
31
  reuse_chat: bool = True
32
+
16
33
  tool_description_for_system_prompt: str = ""
17
34
  tool_description: str = ""
18
35
  param_description_sub_agent_user_message: str = (
19
36
  DEFAULT_PARAM_DESCRIPTION_SUB_AGENT_USER_MESSAGE
20
37
  )
21
38
  tool_format_information_for_system_prompt: str = ""
22
-
23
39
  tool_description_for_user_prompt: str = ""
24
40
  tool_format_information_for_user_prompt: str = ""
25
41
 
26
42
  poll_interval: float = 1.0
27
43
  max_wait: float = 120.0
44
+
45
+ response_display_config: SubAgentToolDisplayConfig = SubAgentToolDisplayConfig()
@@ -3,8 +3,6 @@ from logging import Logger
3
3
  from unique_toolkit.agentic.tools.a2a.config import SubAgentToolConfig
4
4
  from unique_toolkit.agentic.tools.a2a.service import SubAgentTool, ToolProgressReporter
5
5
  from unique_toolkit.agentic.tools.config import ToolBuildConfig
6
- from unique_toolkit.agentic.tools.schemas import BaseToolConfig
7
- from unique_toolkit.agentic.tools.tool import Tool
8
6
  from unique_toolkit.app.schemas import ChatEvent
9
7
 
10
8
 
@@ -19,7 +17,7 @@ class A2AManager:
19
17
 
20
18
  def get_all_sub_agents(
21
19
  self, tool_configs: list[ToolBuildConfig], event: ChatEvent
22
- ) -> tuple[list[ToolBuildConfig], list[Tool[BaseToolConfig]]]:
20
+ ) -> tuple[list[ToolBuildConfig], list[SubAgentTool]]:
23
21
  sub_agents = []
24
22
 
25
23
  for tool_config in tool_configs:
@@ -32,13 +30,15 @@ class A2AManager:
32
30
  )
33
31
  continue
34
32
 
35
- sub_agent_tool_config: SubAgentToolConfig = tool_config.configuration
33
+ sub_agent_tool_config = tool_config.configuration
36
34
 
37
35
  sub_agents.append(
38
36
  SubAgentTool(
39
37
  configuration=sub_agent_tool_config,
40
38
  event=event,
41
39
  tool_progress_reporter=self._tool_progress_reporter,
40
+ name=tool_config.name,
41
+ display_name=tool_config.display_name,
42
42
  )
43
43
  )
44
44
 
@@ -0,0 +1,5 @@
1
+ from unique_toolkit.agentic.tools.a2a.postprocessing.postprocessor import (
2
+ SubAgentResponsesPostprocessor,
3
+ )
4
+
5
+ __all__ = ["SubAgentResponsesPostprocessor"]
@@ -0,0 +1,113 @@
1
+ import re
2
+ from abc import ABC, abstractmethod
3
+ from typing import Literal, override
4
+
5
+ from unique_toolkit.agentic.tools.a2a.config import ResponseDisplayMode
6
+
7
+
8
+ class _ResponseDisplayHandler(ABC):
9
+ @abstractmethod
10
+ def build_response_display(
11
+ self, display_name: str, assistant_id: str, answer: str
12
+ ) -> str:
13
+ raise NotImplementedError()
14
+
15
+ @abstractmethod
16
+ def remove_response_display(self, assistant_id: str, text: str) -> str:
17
+ raise NotImplementedError()
18
+
19
+
20
+ class _DetailsResponseDisplayHandler(_ResponseDisplayHandler):
21
+ def __init__(self, mode: Literal["open", "closed"]) -> None:
22
+ self._mode = mode
23
+
24
+ DETAILS_CLOSED_TEMPLATE = (
25
+ "<details><summary>{display_name}</summary>\n"
26
+ "\n"
27
+ '<div style="display: none;">{assistant_id}</div>\n'
28
+ "\n"
29
+ "{answer}\n"
30
+ "</details>\n"
31
+ "<br>\n"
32
+ "\n"
33
+ )
34
+
35
+ DETAILS_OPEN_TEMPLATE = (
36
+ "<details open><summary>{display_name}</summary>\n"
37
+ "\n"
38
+ '<div style="display: none;">{assistant_id}</div>\n'
39
+ "\n"
40
+ "{answer}\n"
41
+ "\n"
42
+ "</details>\n"
43
+ "<br>\n"
44
+ "\n"
45
+ )
46
+
47
+ def _get_detect_re(self, assistant_id: str) -> str:
48
+ if self._mode == "open":
49
+ return (
50
+ r"(?s)<details open>\s*"
51
+ r"<summary>(.*?)</summary>\s*"
52
+ rf"<div style=\"display: none;\">{re.escape(assistant_id)}</div>\s*"
53
+ r"(.*?)\s*"
54
+ r"</details>\s*"
55
+ r"<br>\s*"
56
+ )
57
+ else:
58
+ return (
59
+ r"(?s)<details>\s*"
60
+ r"<summary>(.*?)</summary>\s*"
61
+ rf"<div style=\"display: none;\">{re.escape(assistant_id)}</div>\s*"
62
+ r"(.*?)\s*"
63
+ r"</details>\s*"
64
+ r"<br>\s*"
65
+ )
66
+
67
+ def _get_template(self) -> str:
68
+ if self._mode == "open":
69
+ return self.DETAILS_OPEN_TEMPLATE
70
+ else:
71
+ return self.DETAILS_CLOSED_TEMPLATE
72
+
73
+ @override
74
+ def build_response_display(
75
+ self, display_name: str, assistant_id: str, answer: str
76
+ ) -> str:
77
+ return self._get_template().format(
78
+ assistant_id=assistant_id, display_name=display_name, answer=answer
79
+ )
80
+
81
+ @override
82
+ def remove_response_display(self, assistant_id: str, text: str) -> str:
83
+ return re.sub(self._get_detect_re(assistant_id=assistant_id), "", text)
84
+
85
+
86
+ _DISPLAY_HANDLERS = {
87
+ ResponseDisplayMode.DETAILS_OPEN: _DetailsResponseDisplayHandler(mode="open"),
88
+ ResponseDisplayMode.DETAILS_CLOSED: _DetailsResponseDisplayHandler(mode="closed"),
89
+ }
90
+
91
+
92
+ def build_sub_agent_answer_display(
93
+ display_name: str, display_mode: ResponseDisplayMode, answer: str, assistant_id: str
94
+ ) -> str:
95
+ if display_mode not in _DISPLAY_HANDLERS:
96
+ return ""
97
+
98
+ display_f = _DISPLAY_HANDLERS[display_mode]
99
+
100
+ return display_f.build_response_display(
101
+ display_name=display_name, answer=answer, assistant_id=assistant_id
102
+ )
103
+
104
+
105
+ def remove_sub_agent_answer_from_text(
106
+ display_mode: ResponseDisplayMode, text: str, assistant_id: str
107
+ ) -> str:
108
+ if display_mode not in _DISPLAY_HANDLERS:
109
+ return text
110
+
111
+ display_f = _DISPLAY_HANDLERS[display_mode]
112
+
113
+ return display_f.remove_response_display(assistant_id=assistant_id, text=text)
@@ -0,0 +1,204 @@
1
+ import logging
2
+ import re
3
+ from typing import NotRequired, TypedDict, override
4
+
5
+ import unique_sdk
6
+
7
+ from unique_toolkit.agentic.postprocessor.postprocessor_manager import Postprocessor
8
+ from unique_toolkit.agentic.tools.a2a.config import (
9
+ ResponseDisplayMode,
10
+ SubAgentToolDisplayConfig,
11
+ )
12
+ from unique_toolkit.agentic.tools.a2a.postprocessing.display import (
13
+ build_sub_agent_answer_display,
14
+ remove_sub_agent_answer_from_text,
15
+ )
16
+ from unique_toolkit.agentic.tools.a2a.service import SubAgentTool
17
+ from unique_toolkit.content.schemas import ContentReference
18
+ from unique_toolkit.language_model.schemas import LanguageModelStreamResponse
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+ SpaceMessage = unique_sdk.Space.Message
23
+
24
+
25
+ class _SubAgentMessageInfo(TypedDict):
26
+ text: str | None
27
+ references: list[unique_sdk.Space.Reference]
28
+
29
+
30
+ class _SubAgentToolInfo(TypedDict):
31
+ display_name: str
32
+ display_config: SubAgentToolDisplayConfig
33
+ response: NotRequired[_SubAgentMessageInfo]
34
+
35
+
36
+ class SubAgentResponsesPostprocessor(Postprocessor):
37
+ def __init__(
38
+ self,
39
+ user_id: str,
40
+ company_id: str,
41
+ agent_chat_id: str,
42
+ sub_agent_tools: list[SubAgentTool],
43
+ ):
44
+ super().__init__(name=self.__class__.__name__)
45
+
46
+ self._user_id = user_id
47
+ self._company_id = company_id
48
+
49
+ self._agent_chat_id = agent_chat_id
50
+
51
+ self._assistant_id_to_tool_info: dict[str, _SubAgentToolInfo] = {}
52
+
53
+ for sub_agent_tool in sub_agent_tools:
54
+ sub_agent_tool.subscribe(self)
55
+
56
+ self._assistant_id_to_tool_info[sub_agent_tool.config.assistant_id] = (
57
+ _SubAgentToolInfo(
58
+ display_config=sub_agent_tool.config.response_display_config,
59
+ display_name=sub_agent_tool.display_name(),
60
+ )
61
+ )
62
+
63
+ self._sub_agent_message = None
64
+
65
+ @override
66
+ async def run(self, loop_response: LanguageModelStreamResponse) -> None:
67
+ self._sub_agent_message = await unique_sdk.Space.get_latest_message_async(
68
+ user_id=self._user_id,
69
+ company_id=self._company_id,
70
+ chat_id=self._agent_chat_id,
71
+ )
72
+
73
+ @override
74
+ def apply_postprocessing_to_response(
75
+ self, loop_response: LanguageModelStreamResponse
76
+ ) -> bool:
77
+ logger.info("Adding sub agent responses to the response")
78
+
79
+ # Get responses to display
80
+ displayed = {}
81
+ for assistant_id, tool_info in self._assistant_id_to_tool_info.items():
82
+ display_mode = tool_info["display_config"].mode
83
+
84
+ if "response" not in tool_info:
85
+ logger.warning(
86
+ "No response from assistant %s",
87
+ assistant_id,
88
+ )
89
+ continue
90
+
91
+ if display_mode != ResponseDisplayMode.HIDDEN:
92
+ displayed[assistant_id] = tool_info["response"]
93
+
94
+ existing_refs = {
95
+ ref.source_id: ref.sequence_number
96
+ for ref in loop_response.message.references
97
+ }
98
+ _consolidate_references_in_place(displayed, existing_refs)
99
+
100
+ modified = len(displayed) > 0
101
+ for assistant_id, message in reversed(displayed.items()):
102
+ tool_info = self._assistant_id_to_tool_info[assistant_id]
103
+ display_mode = tool_info["display_config"].mode
104
+ display_name = tool_info["display_name"]
105
+ loop_response.message.text = (
106
+ build_sub_agent_answer_display(
107
+ display_name=display_name,
108
+ assistant_id=assistant_id,
109
+ display_mode=display_mode,
110
+ answer=message["text"],
111
+ )
112
+ + loop_response.message.text
113
+ )
114
+
115
+ assert self._sub_agent_message is not None
116
+
117
+ loop_response.message.references.extend(
118
+ ContentReference(
119
+ message_id=self._sub_agent_message["id"],
120
+ source_id=ref["sourceId"],
121
+ url=ref["url"],
122
+ source=ref["source"],
123
+ name=ref["name"],
124
+ sequence_number=ref["sequenceNumber"],
125
+ )
126
+ for ref in message["references"]
127
+ )
128
+
129
+ return modified
130
+
131
+ @override
132
+ async def remove_from_text(self, text) -> str:
133
+ for assistant_id, tool_info in self._assistant_id_to_tool_info.items():
134
+ display_config = tool_info["display_config"]
135
+ if display_config.remove_from_history:
136
+ text = remove_sub_agent_answer_from_text(
137
+ display_mode=display_config.mode,
138
+ text=text,
139
+ assistant_id=assistant_id,
140
+ )
141
+ return text
142
+
143
+ def notify_sub_agent_response(
144
+ self, sub_agent_assistant_id: str, response: SpaceMessage
145
+ ) -> None:
146
+ if sub_agent_assistant_id not in self._assistant_id_to_tool_info:
147
+ logger.warning(
148
+ "Unknown assistant id %s received, message will be ignored.",
149
+ sub_agent_assistant_id,
150
+ )
151
+ return
152
+
153
+ self._assistant_id_to_tool_info[sub_agent_assistant_id]["response"] = {
154
+ "text": response["text"],
155
+ "references": [
156
+ {
157
+ "name": ref["name"],
158
+ "url": ref["url"],
159
+ "sequenceNumber": ref["sequenceNumber"],
160
+ "originalIndex": [],
161
+ "sourceId": ref["sourceId"],
162
+ "source": ref["source"],
163
+ }
164
+ for ref in response["references"] or []
165
+ ],
166
+ }
167
+
168
+
169
+ def _consolidate_references_in_place(
170
+ messages: dict[str, _SubAgentMessageInfo], existing_refs: dict[str, int]
171
+ ) -> None:
172
+ start_index = max(existing_refs.values(), default=0) + 1
173
+
174
+ for assistant_id, message in messages.items():
175
+ references = message["references"]
176
+ if len(references) == 0 or message["text"] is None:
177
+ logger.info(
178
+ "Message from assistant %s does not contain any references",
179
+ assistant_id,
180
+ )
181
+ continue
182
+
183
+ references = list(sorted(references, key=lambda ref: ref["sequenceNumber"]))
184
+
185
+ message_new_refs = []
186
+ for reference in references:
187
+ source_id = reference["sourceId"]
188
+
189
+ if source_id not in existing_refs:
190
+ message_new_refs.append(reference)
191
+ existing_refs[source_id] = start_index
192
+ start_index += 1
193
+
194
+ reference_num = existing_refs[source_id]
195
+
196
+ seq_num = reference["sequenceNumber"]
197
+ message["text"] = re.sub(
198
+ rf"<sup>{seq_num}</sup>",
199
+ f"<sup>{reference_num}</sup>",
200
+ message["text"],
201
+ )
202
+ reference["sequenceNumber"] = reference_num
203
+
204
+ message["references"] = message_new_refs
@@ -0,0 +1,412 @@
1
+ import re
2
+
3
+ import pytest
4
+
5
+ from unique_toolkit.agentic.tools.a2a.config import ResponseDisplayMode
6
+ from unique_toolkit.agentic.tools.a2a.postprocessing.display import (
7
+ _DetailsResponseDisplayHandler,
8
+ build_sub_agent_answer_display,
9
+ remove_sub_agent_answer_from_text,
10
+ )
11
+
12
+
13
+ class TestDetailsResponseDisplayHandler:
14
+ """Test suite for DetailsResponseDisplayHandler class."""
15
+
16
+ @pytest.fixture
17
+ def open_handler(self):
18
+ """Create a handler with open mode."""
19
+ return _DetailsResponseDisplayHandler(mode="open")
20
+
21
+ @pytest.fixture
22
+ def closed_handler(self):
23
+ """Create a handler with closed mode."""
24
+ return _DetailsResponseDisplayHandler(mode="closed")
25
+
26
+ @pytest.fixture
27
+ def sample_data(self):
28
+ """Sample data for testing."""
29
+ return {
30
+ "display_name": "Test Assistant",
31
+ "assistant_id": "test_assistant_123",
32
+ "answer": "This is a test answer with multiple lines.\nSecond line here.",
33
+ }
34
+
35
+ def test_build_response_display_open_mode(self, open_handler, sample_data):
36
+ """Test building response display in open mode."""
37
+ result = open_handler.build_response_display(
38
+ display_name=sample_data["display_name"],
39
+ assistant_id=sample_data["assistant_id"],
40
+ answer=sample_data["answer"],
41
+ )
42
+
43
+ assert "<details open>" in result
44
+ assert (
45
+ f'<div style="display: none;">{sample_data["assistant_id"]}</div>' in result
46
+ )
47
+ assert f"<summary>{sample_data['display_name']}</summary>" in result
48
+ assert sample_data["answer"] in result
49
+ assert "</details>" in result
50
+
51
+ def test_build_response_display_closed_mode(self, closed_handler, sample_data):
52
+ """Test building response display in closed mode."""
53
+ result = closed_handler.build_response_display(
54
+ display_name=sample_data["display_name"],
55
+ assistant_id=sample_data["assistant_id"],
56
+ answer=sample_data["answer"],
57
+ )
58
+
59
+ assert "<details>" in result
60
+ assert "<details open>" not in result
61
+ assert (
62
+ f'<div style="display: none;">{sample_data["assistant_id"]}</div>' in result
63
+ )
64
+ assert f"<summary>{sample_data['display_name']}</summary>" in result
65
+ assert sample_data["answer"] in result
66
+ assert "</details>" in result
67
+
68
+ def test_build_response_display_with_special_characters(self, open_handler):
69
+ """Test building response display with special characters in content."""
70
+ result = open_handler.build_response_display(
71
+ display_name="Test & Co.",
72
+ assistant_id="test<>123",
73
+ answer="Answer with <tags> & symbols",
74
+ )
75
+
76
+ assert "Test & Co." in result
77
+ assert "test<>123" in result
78
+ assert "Answer with <tags> & symbols" in result
79
+
80
+ def test_remove_response_display_open_mode(self, open_handler, sample_data):
81
+ """Test removing response display from text in open mode."""
82
+ # First build the display
83
+ display_html = open_handler.build_response_display(
84
+ display_name=sample_data["display_name"],
85
+ assistant_id=sample_data["assistant_id"],
86
+ answer=sample_data["answer"],
87
+ )
88
+
89
+ # Create text with the display embedded
90
+ text_with_display = f"Some text before\n{display_html}\nSome text after"
91
+
92
+ # Remove the display
93
+ result = open_handler.remove_response_display(
94
+ assistant_id=sample_data["assistant_id"], text=text_with_display
95
+ )
96
+
97
+ assert "Some text before" in result
98
+ assert "Some text after" in result
99
+ assert sample_data["display_name"] not in result
100
+ assert sample_data["answer"] not in result
101
+
102
+ def test_remove_response_display_closed_mode(self, closed_handler, sample_data):
103
+ """Test removing response display from text in closed mode."""
104
+ # First build the display
105
+ display_html = closed_handler.build_response_display(
106
+ display_name=sample_data["display_name"],
107
+ assistant_id=sample_data["assistant_id"],
108
+ answer=sample_data["answer"],
109
+ )
110
+
111
+ # Create text with the display embedded
112
+ text_with_display = f"Some text before\n{display_html}\nSome text after"
113
+
114
+ # Remove the display
115
+ result = closed_handler.remove_response_display(
116
+ assistant_id=sample_data["assistant_id"], text=text_with_display
117
+ )
118
+
119
+ assert "Some text before" in result
120
+ assert "Some text after" in result
121
+ assert sample_data["display_name"] not in result
122
+ assert sample_data["answer"] not in result
123
+
124
+ def test_remove_response_display_multiple_instances(self, open_handler):
125
+ """Test removing multiple instances of response display."""
126
+ assistant_id = "test_123"
127
+
128
+ display1 = open_handler.build_response_display(
129
+ display_name="First", assistant_id=assistant_id, answer="First answer"
130
+ )
131
+
132
+ display2 = open_handler.build_response_display(
133
+ display_name="Second", assistant_id=assistant_id, answer="Second answer"
134
+ )
135
+
136
+ text_with_displays = f"Start\n{display1}\nMiddle\n{display2}\nEnd"
137
+
138
+ result = open_handler.remove_response_display(
139
+ assistant_id=assistant_id, text=text_with_displays
140
+ )
141
+
142
+ assert "Start" in result
143
+ assert "Middle" in result
144
+ assert "End" in result
145
+ assert "First answer" not in result
146
+ assert "Second answer" not in result
147
+
148
+ def test_remove_response_display_no_match(self, open_handler):
149
+ """Test removing response display when no match exists."""
150
+ text = "This is some text without any displays"
151
+ result = open_handler.remove_response_display(
152
+ assistant_id="nonexistent", text=text
153
+ )
154
+ assert result == text
155
+
156
+ def test_remove_response_display_with_regex_special_chars(self, open_handler):
157
+ """Test removing response display with regex special characters in assistant_id."""
158
+ assistant_id = "test.+*?[]{}()^$|"
159
+
160
+ display_html = open_handler.build_response_display(
161
+ display_name="Test", assistant_id=assistant_id, answer="Test answer"
162
+ )
163
+
164
+ text_with_display = f"Before\n{display_html}\nAfter"
165
+
166
+ result = open_handler.remove_response_display(
167
+ assistant_id=assistant_id, text=text_with_display
168
+ )
169
+
170
+ assert "Before" in result
171
+ assert "After" in result
172
+ assert "Test answer" not in result
173
+
174
+ def test_get_detect_re_pattern_validity(self, open_handler, closed_handler):
175
+ """Test that the regex patterns are valid and compilable."""
176
+ assistant_id = "test_123"
177
+
178
+ open_pattern = open_handler._get_detect_re(assistant_id)
179
+ closed_pattern = closed_handler._get_detect_re(assistant_id)
180
+
181
+ # Should not raise exceptions
182
+ re.compile(open_pattern)
183
+ re.compile(closed_pattern)
184
+
185
+ assert "(?s)" in open_pattern # multiline flag
186
+ assert "(?s)" in closed_pattern
187
+ assert "details open" in open_pattern
188
+ assert "details>" in closed_pattern
189
+ assert "details open" not in closed_pattern
190
+
191
+
192
+ class TestDisplayFunctions:
193
+ """Test suite for module-level display functions."""
194
+
195
+ @pytest.fixture
196
+ def sample_data(self):
197
+ """Sample data for testing."""
198
+ return {
199
+ "display_name": "Test Assistant",
200
+ "assistant_id": "test_assistant_123",
201
+ "answer": "This is a test answer.",
202
+ }
203
+
204
+ def test_build_sub_agent_answer_display_details_open(self, sample_data):
205
+ """Test building sub-agent answer display with DETAILS_OPEN mode."""
206
+ result = build_sub_agent_answer_display(
207
+ display_name=sample_data["display_name"],
208
+ display_mode=ResponseDisplayMode.DETAILS_OPEN,
209
+ answer=sample_data["answer"],
210
+ assistant_id=sample_data["assistant_id"],
211
+ )
212
+
213
+ assert "<details open>" in result
214
+ assert sample_data["display_name"] in result
215
+ assert sample_data["answer"] in result
216
+ assert sample_data["assistant_id"] in result
217
+
218
+ def test_build_sub_agent_answer_display_details_closed(self, sample_data):
219
+ """Test building sub-agent answer display with DETAILS_CLOSED mode."""
220
+ result = build_sub_agent_answer_display(
221
+ display_name=sample_data["display_name"],
222
+ display_mode=ResponseDisplayMode.DETAILS_CLOSED,
223
+ answer=sample_data["answer"],
224
+ assistant_id=sample_data["assistant_id"],
225
+ )
226
+
227
+ assert "<details>" in result
228
+ assert "<details open>" not in result
229
+ assert sample_data["display_name"] in result
230
+ assert sample_data["answer"] in result
231
+ assert sample_data["assistant_id"] in result
232
+
233
+ def test_build_sub_agent_answer_display_hidden_mode(self, sample_data):
234
+ """Test building sub-agent answer display with HIDDEN mode."""
235
+ result = build_sub_agent_answer_display(
236
+ display_name=sample_data["display_name"],
237
+ display_mode=ResponseDisplayMode.HIDDEN,
238
+ answer=sample_data["answer"],
239
+ assistant_id=sample_data["assistant_id"],
240
+ )
241
+
242
+ assert result == ""
243
+
244
+ def test_remove_sub_agent_answer_from_text_details_open(self, sample_data):
245
+ """Test removing sub-agent answer from text with DETAILS_OPEN mode."""
246
+ # First build the display
247
+ display_html = build_sub_agent_answer_display(
248
+ display_name=sample_data["display_name"],
249
+ display_mode=ResponseDisplayMode.DETAILS_OPEN,
250
+ answer=sample_data["answer"],
251
+ assistant_id=sample_data["assistant_id"],
252
+ )
253
+
254
+ text_with_display = f"Before\n{display_html}\nAfter"
255
+
256
+ result = remove_sub_agent_answer_from_text(
257
+ display_mode=ResponseDisplayMode.DETAILS_OPEN,
258
+ text=text_with_display,
259
+ assistant_id=sample_data["assistant_id"],
260
+ )
261
+
262
+ assert "Before" in result
263
+ assert "After" in result
264
+ assert sample_data["answer"] not in result
265
+
266
+ def test_remove_sub_agent_answer_from_text_details_closed(self, sample_data):
267
+ """Test removing sub-agent answer from text with DETAILS_CLOSED mode."""
268
+ # First build the display
269
+ display_html = build_sub_agent_answer_display(
270
+ display_name=sample_data["display_name"],
271
+ display_mode=ResponseDisplayMode.DETAILS_CLOSED,
272
+ answer=sample_data["answer"],
273
+ assistant_id=sample_data["assistant_id"],
274
+ )
275
+
276
+ text_with_display = f"Before\n{display_html}\nAfter"
277
+
278
+ result = remove_sub_agent_answer_from_text(
279
+ display_mode=ResponseDisplayMode.DETAILS_CLOSED,
280
+ text=text_with_display,
281
+ assistant_id=sample_data["assistant_id"],
282
+ )
283
+
284
+ assert "Before" in result
285
+ assert "After" in result
286
+ assert sample_data["answer"] not in result
287
+
288
+ def test_remove_sub_agent_answer_from_text_hidden_mode(self, sample_data):
289
+ """Test removing sub-agent answer from text with HIDDEN mode."""
290
+ text = "Some text here"
291
+ result = remove_sub_agent_answer_from_text(
292
+ display_mode=ResponseDisplayMode.HIDDEN,
293
+ text=text,
294
+ assistant_id=sample_data["assistant_id"],
295
+ )
296
+
297
+ assert result == text
298
+
299
+ def test_roundtrip_build_and_remove(self, sample_data):
300
+ """Test that building and then removing display results in clean text."""
301
+ original_text = "This is the original text."
302
+
303
+ # Build display
304
+ display_html = build_sub_agent_answer_display(
305
+ display_name=sample_data["display_name"],
306
+ display_mode=ResponseDisplayMode.DETAILS_OPEN,
307
+ answer=sample_data["answer"],
308
+ assistant_id=sample_data["assistant_id"],
309
+ )
310
+
311
+ # Insert into text
312
+ text_with_display = f"{original_text}\n{display_html}"
313
+
314
+ # Remove display
315
+ result = remove_sub_agent_answer_from_text(
316
+ display_mode=ResponseDisplayMode.DETAILS_OPEN,
317
+ text=text_with_display,
318
+ assistant_id=sample_data["assistant_id"],
319
+ )
320
+
321
+ # Should be back to original (with some whitespace differences)
322
+ assert original_text in result.strip()
323
+ assert sample_data["answer"] not in result
324
+
325
+
326
+ class TestEdgeCases:
327
+ """Test suite for edge cases and error conditions."""
328
+
329
+ def test_empty_strings(self):
330
+ """Test handling of empty strings."""
331
+ handler = _DetailsResponseDisplayHandler(mode="open")
332
+
333
+ result = handler.build_response_display(
334
+ display_name="", assistant_id="test", answer=""
335
+ )
336
+
337
+ assert "<details open>" in result
338
+ assert "<summary></summary>" in result
339
+
340
+ def test_multiline_content(self):
341
+ """Test handling of multiline content."""
342
+ handler = _DetailsResponseDisplayHandler(mode="open")
343
+
344
+ multiline_answer = """Line 1
345
+ Line 2
346
+ Line 3 with spaces
347
+
348
+ Line 5 after blank line"""
349
+
350
+ result = handler.build_response_display(
351
+ display_name="Multi-line Test",
352
+ assistant_id="test_ml",
353
+ answer=multiline_answer,
354
+ )
355
+
356
+ assert multiline_answer in result
357
+
358
+ # Test removal
359
+ text_with_display = f"Before\n{result}\nAfter"
360
+ clean_result = handler.remove_response_display(
361
+ assistant_id="test_ml", text=text_with_display
362
+ )
363
+
364
+ assert "Before" in clean_result
365
+ assert "After" in clean_result
366
+ assert multiline_answer not in clean_result
367
+
368
+ def test_html_content_in_answer(self):
369
+ """Test handling of HTML content within the answer."""
370
+ handler = _DetailsResponseDisplayHandler(mode="open")
371
+
372
+ html_answer = "<p>This is <strong>bold</strong> text with <em>emphasis</em></p>"
373
+
374
+ result = handler.build_response_display(
375
+ display_name="HTML Test", assistant_id="test_html", answer=html_answer
376
+ )
377
+
378
+ assert html_answer in result
379
+
380
+ # Test removal
381
+ text_with_display = f"Before\n{result}\nAfter"
382
+ clean_result = handler.remove_response_display(
383
+ assistant_id="test_html", text=text_with_display
384
+ )
385
+
386
+ assert "Before" in clean_result
387
+ assert "After" in clean_result
388
+ assert html_answer not in clean_result
389
+
390
+ def test_unicode_content(self):
391
+ """Test handling of Unicode content."""
392
+ handler = _DetailsResponseDisplayHandler(mode="open")
393
+
394
+ unicode_content = "Testing Unicode: 你好 🌟 café naïve résumé"
395
+
396
+ result = handler.build_response_display(
397
+ display_name="Unicode Test",
398
+ assistant_id="test_unicode",
399
+ answer=unicode_content,
400
+ )
401
+
402
+ assert unicode_content in result
403
+
404
+ # Test removal
405
+ text_with_display = f"Before\n{result}\nAfter"
406
+ clean_result = handler.remove_response_display(
407
+ assistant_id="test_unicode", text=text_with_display
408
+ )
409
+
410
+ assert "Before" in clean_result
411
+ assert "After" in clean_result
412
+ assert unicode_content not in clean_result
@@ -1,8 +1,13 @@
1
+ from typing import Protocol, override
2
+
3
+ import unique_sdk
1
4
  from pydantic import Field, create_model
2
5
  from unique_sdk.utils.chat_in_space import send_message_and_wait_for_completion
3
6
 
4
7
  from unique_toolkit.agentic.evaluation.schemas import EvaluationMetricName
5
- from unique_toolkit.agentic.tools.a2a.config import SubAgentToolConfig
8
+ from unique_toolkit.agentic.tools.a2a.config import (
9
+ SubAgentToolConfig,
10
+ )
6
11
  from unique_toolkit.agentic.tools.a2a.memory import (
7
12
  get_sub_agent_short_term_memory_manager,
8
13
  )
@@ -10,6 +15,7 @@ from unique_toolkit.agentic.tools.a2a.schema import (
10
15
  SubAgentShortTermMemorySchema,
11
16
  SubAgentToolInput,
12
17
  )
18
+ from unique_toolkit.agentic.tools.agent_chunks_hanlder import AgentChunksHandler
13
19
  from unique_toolkit.agentic.tools.factory import ToolFactory
14
20
  from unique_toolkit.agentic.tools.schemas import ToolCallResponse
15
21
  from unique_toolkit.agentic.tools.tool import Tool
@@ -20,9 +26,17 @@ from unique_toolkit.agentic.tools.tool_progress_reporter import (
20
26
  from unique_toolkit.app import ChatEvent
21
27
  from unique_toolkit.language_model import (
22
28
  LanguageModelFunction,
23
- LanguageModelMessage,
24
29
  LanguageModelToolDescription,
25
30
  )
31
+ from unique_toolkit.language_model.schemas import LanguageModelMessage
32
+
33
+
34
+ class SubAgentResponseSubscriber(Protocol):
35
+ def notify_sub_agent_response(
36
+ self,
37
+ sub_agent_assistant_id: str,
38
+ response: unique_sdk.Space.Message,
39
+ ) -> None: ...
26
40
 
27
41
 
28
42
  class SubAgentTool(Tool[SubAgentToolConfig]):
@@ -33,11 +47,15 @@ class SubAgentTool(Tool[SubAgentToolConfig]):
33
47
  configuration: SubAgentToolConfig,
34
48
  event: ChatEvent,
35
49
  tool_progress_reporter: ToolProgressReporter | None = None,
50
+ name: str = "SubAgentTool",
51
+ display_name: str = "SubAgentTool",
36
52
  ):
37
53
  super().__init__(configuration, event, tool_progress_reporter)
38
54
  self._user_id = event.user_id
39
55
  self._company_id = event.company_id
40
- self.name = configuration.name
56
+
57
+ self.name = name
58
+ self._display_name = display_name
41
59
 
42
60
  self._short_term_memory_manager = get_sub_agent_short_term_memory_manager(
43
61
  company_id=self._company_id,
@@ -45,6 +63,13 @@ class SubAgentTool(Tool[SubAgentToolConfig]):
45
63
  chat_id=event.payload.chat_id,
46
64
  assistant_id=self.config.assistant_id,
47
65
  )
66
+ self._subscribers: list[SubAgentResponseSubscriber] = []
67
+
68
+ def display_name(self) -> str:
69
+ return self._display_name
70
+
71
+ def subscribe(self, subscriber: SubAgentResponseSubscriber) -> None:
72
+ self._subscribers.append(subscriber)
48
73
 
49
74
  def tool_description(self) -> LanguageModelToolDescription:
50
75
  tool_input_model_with_description = create_model(
@@ -105,52 +130,96 @@ class SubAgentTool(Tool[SubAgentToolConfig]):
105
130
  SubAgentShortTermMemorySchema(chat_id=chat_id)
106
131
  )
107
132
 
108
- async def run(self, tool_call: LanguageModelFunction) -> ToolCallResponse:
109
- tool_input = SubAgentToolInput.model_validate(tool_call.arguments)
110
-
111
- if self.tool_progress_reporter:
133
+ async def _notify_progress(
134
+ self,
135
+ tool_call: LanguageModelFunction,
136
+ message: str,
137
+ state: ProgressState,
138
+ ) -> None:
139
+ if self.tool_progress_reporter is not None:
112
140
  await self.tool_progress_reporter.notify_from_tool_call(
113
141
  tool_call=tool_call,
114
- name=f"{self.name}",
115
- message=f"{tool_input.user_message}",
116
- state=ProgressState.RUNNING,
142
+ name=self._display_name,
143
+ message=message,
144
+ state=state,
145
+ )
146
+
147
+ async def _execute_and_handle_timeout(
148
+ self, tool_user_message: str, chat_id: str, tool_call: LanguageModelFunction
149
+ ) -> unique_sdk.Space.Message:
150
+ try:
151
+ return await send_message_and_wait_for_completion(
152
+ user_id=self._user_id,
153
+ assistant_id=self.config.assistant_id,
154
+ company_id=self._company_id,
155
+ text=tool_user_message,
156
+ chat_id=chat_id,
157
+ poll_interval=self.config.poll_interval,
158
+ max_wait=self.config.max_wait,
159
+ stop_condition="completedAt",
160
+ )
161
+ except TimeoutError as e:
162
+ await self._notify_progress(
163
+ tool_call=tool_call,
164
+ message="Timeout while waiting for response from sub agent.",
165
+ state=ProgressState.FAILED,
117
166
  )
118
167
 
168
+ raise TimeoutError(
169
+ "Timeout while waiting for response from sub agent. The user should consider increasing the max wait time.",
170
+ ) from e
171
+
172
+ def _notify_subscribers(self, response: unique_sdk.Space.Message) -> None:
173
+ for subsciber in self._subscribers:
174
+ subsciber.notify_sub_agent_response(
175
+ sub_agent_assistant_id=self.config.assistant_id,
176
+ response=response,
177
+ )
178
+
179
+ async def run(self, tool_call: LanguageModelFunction) -> ToolCallResponse:
180
+ tool_input = SubAgentToolInput.model_validate(tool_call.arguments)
181
+
182
+ await self._notify_progress(
183
+ tool_call=tool_call,
184
+ message=tool_input.user_message,
185
+ state=ProgressState.RUNNING,
186
+ )
187
+
119
188
  # Check if there is a saved chat id in short term memory
120
189
  chat_id = await self._get_chat_id()
121
190
 
122
- response = await send_message_and_wait_for_completion(
123
- user_id=self._user_id,
124
- assistant_id=self.config.assistant_id,
125
- company_id=self._company_id,
126
- text=tool_input.user_message, # type: ignore
191
+ response = await self._execute_and_handle_timeout(
192
+ tool_user_message=tool_input.user_message, # type: ignore
127
193
  chat_id=chat_id, # type: ignore
128
- poll_interval=self.config.poll_interval,
129
- max_wait=self.config.max_wait,
194
+ tool_call=tool_call,
130
195
  )
131
196
 
132
- if chat_id is None:
197
+ self._notify_subscribers(response)
198
+
199
+ if chat_id is None and self.config.reuse_chat:
133
200
  await self._save_chat_id(response["chatId"])
134
201
 
135
202
  if response["text"] is None:
136
203
  raise ValueError("No response returned from sub agent")
137
204
 
138
- self._text = response["text"]
205
+ await self._notify_progress(
206
+ tool_call=tool_call,
207
+ message=tool_input.user_message,
208
+ state=ProgressState.FINISHED,
209
+ )
210
+
139
211
  return ToolCallResponse(
140
212
  id=tool_call.id, # type: ignore
141
213
  name=tool_call.name,
142
214
  content=response["text"],
143
215
  )
144
216
 
217
+ @override
145
218
  def get_tool_call_result_for_loop_history(
146
219
  self,
147
220
  tool_response: ToolCallResponse,
148
- ) -> LanguageModelMessage:
149
- return ToolCallResponse(
150
- id=tool_response.id, # type: ignore
151
- name=tool_response.name,
152
- content=tool_response.content,
153
- )
221
+ agent_chunks_handler: AgentChunksHandler,
222
+ ) -> LanguageModelMessage: ... # Empty as method is deprecated
154
223
 
155
224
 
156
225
  ToolFactory.register_tool(SubAgentTool, SubAgentToolConfig)
@@ -78,8 +78,17 @@ class ToolBuildConfig(BaseModel):
78
78
  # Configuration can remain as a dict
79
79
  return value
80
80
 
81
+ is_sub_agent_tool = (
82
+ value.get("is_sub_agent") or value.get("isSubAgent") or False
83
+ )
84
+
81
85
  configuration = value.get("configuration", {})
82
- if isinstance(configuration, dict):
86
+
87
+ if is_sub_agent_tool:
88
+ from unique_toolkit.agentic.tools.a2a.config import SubAgentToolConfig
89
+
90
+ config = SubAgentToolConfig.model_validate(configuration)
91
+ elif isinstance(configuration, dict):
83
92
  # Local import to avoid circular import at module import time
84
93
  from unique_toolkit.agentic.tools.factory import ToolFactory
85
94
 
@@ -6,6 +6,7 @@ from pydantic import BaseModel, Field
6
6
 
7
7
  from unique_toolkit.agentic.evaluation.schemas import EvaluationMetricName
8
8
  from unique_toolkit.agentic.tools.a2a.manager import A2AManager
9
+ from unique_toolkit.agentic.tools.a2a.service import SubAgentTool
9
10
  from unique_toolkit.agentic.tools.config import ToolBuildConfig
10
11
  from unique_toolkit.agentic.tools.factory import ToolFactory
11
12
  from unique_toolkit.agentic.tools.mcp.manager import MCPManager
@@ -113,6 +114,7 @@ class ToolManager:
113
114
  mcp_tools = self._mcp_manager.get_all_mcp_tools()
114
115
  # Combine both types of tools
115
116
  self.available_tools = internal_tools + mcp_tools + sub_agents
117
+ self._sub_agents = sub_agents
116
118
 
117
119
  for t in self.available_tools:
118
120
  if not t.is_enabled():
@@ -136,6 +138,10 @@ class ToolManager:
136
138
 
137
139
  self._tools.append(t)
138
140
 
141
+ @property
142
+ def sub_agents(self) -> list[SubAgentTool]:
143
+ return self._sub_agents
144
+
139
145
  def get_evaluation_check_list(self) -> list[EvaluationMetricName]:
140
146
  return list(self._tool_evaluation_check_list)
141
147
 
@@ -143,7 +149,7 @@ class ToolManager:
143
149
  self._logger.info(f"Loaded tools: {[tool.name for tool in self._tools]}")
144
150
 
145
151
  def get_tools(self) -> list[Tool]:
146
- return self._tools
152
+ return self._tools # type: ignore
147
153
 
148
154
  def get_tool_by_name(self, name: str) -> Tool | None:
149
155
  for tool in self._tools:
@@ -80,13 +80,14 @@ class LanguageModelFunction(BaseModel):
80
80
 
81
81
  @field_validator("id", mode="before")
82
82
  def randomize_id(cls, value):
83
- return uuid4().hex
83
+ if not value:
84
+ return uuid4().hex
85
+ else:
86
+ return value
84
87
 
85
88
  @model_serializer()
86
89
  def serialize_model(self):
87
90
  seralization = {}
88
- if self.id:
89
- seralization["id"] = self.id
90
91
  seralization["name"] = self.name
91
92
  if self.arguments:
92
93
  seralization["arguments"] = json.dumps(self.arguments)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: unique_toolkit
3
- Version: 1.1.8
3
+ Version: 1.2.0
4
4
  Summary:
5
5
  License: Proprietary
6
6
  Author: Cedric Klinkert
@@ -118,6 +118,13 @@ All notable changes to this project will be documented in this file.
118
118
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
119
119
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
120
120
 
121
+
122
+ ## [1.2.0] - 2025-09-24
123
+ - Add ability to display sub agent responses in the chat.
124
+
125
+ ## [1.1.9] - 2025-09-24
126
+ - Fix bug in `LanguageModelFunction` to extend support mistral tool calling.
127
+
121
128
  ## [1.1.8] - 2025-09-23
122
129
  - Revert last to version 1.1.6
123
130
 
@@ -44,19 +44,23 @@ unique_toolkit/agentic/history_manager/history_construction_with_contents.py,sha
44
44
  unique_toolkit/agentic/history_manager/history_manager.py,sha256=qo6vjEXueCXUxHSrawYeMmFn7tuTEXGTWnBVb0H8bDY,8423
45
45
  unique_toolkit/agentic/history_manager/loop_token_reducer.py,sha256=9ZWh3dfs7SGsU33bxzKo9x94haEhc7Rerk9PtfFzcxg,18474
46
46
  unique_toolkit/agentic/history_manager/utils.py,sha256=NDSSz0Jp3oVJU3iKlVScmM1AOe-6hTiVjLr16DUPsV0,5656
47
- unique_toolkit/agentic/postprocessor/postprocessor_manager.py,sha256=lUCPQsYIGjpJdw7ZqEG3JcGc3B2bFBFqH9Rs1KxWzaM,4221
47
+ unique_toolkit/agentic/postprocessor/postprocessor_manager.py,sha256=GDzJhaoOUwxZ37IINkQ7au4CHmAOFS5miP2lqv8ZwZA,4277
48
48
  unique_toolkit/agentic/reference_manager/reference_manager.py,sha256=1GeoFX1-RLdTcns1358GJADDSAcTAM2J0jJJpln08qo,4005
49
49
  unique_toolkit/agentic/short_term_memory_manager/persistent_short_term_memory_manager.py,sha256=uF3HSoZF0hBfuNhIE9N8KRtuwDfpoeXUFVrv_cyZ3Sw,5839
50
50
  unique_toolkit/agentic/thinking_manager/thinking_manager.py,sha256=41QWFsdRrbWlQHBfYCFv726UDom4WbcvaRfjCmoUOQI,4183
51
51
  unique_toolkit/agentic/tools/__init__.py,sha256=-ToY9-Xiz0K7qCUydH1h1yG6n4h1hQS8sBuSVPNEq2Y,43
52
52
  unique_toolkit/agentic/tools/a2a/__init__.py,sha256=NdY0J33b1G4sbx6UWwNS74JVSAeEtu8u_iEXOT64Uq0,187
53
- unique_toolkit/agentic/tools/a2a/config.py,sha256=6A75FdiCOh35D4lJbmcUZNiYQ6v32T-kWYMA4v3NTl8,880
54
- unique_toolkit/agentic/tools/a2a/manager.py,sha256=JYX8Sh4zWdMG9altCkAIwUxwqC0kz_W_F3pSI89qKRw,1706
53
+ unique_toolkit/agentic/tools/a2a/config.py,sha256=m6INkff6jHEMB_DPm8MmtXfrGaMy1TIwXd1vFwHBFAU,1312
54
+ unique_toolkit/agentic/tools/a2a/manager.py,sha256=yuuQuBrAcsT3gAWEdxf6EvRnL_iWtvaK14lRs21w5PA,1665
55
55
  unique_toolkit/agentic/tools/a2a/memory.py,sha256=4VFBzITCv5E_8YCc4iF4Y6FhzplS2C-FZaZHdeC7DyA,1028
56
+ unique_toolkit/agentic/tools/a2a/postprocessing/__init__.py,sha256=9M5RRSO4gmQVI1gfve6MGEfVktT9WcELQFZ8Sv6xu4Y,160
57
+ unique_toolkit/agentic/tools/a2a/postprocessing/display.py,sha256=rlBWO2M8Lr5Kx-vmvwlV-vEu33BZE4votP-TMr3-3Dw,3366
58
+ unique_toolkit/agentic/tools/a2a/postprocessing/postprocessor.py,sha256=BWXFsv5akPhkNxRQ3JLktrBkoT1_66joTel_uM4BgRY,6963
59
+ unique_toolkit/agentic/tools/a2a/postprocessing/test/test_display.py,sha256=UVOd5tVTltngVfsdOn6cuyvG7QmRBkfgUj_SruTgmHI,15279
56
60
  unique_toolkit/agentic/tools/a2a/schema.py,sha256=T1l5z6trtPE5nhqPzt5tvfRNDhqL_ST1Wj7_lBWJ58g,304
57
- unique_toolkit/agentic/tools/a2a/service.py,sha256=7n7BkJxTR-hpoQczfHLK8eS0rtATL7-YARH-4netbbI,5339
61
+ unique_toolkit/agentic/tools/a2a/service.py,sha256=cACUPwWXzi_XfPlFqK0x6nawugx17Vq18-XRUbhgL4k,7669
58
62
  unique_toolkit/agentic/tools/agent_chunks_hanlder.py,sha256=x32Dp1Z8cVW5i-XzXbaMwX2KHPcNGmqEU-FB4AV9ZGo,1909
59
- unique_toolkit/agentic/tools/config.py,sha256=B9Tl0uJ81XB4SjSke4kSydame4glGT2s69ZXfr0ViZU,4634
63
+ unique_toolkit/agentic/tools/config.py,sha256=91Gw92YoTC6WeWa4lfOpXSvIYekCwELcVNkHZZEkW2o,4936
60
64
  unique_toolkit/agentic/tools/factory.py,sha256=Wt0IGSbLg8ZTq5PU9p_JTW0LtNATWLpc3336irJKXlM,1277
61
65
  unique_toolkit/agentic/tools/mcp/__init__.py,sha256=RLF_p-LDRC7GhiB3fdCi4u3bh6V9PY_w26fg61BLyco,122
62
66
  unique_toolkit/agentic/tools/mcp/manager.py,sha256=DPYwwDe6RSZyuPaxn-je49fP_qOOs0ZV46EM6GZcV4c,2748
@@ -66,7 +70,7 @@ unique_toolkit/agentic/tools/schemas.py,sha256=0ZR8xCdGj1sEdPE0lfTIG2uSR5zqWoprU
66
70
  unique_toolkit/agentic/tools/test/test_mcp_manager.py,sha256=9F7FjpYKeOkg2Z4bt2H1WGaxu9fzB-1iiE-b7g3KzQk,15724
67
71
  unique_toolkit/agentic/tools/test/test_tool_progress_reporter.py,sha256=dod5QPqgGUInVAGXAbsAKNTEypIi6pUEWhDbJr9YfUU,6307
68
72
  unique_toolkit/agentic/tools/tool.py,sha256=m56VLxiHuKU2_J5foZp00xhm5lTxWEW7zRLGbIE9ssU,6744
69
- unique_toolkit/agentic/tools/tool_manager.py,sha256=E1eKZuc7eHPeGFqrGfdMGPNLqu3NYhrfKDjSJxMSuvU,10925
73
+ unique_toolkit/agentic/tools/tool_manager.py,sha256=l8OGQiSeMWqesnFQ4vmgnrSU4e6ipAbB2pq0HmQM4AE,11140
70
74
  unique_toolkit/agentic/tools/tool_progress_reporter.py,sha256=ixud9VoHey1vlU1t86cW0-WTvyTwMxNSWBon8I11SUk,7955
71
75
  unique_toolkit/agentic/tools/utils/__init__.py,sha256=iD1YYzf9LcJFv95Z8BqCAFSewNBabybZRZyvPKGfvro,27
72
76
  unique_toolkit/agentic/tools/utils/execution/__init__.py,sha256=OHiKpqBnfhBiEQagKVWJsZlHv8smPp5OI4dFIexzibw,37
@@ -117,7 +121,7 @@ unique_toolkit/language_model/functions.py,sha256=PNCmbYovhgMSkY89p7-3DunG6jIeka
117
121
  unique_toolkit/language_model/infos.py,sha256=eHln--Y5f6znFxknV6A8m-fRaEpH5-kmRh9m-ZWqco4,57188
118
122
  unique_toolkit/language_model/prompt.py,sha256=JSawaLjQg3VR-E2fK8engFyJnNdk21zaO8pPIodzN4Q,3991
119
123
  unique_toolkit/language_model/reference.py,sha256=nkX2VFz-IrUz8yqyc3G5jUMNwrNpxITBrMEKkbqqYoI,8583
120
- unique_toolkit/language_model/schemas.py,sha256=EOgy-p1GRcS46Sq0qEsN8MfOMl-KCcvEd9aCmqm9d08,16497
124
+ unique_toolkit/language_model/schemas.py,sha256=w23zH2OAYkTsS-wAqelUdhO9TCgis0TbFa8PszmhZYY,16501
121
125
  unique_toolkit/language_model/service.py,sha256=JkYGtCug8POQskTv_aoYkzTMOaPCWRM94y73o3bUttQ,12019
122
126
  unique_toolkit/language_model/utils.py,sha256=bPQ4l6_YO71w-zaIPanUUmtbXC1_hCvLK0tAFc3VCRc,1902
123
127
  unique_toolkit/protocols/support.py,sha256=V15WEIFKVMyF1QCnR8vIi4GrJy4dfTCB6d6JlqPZ58o,2341
@@ -128,7 +132,7 @@ unique_toolkit/short_term_memory/schemas.py,sha256=OhfcXyF6ACdwIXW45sKzjtZX_gkcJ
128
132
  unique_toolkit/short_term_memory/service.py,sha256=5PeVBu1ZCAfyDb2HLVvlmqSbyzBBuE9sI2o9Aajqjxg,8884
129
133
  unique_toolkit/smart_rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
130
134
  unique_toolkit/smart_rules/compile.py,sha256=cxWjb2dxEI2HGsakKdVCkSNi7VK9mr08w5sDcFCQyWI,9553
131
- unique_toolkit-1.1.8.dist-info/LICENSE,sha256=GlN8wHNdh53xwOPg44URnwag6TEolCjoq3YD_KrWgss,193
132
- unique_toolkit-1.1.8.dist-info/METADATA,sha256=PZk9DqoRKtH35hnqcqRyNkcy20mLwytAiuqB6EAJp28,33104
133
- unique_toolkit-1.1.8.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
134
- unique_toolkit-1.1.8.dist-info/RECORD,,
135
+ unique_toolkit-1.2.0.dist-info/LICENSE,sha256=GlN8wHNdh53xwOPg44URnwag6TEolCjoq3YD_KrWgss,193
136
+ unique_toolkit-1.2.0.dist-info/METADATA,sha256=Fp7cz1tbhox_Vcohuj8hL8ahu14rw0GJ6gUbL0t-T1c,33290
137
+ unique_toolkit-1.2.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
138
+ unique_toolkit-1.2.0.dist-info/RECORD,,