unique_toolkit 2026.36.0.dev0__py3-none-any.whl → 2026.36.0.dev1__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.
@@ -4,9 +4,10 @@ import json
4
4
  import logging
5
5
  import mimetypes
6
6
  import uuid
7
- from typing import Any, Dict
7
+ from typing import Any
8
8
 
9
9
  import unique_sdk
10
+ from pydantic import ValidationError
10
11
 
11
12
  from unique_toolkit.agentic.evaluation.schemas import EvaluationMetricName
12
13
  from unique_toolkit.agentic.feature_flags import FeatureFlagNames
@@ -21,6 +22,7 @@ from unique_toolkit.app.schemas import ChatEvent, McpServer, McpTool
21
22
  from unique_toolkit.chat.schemas import MessageLog, MessageLogStatus
22
23
  from unique_toolkit.chat.service import ChatService
23
24
  from unique_toolkit.content.functions import upload_content_from_bytes
25
+ from unique_toolkit.content.schemas import ContentChunk, ContentReference
24
26
  from unique_toolkit.experimental.resources.feature_flags import is_flag_enabled
25
27
  from unique_toolkit.language_model import LanguageModelService
26
28
  from unique_toolkit.language_model.schemas import (
@@ -30,6 +32,8 @@ from unique_toolkit.language_model.schemas import (
30
32
 
31
33
  logger = logging.getLogger(__name__)
32
34
 
35
+ REFERENCE_META_KEY = "unique.app/reference"
36
+
33
37
 
34
38
  class MCPToolWrapper(Tool[MCPToolConfig]):
35
39
  """Wrapper class for MCP tools that implements the Tool interface"""
@@ -72,7 +76,7 @@ class MCPToolWrapper(Tool[MCPToolConfig]):
72
76
  parameters=self._mcp_tool.input_schema,
73
77
  )
74
78
 
75
- def _json_schema_to_python_type(self, schema: Dict[str, Any]) -> type:
79
+ def _json_schema_to_python_type(self, schema: dict[str, Any]) -> type:
76
80
  """Convert JSON schema type to Python type"""
77
81
  json_type = schema.get("type", "string")
78
82
 
@@ -152,9 +156,14 @@ class MCPToolWrapper(Tool[MCPToolConfig]):
152
156
  # Use SDK to call the public API
153
157
  result = await self._call_mcp_tool_via_sdk(arguments)
154
158
 
159
+ content_chunks, references = self._extract_structured_references(result)
155
160
  content_str, image_data_urls = await asyncio.to_thread(
156
161
  self._process_mcp_result, result
157
162
  )
163
+ if content_chunks:
164
+ # Structured references already carry the text; keep any
165
+ # extracted images but drop the redundant flat text.
166
+ content_str = ""
158
167
 
159
168
  # TODO: Why result here not applied directly to the body of the tool_response? like so how does it know the results in the history?
160
169
  tool_response = ToolCallResponse(
@@ -167,6 +176,7 @@ class MCPToolWrapper(Tool[MCPToolConfig]):
167
176
  },
168
177
  error_message="",
169
178
  content=content_str,
179
+ content_chunks=content_chunks or None,
170
180
  image_data_urls=image_data_urls,
171
181
  )
172
182
 
@@ -187,6 +197,7 @@ class MCPToolWrapper(Tool[MCPToolConfig]):
187
197
  progress_message="_Completed MCP tool_",
188
198
  status=MessageLogStatus.COMPLETED,
189
199
  active_message_log=active_message_log,
200
+ references=references,
190
201
  )
191
202
 
192
203
  return tool_response
@@ -230,17 +241,19 @@ class MCPToolWrapper(Tool[MCPToolConfig]):
230
241
  progress_message: str | None = None,
231
242
  status: MessageLogStatus = MessageLogStatus.RUNNING,
232
243
  active_message_log: MessageLog | None = None,
244
+ references: list[ContentReference] | None = None,
233
245
  ) -> MessageLog | None:
234
246
  return self._message_step_logger.create_or_update_message_log(
235
247
  active_message_log=active_message_log,
236
248
  header=self.display_name(),
237
249
  progress_message=progress_message,
238
250
  status=status,
251
+ references=references or [],
239
252
  )
240
253
 
