unique-sdk 2026.30.0.dev3__tar.gz → 2026.30.0.dev5__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.
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/PKG-INFO +1 -1
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/pyproject.toml +1 -1
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/cli/commands/mcp.py +88 -6
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/cli/skills/unique-cli-search/SKILL.md +1 -37
- unique_sdk-2026.30.0.dev5/unique_sdk/cli/skills/unique-cli-uploaded-search/SKILL.md +113 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/cli/state.py +10 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/README.md +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/__init__.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/_api_requestor.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/_api_resource.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/_api_version.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/_error.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/_http_client.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/_list_object.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/_object_classes.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/_request_options.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/_unique_object.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/_unique_ql.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/_unique_response.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/_util.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/_version.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/_webhook.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/api_resources/__init__.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/api_resources/_acronyms.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/api_resources/_agentic_table.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/api_resources/_analytics_order.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/api_resources/_benchmarking.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/api_resources/_briefing.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/api_resources/_chat_completion.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/api_resources/_content.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/api_resources/_dynamic_frontend.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/api_resources/_elicitation.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/api_resources/_embedding.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/api_resources/_event.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/api_resources/_folder.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/api_resources/_group.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/api_resources/_integrated.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/api_resources/_llm_models.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/api_resources/_mcp.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/api_resources/_message.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/api_resources/_message_assessment.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/api_resources/_message_execution.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/api_resources/_message_log.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/api_resources/_message_tool.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/api_resources/_module.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/api_resources/_scheduled_task.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/api_resources/_search.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/api_resources/_search_string.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/api_resources/_short_term_memory.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/api_resources/_space.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/api_resources/_user.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/api_resources/_web_search.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/cli/__init__.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/cli/__main__.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/cli/cli.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/cli/commands/__init__.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/cli/commands/_citation_manifest.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/cli/commands/browser.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/cli/commands/cite_file.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/cli/commands/dynamic_frontend.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/cli/commands/elicitation.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/cli/commands/files.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/cli/commands/folders.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/cli/commands/navigation.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/cli/commands/read.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/cli/commands/scheduled_tasks.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/cli/commands/search.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/cli/commands/subagent.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/cli/commands/web_search.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/cli/commands/web_search_config.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/cli/config.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/cli/formatting.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/cli/metadata_filter.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/cli/shell.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/cli/skills/unique-cli-dynamic-frontend/SKILL.md +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/cli/skills/unique-cli-elicitation/SKILL.md +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/cli/skills/unique-cli-file-management/SKILL.md +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/cli/skills/unique-cli-mcp/SKILL.md +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/cli/skills/unique-cli-scheduled-tasks/SKILL.md +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/cli/skills/unique-cli-subagent/SKILL.md +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/cli/skills/unique-cli-web-search/SKILL.md +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/utils/analytics_order_run.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/utils/benchmarking_run.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/utils/chat_history.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/utils/chat_in_space.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/utils/file_io.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/utils/sources.py +0 -0
- {unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/utils/token.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: unique-sdk
|
|
3
|
-
Version: 2026.30.0.
|
|
3
|
+
Version: 2026.30.0.dev5
|
|
4
4
|
Summary:
|
|
5
5
|
Author: Martin Fadler, Konstantin Krauss, Andreas Hauri
|
|
6
6
|
Author-email: Martin Fadler <martin.fadler@unique.ch>, Konstantin Krauss <konstantin@unique.ch>, Andreas Hauri <andreas@unique.ch>
|
|
@@ -45,6 +45,13 @@ _MCP_OUTPUT_TEXT_CHAR_LIMIT = 200_000
|
|
|
45
45
|
_MCP_REFS_LOG_RELATIVE_PATH = Path(".unique") / "mcp-refs.jsonl"
|
|
46
46
|
_MCP_REFS_LOCK_FILENAME = "mcp-refs.lock"
|
|
47
47
|
_MCP_SNIPPET_CHAR_LIMIT = 300
|
|
48
|
+
# Writer-side cap on the per-item ``text`` recorded in the refs manifest — the
|
|
49
|
+
# cited item's underlying text, consumed by the runner's hallucination check to
|
|
50
|
+
# ground each ``[mcpsourceN]`` citation on what was actually retrieved
|
|
51
|
+
# (UN-22762). Half the flat-output cap (``_MCP_OUTPUT_TEXT_CHAR_LIMIT``): one
|
|
52
|
+
# cited item (a page, an issue record) rarely exceeds it, and the eval side
|
|
53
|
+
# bounds the combined cited-text payload separately.
|
|
54
|
+
_MCP_REF_TEXT_CHAR_LIMIT = 100_000
|
|
48
55
|
|
|
49
56
|
# Keys an MCP tool's JSON result commonly uses for a record's human title.
|
|
50
57
|
_TITLE_KEYS = ("title", "name", "displayName", "subject", "summary", "key")
|
|
@@ -247,6 +254,7 @@ def _titles_from_json(text: str) -> list[dict[str, Any]]:
|
|
|
247
254
|
"title": title,
|
|
248
255
|
"snippet": None,
|
|
249
256
|
"details": _details_from_json(entry),
|
|
257
|
+
"text": _record_text(entry),
|
|
250
258
|
}
|
|
251
259
|
)
|
|
252
260
|
return items
|
|
@@ -350,6 +358,31 @@ def _first_text_title(response: Any, max_chars: int) -> str | None:
|
|
|
350
358
|
return None
|
|
351
359
|
|
|
352
360
|
|
|
361
|
+
def _all_text_blocks(response: Any) -> str | None:
|
|
362
|
+
"""Concatenation of every text block — the underlying text of a single-item
|
|
363
|
+
result (e.g. a fetched document) recorded as that item's ground truth."""
|
|
364
|
+
texts: list[str] = []
|
|
365
|
+
for block in getattr(response, "content", None) or []:
|
|
366
|
+
if not isinstance(block, dict) or block.get("type") != "text":
|
|
367
|
+
continue
|
|
368
|
+
text = block.get("text")
|
|
369
|
+
if isinstance(text, str) and text.strip():
|
|
370
|
+
texts.append(text)
|
|
371
|
+
return "\n\n".join(texts) or None
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def _record_text(record: Any) -> str | None:
|
|
375
|
+
"""A record's underlying text for citation grounding: the record itself
|
|
376
|
+
when it is a plain string, else the serialized record — which carries the
|
|
377
|
+
titled field plus all metadata the agent may cite."""
|
|
378
|
+
if isinstance(record, str):
|
|
379
|
+
return record or None
|
|
380
|
+
try:
|
|
381
|
+
return json.dumps(record, ensure_ascii=False, default=str)
|
|
382
|
+
except (TypeError, ValueError):
|
|
383
|
+
return None
|
|
384
|
+
|
|
385
|
+
|
|
353
386
|
def _extract_with_reference_mapping(
|
|
354
387
|
response: Any, mapping: dict[str, Any]
|
|
355
388
|
) -> list[dict[str, Any]]:
|
|
@@ -409,6 +442,7 @@ def _extract_with_reference_mapping(
|
|
|
409
442
|
"title": title,
|
|
410
443
|
"snippet": None,
|
|
411
444
|
"details": str(details).strip() if details else None,
|
|
445
|
+
"text": _record_text(record),
|
|
412
446
|
}
|
|
413
447
|
)
|
|
414
448
|
if items:
|
|
@@ -421,7 +455,14 @@ def _extract_with_reference_mapping(
|
|
|
421
455
|
if not records and title_from_text:
|
|
422
456
|
title = _first_text_title(response, title_max_chars)
|
|
423
457
|
if title:
|
|
424
|
-
return [
|
|
458
|
+
return [
|
|
459
|
+
{
|
|
460
|
+
"title": title,
|
|
461
|
+
"snippet": None,
|
|
462
|
+
"details": None,
|
|
463
|
+
"text": _all_text_blocks(response),
|
|
464
|
+
}
|
|
465
|
+
]
|
|
425
466
|
return items
|
|
426
467
|
|
|
427
468
|
|
|
@@ -431,8 +472,9 @@ def _extract_mcp_citation_items(
|
|
|
431
472
|
tool_name: str,
|
|
432
473
|
server_name: str | None,
|
|
433
474
|
reference_mapping: dict[str, Any] | None = None,
|
|
475
|
+
fallback_text: str | None = None,
|
|
434
476
|
) -> list[dict[str, Any]]:
|
|
435
|
-
"""Context for what the tool retrieved: ``{title, snippet}`` per item.
|
|
477
|
+
"""Context for what the tool retrieved: ``{title, snippet, text}`` per item.
|
|
436
478
|
|
|
437
479
|
An optional admin ``reference_mapping`` is applied first (deterministic
|
|
438
480
|
destructuring of a list result); when it yields nothing we fall back to the
|
|
@@ -441,6 +483,12 @@ def _extract_mcp_citation_items(
|
|
|
441
483
|
JSON-in-text). No URLs are extracted — the chip is display-only. Falls back
|
|
442
484
|
to a single title-less item (the runner names it after the tool) when the
|
|
443
485
|
result carries no recognizable title.
|
|
486
|
+
|
|
487
|
+
``text`` is the item's underlying retrieved text (the serialized record, a
|
|
488
|
+
fetched document body, or — for the title-less fallback — ``fallback_text``,
|
|
489
|
+
the whole formatted output). The runner grounds the hallucination check for
|
|
490
|
+
each cited ``[mcpsourceN]`` on it. A ``resource_link`` carries no body, so
|
|
491
|
+
its ``text`` is the link description only.
|
|
444
492
|
"""
|
|
445
493
|
if reference_mapping:
|
|
446
494
|
mapped = _extract_with_reference_mapping(response, reference_mapping)
|
|
@@ -457,7 +505,11 @@ def _extract_mcp_citation_items(
|
|
|
457
505
|
name = (block.get("name") or "").strip()
|
|
458
506
|
if name:
|
|
459
507
|
items.append(
|
|
460
|
-
{
|
|
508
|
+
{
|
|
509
|
+
"title": name,
|
|
510
|
+
"snippet": _snippet(block.get("description")),
|
|
511
|
+
"text": block.get("description") or None,
|
|
512
|
+
}
|
|
461
513
|
)
|
|
462
514
|
|
|
463
515
|
if not items:
|
|
@@ -467,7 +519,7 @@ def _extract_mcp_citation_items(
|
|
|
467
519
|
|
|
468
520
|
if not items:
|
|
469
521
|
# No recognizable title — one chip named after the tool itself.
|
|
470
|
-
items.append({"title": None, "snippet": None})
|
|
522
|
+
items.append({"title": None, "snippet": None, "text": fallback_text})
|
|
471
523
|
|
|
472
524
|
return items
|
|
473
525
|
|
|
@@ -489,6 +541,16 @@ def _item_dedup_key(tool_name: str, item: dict[str, Any]) -> str:
|
|
|
489
541
|
return f"tool:{tool_name}"
|
|
490
542
|
|
|
491
543
|
|
|
544
|
+
def _ref_text(item: dict[str, Any]) -> str | None:
|
|
545
|
+
"""The item's underlying text for the manifest, capped at
|
|
546
|
+
``_MCP_REF_TEXT_CHAR_LIMIT`` (single write-side cap shared by all
|
|
547
|
+
extraction modes)."""
|
|
548
|
+
text = item.get("text")
|
|
549
|
+
if not isinstance(text, str) or not text:
|
|
550
|
+
return None
|
|
551
|
+
return text[:_MCP_REF_TEXT_CHAR_LIMIT]
|
|
552
|
+
|
|
553
|
+
|
|
492
554
|
def _annotate_mcp_results_for_citations(
|
|
493
555
|
response: Any,
|
|
494
556
|
*,
|
|
@@ -496,6 +558,7 @@ def _annotate_mcp_results_for_citations(
|
|
|
496
558
|
server_name: str | None,
|
|
497
559
|
refs_log_path: Path | None = None,
|
|
498
560
|
reference_mapping: dict[str, Any] | None = None,
|
|
561
|
+
fallback_text: str | None = None,
|
|
499
562
|
) -> list[tuple[int, dict[str, Any]]]:
|
|
500
563
|
"""Assign per-turn ``[mcpsourceN]`` numbers to each retrieved item and append
|
|
501
564
|
the refs manifest. Returns ``[(sourceNumber, item)]`` for the Sources block.
|
|
@@ -512,6 +575,7 @@ def _annotate_mcp_results_for_citations(
|
|
|
512
575
|
tool_name=tool_name,
|
|
513
576
|
server_name=server_name,
|
|
514
577
|
reference_mapping=reference_mapping,
|
|
578
|
+
fallback_text=fallback_text,
|
|
515
579
|
)
|
|
516
580
|
with _locked_turn_refs_manifest(
|
|
517
581
|
refs_log_path, lock_filename=_MCP_REFS_LOCK_FILENAME
|
|
@@ -542,6 +606,7 @@ def _annotate_mcp_results_for_citations(
|
|
|
542
606
|
"title": item.get("title"),
|
|
543
607
|
"snippet": item.get("snippet"),
|
|
544
608
|
"details": item.get("details"),
|
|
609
|
+
"text": _ref_text(item),
|
|
545
610
|
}
|
|
546
611
|
try:
|
|
547
612
|
_append_turn_refs_manifest_entry(refs_log_path, manifest_entry)
|
|
@@ -563,13 +628,26 @@ def _annotate_mcp_results_for_citations(
|
|
|
563
628
|
if stored is not None and new_details and not stored.get("details"):
|
|
564
629
|
stored["details"] = new_details
|
|
565
630
|
needs_rewrite = True
|
|
631
|
+
# ``text`` upgrades to the longer capture: the common turn
|
|
632
|
+
# is a search (small per-record JSON) followed by a full
|
|
633
|
+
# fetch of the same titled item — the later, richer text is
|
|
634
|
+
# the better ground truth for the hallucination check.
|
|
635
|
+
new_text = _ref_text(item)
|
|
636
|
+
if stored is not None and new_text:
|
|
637
|
+
stored_text = stored.get("text")
|
|
638
|
+
stored_len = (
|
|
639
|
+
len(stored_text) if isinstance(stored_text, str) else 0
|
|
640
|
+
)
|
|
641
|
+
if len(new_text) > stored_len:
|
|
642
|
+
stored["text"] = new_text
|
|
643
|
+
needs_rewrite = True
|
|
566
644
|
annotated.append((source_number, item))
|
|
567
645
|
if needs_rewrite:
|
|
568
646
|
try:
|
|
569
647
|
_rewrite_turn_refs_manifest(refs_log_path, entries)
|
|
570
648
|
except (UnsafeRefsLogPathError, OSError) as exc:
|
|
571
649
|
_LOGGER.warning(
|
|
572
|
-
"mcp: failed to backfill refs manifest
|
|
650
|
+
"mcp: failed to backfill refs manifest enrichment: %s", exc
|
|
573
651
|
)
|
|
574
652
|
except (UnsafeRefsLogPathError, OSError) as exc:
|
|
575
653
|
_LOGGER.warning("mcp: failed to append refs manifest: %s", exc)
|
|
@@ -656,7 +734,10 @@ def record_mcp_citations(
|
|
|
656
734
|
- ``response`` is the raw ``unique_sdk.MCP`` result, used for citation
|
|
657
735
|
extraction (titles from ``resource_link`` names / JSON bodies).
|
|
658
736
|
- ``formatted_text`` is the source text the model actually saw for this
|
|
659
|
-
tool result, recorded as the hallucination groundedness context.
|
|
737
|
+
tool result, recorded as the hallucination groundedness context. It also
|
|
738
|
+
serves as the per-item ``text`` of the title-less fallback chip, so a
|
|
739
|
+
cited ``[mcpsourceN]`` without extractable records still grounds on the
|
|
740
|
+
whole output.
|
|
660
741
|
|
|
661
742
|
Best-effort and never raises: the underlying manifest writers swallow their
|
|
662
743
|
own errors, and ``_annotate_mcp_results_for_citations`` owns the per-turn
|
|
@@ -675,6 +756,7 @@ def record_mcp_citations(
|
|
|
675
756
|
server_name=server_name,
|
|
676
757
|
refs_log_path=unique_dir / _MCP_REFS_LOG_RELATIVE_PATH.name,
|
|
677
758
|
reference_mapping=reference_mapping,
|
|
759
|
+
fallback_text=formatted_text,
|
|
678
760
|
)
|
|
679
761
|
return _citation_sources_block(annotated)
|
|
680
762
|
|
|
@@ -7,10 +7,7 @@ description: >-
|
|
|
7
7
|
platform. Use whenever the user asks to find, search, or query documents
|
|
8
8
|
or content on Unique, including filtering by folder or metadata.
|
|
9
9
|
Also covers `unique-cli read <cont_id>` for reading the full indexed text
|
|
10
|
-
of a document when its content ID is already known
|
|
11
|
-
`unique-cli uploaded-search "<query>"` for searching documents uploaded for
|
|
12
|
-
the current task/row, which are NOT in the knowledge-base scope and never
|
|
13
|
-
appear in `unique-cli search`.
|
|
10
|
+
of a document when its content ID is already known.
|
|
14
11
|
NOTE: This search uses combined vector + full-text indexing. Excel
|
|
15
12
|
(.xlsx/.xls), CSV (.csv), and image files are NOT full-text indexed,
|
|
16
13
|
so they will not appear in search results. To locate these file types,
|
|
@@ -95,43 +92,10 @@ unique-cli search "audit" -m department=Legal -m year=2025
|
|
|
95
92
|
unique-cli search "regulatory" -f /Legal -m year=2025 -l 50
|
|
96
93
|
```
|
|
97
94
|
|
|
98
|
-
## Searching Uploaded Documents (`uploaded-search`)
|
|
99
|
-
|
|
100
|
-
Documents **uploaded for the current task** (e.g. an Agentic Table row's
|
|
101
|
-
attached files) are **not** part of the knowledge-base folder scope. They will
|
|
102
|
-
**never** appear in `unique-cli search` results, no matter the folder or
|
|
103
|
-
metadata filters — uploaded files are scoped to the chat, not to a KB folder.
|
|
104
|
-
Use `uploaded-search` to retrieve them:
|
|
105
|
-
|
|
106
|
-
```bash
|
|
107
|
-
# Search the documents uploaded for this row/task
|
|
108
|
-
unique-cli uploaded-search "target asset classes and investment strategy"
|
|
109
|
-
|
|
110
|
-
# Limit results
|
|
111
|
-
unique-cli uploaded-search "fee structure" --limit 50
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
When to use which:
|
|
115
|
-
|
|
116
|
-
| You want to search… | Command |
|
|
117
|
-
|---------------------|---------|
|
|
118
|
-
| The knowledge base (folders the task scope grants) | `unique-cli search "<query>"` |
|
|
119
|
-
| Documents uploaded for **this** row/task | `unique-cli uploaded-search "<query>"` |
|
|
120
|
-
|
|
121
|
-
`uploaded-search` returns the same `<sourceN>...</sourceN>` blocks as `search`
|
|
122
|
-
and shares the **same per-turn citation manifest**, so `[sourceN]` numbering is
|
|
123
|
-
continuous across both commands within a turn — cite an uploaded-document fact
|
|
124
|
-
exactly the same way (`[sourceN]`). If no documents were uploaded for the task,
|
|
125
|
-
the command reports that and you should fall back to `unique-cli search`.
|
|
126
|
-
|
|
127
|
-
> **Note:** there is no `--folder`/`--metadata` for `uploaded-search` — the set
|
|
128
|
-
> of uploaded documents is fixed by what was attached to the task.
|
|
129
|
-
|
|
130
95
|
## Command Reference
|
|
131
96
|
|
|
132
97
|
```
|
|
133
98
|
unique-cli search <query> [--folder <path|scope_id>] [--metadata <key=value>]... [--limit <N>]
|
|
134
|
-
unique-cli uploaded-search <query> [--limit <N>]
|
|
135
99
|
```
|
|
136
100
|
|
|
137
101
|
| Option | Short | Default | Description |
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: unique-cli-uploaded-search
|
|
3
|
+
description: >-
|
|
4
|
+
Search the documents uploaded for the CURRENT task/row (e.g. an Agentic
|
|
5
|
+
Table row's attached files) via the `unique-cli uploaded-search "<query>"`
|
|
6
|
+
command, with the same per-turn citation tracking as `unique-cli search`
|
|
7
|
+
so cited facts render as `<sup>N</sup>` footnotes and clickable reference
|
|
8
|
+
chips on the Unique platform. ALWAYS use this skill when the user refers to
|
|
9
|
+
documents they uploaded/attached to this task, or when you need facts from
|
|
10
|
+
the task's own attached files. These uploaded files are scoped to the chat,
|
|
11
|
+
NOT to a knowledge-base folder, so they will NEVER appear in
|
|
12
|
+
`unique-cli search` results no matter the folder or metadata filters. Use
|
|
13
|
+
`unique-cli search` for the knowledge base and this command for the task's
|
|
14
|
+
uploaded documents; the two are complementary and citation numbering is
|
|
15
|
+
shared across them within a turn.
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
# Unique CLI -- Uploaded Document Search
|
|
19
|
+
|
|
20
|
+
Documents **uploaded for the current task** (for example an Agentic Table
|
|
21
|
+
row's attached files) are **not** part of the knowledge-base folder scope.
|
|
22
|
+
They are scoped to the chat, so they will **never** appear in
|
|
23
|
+
`unique-cli search` results — no folder or metadata filter can surface them.
|
|
24
|
+
Use `unique-cli uploaded-search` to retrieve them.
|
|
25
|
+
|
|
26
|
+
## Basic Usage
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# Search the documents uploaded for this row/task
|
|
30
|
+
unique-cli uploaded-search "target asset classes and investment strategy"
|
|
31
|
+
|
|
32
|
+
# Limit results
|
|
33
|
+
unique-cli uploaded-search "fee structure" --limit 50
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
There is **no** `--folder`/`--metadata` for `uploaded-search` — the set of
|
|
37
|
+
uploaded documents is fixed by what was attached to the task.
|
|
38
|
+
|
|
39
|
+
If no documents were uploaded for this task, the command reports that and you
|
|
40
|
+
should fall back to `unique-cli search` for the knowledge base.
|
|
41
|
+
|
|
42
|
+
## When to use which command
|
|
43
|
+
|
|
44
|
+
| You want to search… | Command |
|
|
45
|
+
|---------------------|---------|
|
|
46
|
+
| The knowledge base (folders the task scope grants) | `unique-cli search "<query>"` |
|
|
47
|
+
| Documents uploaded for **this** row/task | `unique-cli uploaded-search "<query>"` |
|
|
48
|
+
|
|
49
|
+
## Output Format
|
|
50
|
+
|
|
51
|
+
Each result is rendered as a `<sourceN>...</sourceN>` block, identical to
|
|
52
|
+
`unique-cli search`. `N` is **1-based** and **shared with `unique-cli search`
|
|
53
|
+
within the same turn** — both commands append to the same per-turn citation
|
|
54
|
+
manifest, so numbering stays continuous across them (a `search` call that
|
|
55
|
+
emitted `<source1>`–`<source3>` is followed by an `uploaded-search` call whose
|
|
56
|
+
first result is `<source4>`).
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
Found 1 result(s):
|
|
60
|
+
|
|
61
|
+
<source1>
|
|
62
|
+
<|document|>investment-mandate.pdf</|document|>
|
|
63
|
+
<|page|>2</|page|>
|
|
64
|
+
<|info|>cont_abc123</|info|>
|
|
65
|
+
...the mandate targets EMEA equities with a 5% cap per issuer...
|
|
66
|
+
</source1>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Citation Rules
|
|
70
|
+
|
|
71
|
+
Cite a fact from `uploaded-search` results with `[sourceN]`, **exactly** as you
|
|
72
|
+
would for `unique-cli search` — the two share the same namespace and manifest.
|
|
73
|
+
The Unique platform converts each `[sourceN]` marker in your final answer into a
|
|
74
|
+
`<sup>N</sup>` footnote and a clickable reference chip.
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
The uploaded mandate caps single-issuer exposure at 5% [source1].
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**Rules** (enforced by the platform's reference post-processor):
|
|
81
|
+
|
|
82
|
+
1. **`[sourceN]` is for KB and uploaded-document results.** Web results from
|
|
83
|
+
`unique-cli web-search` use `[websourceN]` — never mix the two namespaces.
|
|
84
|
+
2. Only cite numbers you saw in the **current** turn's `search` /
|
|
85
|
+
`uploaded-search` output. Numbers from previous turns are stale and will be
|
|
86
|
+
silently dropped.
|
|
87
|
+
3. Write `source` in singular form with the number in digits: `[source1]`,
|
|
88
|
+
`[source2]` — not `[Source 1]` or `[source one]`.
|
|
89
|
+
4. Prefer citing each fact with a single, most-relevant source.
|
|
90
|
+
5. Do not invent source numbers for remembered or inferred facts.
|
|
91
|
+
|
|
92
|
+
## Command Reference
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
unique-cli uploaded-search <query> [--limit <N>]
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
| Option | Short | Default | Description |
|
|
99
|
+
|--------|-------|---------|-------------|
|
|
100
|
+
| `--limit` | `-l` | 200 | Max results |
|
|
101
|
+
|
|
102
|
+
## Prerequisites
|
|
103
|
+
|
|
104
|
+
Requires these environment variables:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
UNIQUE_USER_ID # User ID (required)
|
|
108
|
+
UNIQUE_COMPANY_ID # Company ID (required)
|
|
109
|
+
UNIQUE_API_KEY # API key — optional on localhost / secured cluster
|
|
110
|
+
UNIQUE_APP_ID # App ID — optional on localhost / secured cluster
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Install: `pip install unique-sdk`
|
|
@@ -263,6 +263,14 @@ class ShellState:
|
|
|
263
263
|
"""Return True if *content_id* is in the current workspace scope.
|
|
264
264
|
|
|
265
265
|
Precedence (UN-21780):
|
|
266
|
+
0. Per-row uploaded documents (``.unique-uploaded.json``) surfaced by
|
|
267
|
+
``unique-cli uploaded-search`` are task inputs that live *outside* the
|
|
268
|
+
KB scope boundary by design, so neither the metadata filter nor the
|
|
269
|
+
static ``scopeIds`` path below would admit them. Exempt them for
|
|
270
|
+
non-destructive access so the agent can ``read``/``ls``/``cite`` a doc
|
|
271
|
+
it just found via uploaded-search — mirroring the chat-file exemption.
|
|
272
|
+
Mutating ops (``rm``/``mv``) pass ``allow_chat_files=False`` and stay
|
|
273
|
+
denied, so an uploaded input can't be deleted or renamed this way.
|
|
266
274
|
1. A per-message UniqueQL ``metaDataFilter`` (e.g. an Agentic Table
|
|
267
275
|
column's ``scope_rules``) is the authority for this turn and
|
|
268
276
|
*replaces* the static ``scopeIds`` for content access. Files the
|
|
@@ -275,6 +283,8 @@ class ShellState:
|
|
|
275
283
|
``is_folder_target_within_workspace``).
|
|
276
284
|
3. With neither configured, everything is in scope.
|
|
277
285
|
"""
|
|
286
|
+
if allow_chat_files and content_id in self.uploaded_search_content_ids:
|
|
287
|
+
return True
|
|
278
288
|
if self.workspace_metadata_filter is not None:
|
|
279
289
|
if allow_chat_files and content_id in self._chat_file_content_ids():
|
|
280
290
|
return True
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/api_resources/__init__.py
RENAMED
|
File without changes
|
{unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/api_resources/_acronyms.py
RENAMED
|
File without changes
|
{unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/api_resources/_agentic_table.py
RENAMED
|
File without changes
|
{unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/api_resources/_analytics_order.py
RENAMED
|
File without changes
|
{unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/api_resources/_benchmarking.py
RENAMED
|
File without changes
|
{unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/api_resources/_briefing.py
RENAMED
|
File without changes
|
{unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/api_resources/_chat_completion.py
RENAMED
|
File without changes
|
{unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/api_resources/_content.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/api_resources/_elicitation.py
RENAMED
|
File without changes
|
{unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/api_resources/_embedding.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/api_resources/_integrated.py
RENAMED
|
File without changes
|
{unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/api_resources/_llm_models.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/api_resources/_message.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/api_resources/_message_log.py
RENAMED
|
File without changes
|
{unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/api_resources/_message_tool.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/api_resources/_scheduled_task.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/api_resources/_search_string.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/api_resources/_web_search.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/cli/commands/cite_file.py
RENAMED
|
File without changes
|
{unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/cli/commands/dynamic_frontend.py
RENAMED
|
File without changes
|
{unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/cli/commands/elicitation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/cli/commands/navigation.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/cli/commands/scheduled_tasks.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/cli/commands/web_search.py
RENAMED
|
File without changes
|
{unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/cli/commands/web_search_config.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/utils/analytics_order_run.py
RENAMED
|
File without changes
|
{unique_sdk-2026.30.0.dev3 → unique_sdk-2026.30.0.dev5}/unique_sdk/utils/benchmarking_run.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|