241
254
  def _extract_and_validate_arguments(
242
255
  self, tool_call: LanguageModelFunction
243
- ) -> Dict[str, Any]:
256
+ ) -> dict[str, Any]:
244
257
  """
245
258
  Extract and validate arguments from tool call, handling various formats robustly.
246
259
 
@@ -287,7 +300,39 @@ class MCPToolWrapper(Tool[MCPToolConfig]):
287
300
  f"Unexpected arguments type for MCP tool {self.name}: {type(raw_arguments)}"
288
301
  )
289
302
 
290
- def _process_mcp_result(self, result: Dict[str, Any]) -> tuple[str, list[str]]:
303
+ def _extract_structured_references(
304
+ self, result: dict[str, Any]
305
+ ) -> tuple[list[ContentChunk], list[ContentReference]]:
306
+ """Opt-in: content items with valid ``unique.app/reference`` + chunk ``_meta``."""
307
+ chunks: list[ContentChunk] = []
308
+ references: list[ContentReference] = []
309
+ content_items = self._extract_content_items(result)
310
+ for item in content_items:
311
+ if not isinstance(item, dict) or item.get("type") != "text":
312
+ continue
313
+ meta = item.get("_meta")
314
+ if not isinstance(meta, dict):
315
+ continue
316
+ ref_payload = meta.get(REFERENCE_META_KEY)
317
+ if not isinstance(ref_payload, dict):
318
+ continue
319
+ try:
320
+ reference = ContentReference.model_validate(ref_payload)
321
+ chunk = ContentChunk.model_validate(
322
+ {k: v for k, v in meta.items() if k != REFERENCE_META_KEY}
323
+ )
324
+ except ValidationError:
325
+ self.logger.warning(
326
+ "MCP tool %s: ignoring invalid %s metadata",
327
+ self.name,
328
+ REFERENCE_META_KEY,
329
+ )
330
+ continue
331
+ references.append(reference)
332
+ chunks.append(chunk)
333
+ return chunks, references
334
+
335
+ def _process_mcp_result(self, result: dict[str, Any]) -> tuple[str, list[str]]:
291
336
  """Parse MCP result content array, upload images, return content string and data URLs."""
292
337
  content_items = self._extract_content_items(result)
293
338
  if not content_items:
@@ -327,7 +372,7 @@ class MCPToolWrapper(Tool[MCPToolConfig]):
327
372
  content_str = json.dumps(result)
328
373
  return content_str, image_data_urls
329
374
 
330
- def _extract_content_items(self, result: Dict[str, Any]) -> list[Any]:
375
+ def _extract_content_items(self, result: dict[str, Any]) -> list[Any]:
331
376
  """Extract content array from MCP result (handles various response shapes)."""
332
377
  if "content" in result and isinstance(result["content"], list):
333
378
  return result["content"]
@@ -378,7 +423,7 @@ class MCPToolWrapper(Tool[MCPToolConfig]):
378
423
 
379
424
  return data_url, content_id
380
425
 
381
- async def _call_mcp_tool_via_sdk(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
426
+ async def _call_mcp_tool_via_sdk(self, arguments: dict[str, Any]) -> dict[str, Any]:
382
427
  """Call MCP tool via SDK to public API"""
383
428
  try:
384
429
  self.logger.info(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: unique-toolkit
3
- Version: 2026.36.0.dev0
3
+ Version: 2026.36.0.dev1
4
4
  Summary:
5
5
  Author: Cedric Klinkert, Martin Fadler, Sadique Sheik, Fabian Schläpfer, Pascal Hauri, Ejiroghene Oki-Peter, Rami Azouz, Ricardo Visini, Sanzio Monti
6
6
  Author-email: Cedric Klinkert <cedric.klinkert@unique.ch>, Martin Fadler <martin.fadler@unique.ch>, Sadique Sheik <sadique@unique.ch>, Fabian Schläpfer <fabian@unique.ch>, Pascal Hauri <pascal@unique.ch>, Ejiroghene Oki-Peter <oki-peter.ejiroghene@unique.ai>, Rami Azouz <rami.ext@unique.ch>, Ricardo Visini <ricardo.visini@unique.ch>, Sanzio Monti <sanzio.monti@unique.ai>
@@ -201,7 +201,7 @@ unique_toolkit/agentic/tools/factory.py,sha256=YK9uh2EqotQFb9stsXs-A8lsNaxklTCd_
201
201
  unique_toolkit/agentic/tools/mcp/__init__.py,sha256=RLF_p-LDRC7GhiB3fdCi4u3bh6V9PY_w26fg61BLyco,122
202
202
  unique_toolkit/agentic/tools/mcp/manager.py,sha256=EfWbfNhB8Lnx2EqBgUXhK7klFLaBv2eezvYq8J0TXyo,3808
203
203
  unique_toolkit/agentic/tools/mcp/models.py,sha256=DaBVzwJ4dqtgzm8Mv0-NOGBU04zyVe-RgWntBhkpmnE,742
204
- unique_toolkit/agentic/tools/mcp/tool_wrapper.py,sha256=fE5nVALJ-wVZkkbbJ_ySRLBhAAR6sCAAu1lUxQQVWC4,15596
204
+ unique_toolkit/agentic/tools/mcp/tool_wrapper.py,sha256=-WZaPjhbxozrPWgwZDD48hfYIp7s1cGAKU3IXqLXFVA,17575
205
205
  unique_toolkit/agentic/tools/names.py,sha256=MDF0ONLrxeC-iz--EtyrHAox_RN6zasxo5MSbXQYGyw,412
206
206
  unique_toolkit/agentic/tools/openai_builtin/__init__.py,sha256=Q6d9mhsLRVF3erT2yMxLld62D3FTZ8nbovI4vLv4Nso,701
207
207
  unique_toolkit/agentic/tools/openai_builtin/base.py,sha256=QWF-GL_fkJqzGPpxvtg69-TB_IjsrxokeJVQESzH-18,1415
@@ -420,6 +420,6 @@ unique_toolkit/short_term_memory/service.py,sha256=2JMBEBn6GoyK-a0xvdcWpPft-3C74
420
420
  unique_toolkit/smart_rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
421
421
  unique_toolkit/smart_rules/compile.py,sha256=vRe0aMJWuVIK-VsqomgxQImzke7MDmwgSxxJfYCa7zw,1386
422
422
  unique_toolkit/test_utilities/events.py,sha256=FLrhMh0A_mQutAUjoD0FdMcVQ4Gq31r35OVGzRjvLro,6760
423
- unique_toolkit-2026.36.0.dev0.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
424
- unique_toolkit-2026.36.0.dev0.dist-info/METADATA,sha256=FDHFO1DJ6xFdOS6tl_Nise6nYmhJmGppXAW5CkEmlL4,7821
425
- unique_toolkit-2026.36.0.dev0.dist-info/RECORD,,
423
+ unique_toolkit-2026.36.0.dev1.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
424
+ unique_toolkit-2026.36.0.dev1.dist-info/METADATA,sha256=TRfcAs3vlbrpma_tG96J6ZZq6eAu9L9NWGIrq2Y2eVY,7821
425
+ unique_toolkit-2026.36.0.dev1.dist-info/RECORD,,