unique-sdk 2026.40.0.dev5__tar.gz → 2026.40.0.dev7__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.40.0.dev5 → unique_sdk-2026.40.0.dev7}/PKG-INFO +1 -1
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/pyproject.toml +1 -1
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/api_resources/_agentic_table.py +1 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/cli/commands/mcp.py +163 -35
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/README.md +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/__init__.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/_api_requestor.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/_api_resource.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/_api_version.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/_error.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/_http_client.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/_list_object.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/_object_classes.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/_request_options.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/_unique_object.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/_unique_ql.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/_unique_response.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/_util.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/_version.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/_webhook.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/api_resources/__init__.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/api_resources/_acronyms.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/api_resources/_analytics_order.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/api_resources/_benchmarking.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/api_resources/_briefing.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/api_resources/_chat_completion.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/api_resources/_content.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/api_resources/_context_memory.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/api_resources/_elicitation.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/api_resources/_embedding.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/api_resources/_event.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/api_resources/_folder.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/api_resources/_group.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/api_resources/_integrated.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/api_resources/_llm_models.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/api_resources/_mcp.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/api_resources/_message.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/api_resources/_message_assessment.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/api_resources/_message_execution.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/api_resources/_message_log.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/api_resources/_message_tool.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/api_resources/_module.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/api_resources/_scheduled_task.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/api_resources/_search.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/api_resources/_search_string.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/api_resources/_short_term_memory.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/api_resources/_space.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/api_resources/_user.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/api_resources/_web_search.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/cli/__init__.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/cli/__main__.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/cli/cli.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/cli/commands/__init__.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/cli/commands/_citation_manifest.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/cli/commands/agentic_table.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/cli/commands/agentic_table_write.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/cli/commands/browser.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/cli/commands/cite_file.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/cli/commands/elicitation.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/cli/commands/files.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/cli/commands/folders.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/cli/commands/navigation.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/cli/commands/read.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/cli/commands/scheduled_tasks.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/cli/commands/search.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/cli/commands/subagent.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/cli/commands/web_search.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/cli/commands/web_search_config.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/cli/config.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/cli/formatting.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/cli/identity.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/cli/metadata_filter.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/cli/shell.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/cli/skills/unique-cli-agentic-table/SKILL.md +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/cli/skills/unique-cli-elicitation/SKILL.md +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/cli/skills/unique-cli-file-management/SKILL.md +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/cli/skills/unique-cli-mcp/SKILL.md +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/cli/skills/unique-cli-scheduled-tasks/SKILL.md +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/cli/skills/unique-cli-search/SKILL.md +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/cli/skills/unique-cli-subagent/SKILL.md +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/cli/skills/unique-cli-uploaded-search/SKILL.md +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/cli/skills/unique-cli-web-search/SKILL.md +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/cli/state.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/cli/workspace.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/utils/analytics_order_run.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/utils/benchmarking_run.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/utils/chat_history.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/utils/chat_in_space.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/utils/file_io.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/utils/sources.py +0 -0
- {unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/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.40.0.
|
|
3
|
+
Version: 2026.40.0.dev7
|
|
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>
|
{unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/api_resources/_agentic_table.py
RENAMED
|
@@ -302,6 +302,7 @@ class AgenticTable(APIResource["AgenticTable"]):
|
|
|
302
302
|
rowOrders: list[int]
|
|
303
303
|
status: RowVerificationStatus
|
|
304
304
|
locked: NotRequired[bool]
|
|
305
|
+
emitLibrarySheetVerifiedEvent: NotRequired[bool]
|
|
305
306
|
|
|
306
307
|
class CreateSheet(RequestOptions):
|
|
307
308
|
"""Body for `POST /magic-table` (create a new sheet in a space)."""
|
|
@@ -43,9 +43,10 @@ _MCP_OUTPUT_TEXT_CHAR_LIMIT = 200_000
|
|
|
43
43
|
# Per-turn manifest of citable MCP sources, consumed by the runner to stitch
|
|
44
44
|
# ``[mcpsourceN]`` markers into ``<sup>N</sup>`` footnotes + reference chips
|
|
45
45
|
# (UN-21285). One entry per retrieved item: a short *title* describing what was
|
|
46
|
-
# retrieved
|
|
47
|
-
#
|
|
48
|
-
# the
|
|
46
|
+
# retrieved, plus that item's own http(s) link when the tool result includes
|
|
47
|
+
# one. The link is stored on the row and is not copied into the Sources block.
|
|
48
|
+
# The runner labels the chip with that title + the MCP tool name; falls back
|
|
49
|
+
# to the tool alone when the result carries no recognizable title.
|
|
49
50
|
_MCP_REFS_LOG_RELATIVE_PATH = Path(".unique") / "mcp-refs.jsonl"
|
|
50
51
|
_MCP_REFS_LOCK_FILENAME = "mcp-refs.lock"
|
|
51
52
|
# Persistent per-chat seed for ``[mcpsourceN]`` numbering. The SI runner wipes
|
|
@@ -68,6 +69,16 @@ _MCP_REF_TEXT_CHAR_LIMIT = 100_000
|
|
|
68
69
|
# Keys an MCP tool's JSON result commonly uses for a record's human title.
|
|
69
70
|
_TITLE_KEYS = ("title", "name", "displayName", "subject", "summary", "key")
|
|
70
71
|
|
|
72
|
+
# Preferred keys for a record's own http(s) link. Any other string field is
|
|
73
|
+
# inspected only after these miss.
|
|
74
|
+
_URL_KEYS = ("url", "uri", "href", "webUrl", "web_url", "outlookWebLink", "link")
|
|
75
|
+
|
|
76
|
+
# Markdown list item: a bullet or numbered title, then an indented http(s) URL.
|
|
77
|
+
# A list marker is required so a prose line above an indented link is not a hit.
|
|
78
|
+
_MARKDOWN_TITLE_URL = re.compile(
|
|
79
|
+
r"(?m)^(?:[-*]\s+|\d+\.\s+)(.+?)\n[ \t]+(https?://\S+)\s*$"
|
|
80
|
+
)
|
|
81
|
+
|
|
71
82
|
# Keys an MCP tool's JSON result commonly uses for the optional "details" line
|
|
72
83
|
# (UN-22310) — e.g. a date and an author such as "10/10/2026 - Jamie Dimon".
|
|
73
84
|
# Best-effort: only top-level record keys are inspected for these (nested
|
|
@@ -247,6 +258,35 @@ def _records_from_parsed(parsed: Any) -> list[dict[str, Any]]:
|
|
|
247
258
|
return []
|
|
248
259
|
|
|
249
260
|
|
|
261
|
+
def _is_http_url(value: object) -> str | None:
|
|
262
|
+
"""Return ``value`` when it is an http(s) URL, else None."""
|
|
263
|
+
if not isinstance(value, str):
|
|
264
|
+
return None
|
|
265
|
+
stripped = value.strip()
|
|
266
|
+
if stripped.startswith("https://") or stripped.startswith("http://"):
|
|
267
|
+
return stripped
|
|
268
|
+
return None
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def _url_from_item(obj: dict[str, Any]) -> str | None:
|
|
272
|
+
"""First http(s) field on a record. Preferred keys, then any string field."""
|
|
273
|
+
for key in _URL_KEYS:
|
|
274
|
+
url = _is_http_url(obj.get(key))
|
|
275
|
+
if url:
|
|
276
|
+
return url
|
|
277
|
+
for value in obj.values():
|
|
278
|
+
url = _is_http_url(value)
|
|
279
|
+
if url:
|
|
280
|
+
return url
|
|
281
|
+
return None
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def _with_url(item: dict[str, Any], url: str | None) -> dict[str, Any]:
|
|
285
|
+
if url:
|
|
286
|
+
item["url"] = url
|
|
287
|
+
return item
|
|
288
|
+
|
|
289
|
+
|
|
250
290
|
def _titles_from_json(text: str) -> list[dict[str, Any]]:
|
|
251
291
|
"""Best-effort: pull a human title out of a JSON result, e.g. an Atlassian
|
|
252
292
|
page/issue returned as JSON-in-text. Tolerates a non-JSON preamble before
|
|
@@ -262,16 +302,44 @@ def _titles_from_json(text: str) -> list[dict[str, Any]]:
|
|
|
262
302
|
title = _title_from_json(entry)
|
|
263
303
|
if title:
|
|
264
304
|
items.append(
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
305
|
+
_with_url(
|
|
306
|
+
{
|
|
307
|
+
"title": title,
|
|
308
|
+
"snippet": None,
|
|
309
|
+
"details": _details_from_json(entry),
|
|
310
|
+
"text": _record_text(entry),
|
|
311
|
+
},
|
|
312
|
+
_url_from_item(entry),
|
|
313
|
+
)
|
|
271
314
|
)
|
|
272
315
|
return items
|
|
273
316
|
|
|
274
317
|
|
|
318
|
+
def _titles_from_markdown_list(text: str) -> list[dict[str, Any]]:
|
|
319
|
+
"""One item per markdown list entry that pairs a title with an http(s) URL.
|
|
320
|
+
|
|
321
|
+
Returns [] when no pair is found.
|
|
322
|
+
"""
|
|
323
|
+
if not text.strip():
|
|
324
|
+
return []
|
|
325
|
+
items: list[dict[str, Any]] = []
|
|
326
|
+
for match in _MARKDOWN_TITLE_URL.finditer(text):
|
|
327
|
+
title = match.group(1).strip().lstrip("-* ").strip()
|
|
328
|
+
url = _is_http_url(match.group(2))
|
|
329
|
+
if not title or not url:
|
|
330
|
+
continue
|
|
331
|
+
items.append(
|
|
332
|
+
{
|
|
333
|
+
"title": title,
|
|
334
|
+
"snippet": None,
|
|
335
|
+
"details": None,
|
|
336
|
+
"text": match.group(0).strip(),
|
|
337
|
+
"url": url,
|
|
338
|
+
}
|
|
339
|
+
)
|
|
340
|
+
return items
|
|
341
|
+
|
|
342
|
+
|
|
275
343
|
def _get_by_dotted_path(obj: Any, path: str) -> Any:
|
|
276
344
|
"""Walk a dotted path over dicts and lists (numeric segments index lists),
|
|
277
345
|
e.g. ``"issues"``, ``"fields.summary"``, ``"result.items.0.key"``. Returns
|
|
@@ -310,6 +378,26 @@ def _render_title_template(template: str, record: dict[str, Any]) -> str | None:
|
|
|
310
378
|
return rendered or None
|
|
311
379
|
|
|
312
380
|
|
|
381
|
+
def _as_content_dict(block: Any) -> dict[str, Any] | None:
|
|
382
|
+
"""Normalize an MCP content block to a dict.
|
|
383
|
+
|
|
384
|
+
Client libraries often hand back typed objects (``mcp.types.TextContent``)
|
|
385
|
+
with the same fields as the wire dict. Only known attribute names are
|
|
386
|
+
copied so a hostile object cannot inject unexpected keys.
|
|
387
|
+
"""
|
|
388
|
+
if isinstance(block, dict):
|
|
389
|
+
return block
|
|
390
|
+
block_type = getattr(block, "type", None)
|
|
391
|
+
if not isinstance(block_type, str) or not block_type.strip():
|
|
392
|
+
return None
|
|
393
|
+
out: dict[str, Any] = {"type": block_type}
|
|
394
|
+
for key in ("text", "name", "description", "uri"):
|
|
395
|
+
value = getattr(block, key, None)
|
|
396
|
+
if value is not None:
|
|
397
|
+
out[key] = value
|
|
398
|
+
return out
|
|
399
|
+
|
|
400
|
+
|
|
313
401
|
def _mapped_records(response: Any, list_path: str | None) -> list[Any]:
|
|
314
402
|
"""Locate the records a reference mapping applies to, preferring the
|
|
315
403
|
MCP-native ``structuredContent`` then any JSON-in-text block (preamble
|
|
@@ -326,8 +414,9 @@ def _mapped_records(response: Any, list_path: str | None) -> list[Any]:
|
|
|
326
414
|
if structured is not None:
|
|
327
415
|
sources.append(structured)
|
|
328
416
|
for block in getattr(response, "content", None) or []:
|
|
329
|
-
|
|
330
|
-
|
|
417
|
+
normalized = _as_content_dict(block)
|
|
418
|
+
if normalized is not None and normalized.get("type") == "text":
|
|
419
|
+
parsed = _loads_embedded_json(normalized.get("text") or "")
|
|
331
420
|
if parsed is not None:
|
|
332
421
|
sources.append(parsed)
|
|
333
422
|
|
|
@@ -363,8 +452,9 @@ def _first_text_title(response: Any, max_chars: int) -> str | None:
|
|
|
363
452
|
"""Title from the first non-empty line of the first text block — for a
|
|
364
453
|
non-JSON result such as a fetched Markdown document (e.g. ``read_doc``)."""
|
|
365
454
|
for block in getattr(response, "content", None) or []:
|
|
366
|
-
|
|
367
|
-
|
|
455
|
+
normalized = _as_content_dict(block)
|
|
456
|
+
if normalized is not None and normalized.get("type") == "text":
|
|
457
|
+
title = _leading_title_line(normalized.get("text"), max_chars)
|
|
368
458
|
if title:
|
|
369
459
|
return title
|
|
370
460
|
return None
|
|
@@ -375,9 +465,10 @@ def _all_text_blocks(response: Any) -> str | None:
|
|
|
375
465
|
result (e.g. a fetched document) recorded as that item's ground truth."""
|
|
376
466
|
texts: list[str] = []
|
|
377
467
|
for block in getattr(response, "content", None) or []:
|
|
378
|
-
|
|
468
|
+
normalized = _as_content_dict(block)
|
|
469
|
+
if normalized is None or normalized.get("type") != "text":
|
|
379
470
|
continue
|
|
380
|
-
text =
|
|
471
|
+
text = normalized.get("text")
|
|
381
472
|
if isinstance(text, str) and text.strip():
|
|
382
473
|
texts.append(text)
|
|
383
474
|
return "\n\n".join(texts) or None
|
|
@@ -412,6 +503,7 @@ def _extract_with_reference_mapping(
|
|
|
412
503
|
# points at the text field within each item (e.g. ``content``); leave unset
|
|
413
504
|
# when each item is itself a plain string.
|
|
414
505
|
title_text_path = mapping.get("titleTextPath") or mapping.get("title_text_path")
|
|
506
|
+
url_path = mapping.get("urlPath") or mapping.get("url_path")
|
|
415
507
|
try:
|
|
416
508
|
title_max_chars = int(
|
|
417
509
|
mapping.get("titleMaxChars")
|
|
@@ -449,13 +541,22 @@ def _extract_with_reference_mapping(
|
|
|
449
541
|
if details_path and is_dict
|
|
450
542
|
else None
|
|
451
543
|
)
|
|
544
|
+
url = None
|
|
545
|
+
if is_dict:
|
|
546
|
+
if url_path:
|
|
547
|
+
url = _is_http_url(_get_by_dotted_path(record, url_path))
|
|
548
|
+
if not url:
|
|
549
|
+
url = _url_from_item(record)
|
|
452
550
|
items.append(
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
551
|
+
_with_url(
|
|
552
|
+
{
|
|
553
|
+
"title": title,
|
|
554
|
+
"snippet": None,
|
|
555
|
+
"details": str(details).strip() if details else None,
|
|
556
|
+
"text": _record_text(record),
|
|
557
|
+
},
|
|
558
|
+
url,
|
|
559
|
+
)
|
|
459
560
|
)
|
|
460
561
|
if items:
|
|
461
562
|
return items
|
|
@@ -492,9 +593,13 @@ def _extract_mcp_citation_items(
|
|
|
492
593
|
destructuring of a list result); when it yields nothing we fall back to the
|
|
493
594
|
generic heuristic: MCP ``resource_link`` names (spec-native) or a best-effort
|
|
494
595
|
JSON-title heuristic over text blocks (for tools like Atlassian that return
|
|
495
|
-
JSON-in-text)
|
|
496
|
-
|
|
497
|
-
result
|
|
596
|
+
JSON-in-text), or a markdown list that pairs a title with an indented
|
|
597
|
+
http(s) URL. Typed content blocks from an MCP client library are
|
|
598
|
+
normalized to dicts first so a list result is not collapsed to one
|
|
599
|
+
title-less chip. An http(s) link on a record is stored on the row and is
|
|
600
|
+
not copied into the Sources block. Falls back to a single title-less item
|
|
601
|
+
(the runner names it after the tool) when the result carries no
|
|
602
|
+
recognizable title.
|
|
498
603
|
|
|
499
604
|
``text`` is the item's underlying retrieved text (the serialized record, a
|
|
500
605
|
fetched document body, or — for the title-less fallback — ``fallback_text``,
|
|
@@ -511,23 +616,39 @@ def _extract_mcp_citation_items(
|
|
|
511
616
|
items: list[dict[str, Any]] = []
|
|
512
617
|
|
|
513
618
|
for block in content:
|
|
514
|
-
|
|
619
|
+
normalized = _as_content_dict(block)
|
|
620
|
+
if normalized is None:
|
|
515
621
|
continue
|
|
516
|
-
if
|
|
517
|
-
name = (
|
|
622
|
+
if normalized.get("type") == "resource_link":
|
|
623
|
+
name = (normalized.get("name") or "").strip()
|
|
518
624
|
if name:
|
|
519
625
|
items.append(
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
626
|
+
_with_url(
|
|
627
|
+
{
|
|
628
|
+
"title": name,
|
|
629
|
+
"snippet": _snippet(normalized.get("description")),
|
|
630
|
+
"text": normalized.get("description") or None,
|
|
631
|
+
},
|
|
632
|
+
_is_http_url(normalized.get("uri")),
|
|
633
|
+
)
|
|
525
634
|
)
|
|
526
635
|
|
|
527
636
|
if not items:
|
|
528
637
|
for block in content:
|
|
529
|
-
|
|
530
|
-
|
|
638
|
+
normalized = _as_content_dict(block)
|
|
639
|
+
if normalized is not None and normalized.get("type") == "text":
|
|
640
|
+
items.extend(_titles_from_json(normalized.get("text") or ""))
|
|
641
|
+
|
|
642
|
+
if not items:
|
|
643
|
+
for block in content:
|
|
644
|
+
normalized = _as_content_dict(block)
|
|
645
|
+
if normalized is not None and normalized.get("type") == "text":
|
|
646
|
+
items.extend(_titles_from_markdown_list(normalized.get("text") or ""))
|
|
647
|
+
|
|
648
|
+
if not items and fallback_text:
|
|
649
|
+
items.extend(_titles_from_json(fallback_text))
|
|
650
|
+
if not items:
|
|
651
|
+
items.extend(_titles_from_markdown_list(fallback_text))
|
|
531
652
|
|
|
532
653
|
if not items:
|
|
533
654
|
# No recognizable title — one chip named after the tool itself.
|
|
@@ -696,6 +817,9 @@ def _annotate_mcp_results_for_citations(
|
|
|
696
817
|
"details": item.get("details"),
|
|
697
818
|
"text": _ref_text(item),
|
|
698
819
|
}
|
|
820
|
+
item_url = item.get("url")
|
|
821
|
+
if isinstance(item_url, str) and item_url:
|
|
822
|
+
manifest_entry["url"] = item_url
|
|
699
823
|
try:
|
|
700
824
|
_append_turn_refs_manifest_entry(refs_log_path, manifest_entry)
|
|
701
825
|
except (UnsafeRefsLogPathError, OSError) as exc:
|
|
@@ -717,6 +841,10 @@ def _annotate_mcp_results_for_citations(
|
|
|
717
841
|
if stored is not None and new_details and not stored.get("details"):
|
|
718
842
|
stored["details"] = new_details
|
|
719
843
|
needs_rewrite = True
|
|
844
|
+
new_url = item.get("url")
|
|
845
|
+
if stored is not None and new_url and not stored.get("url"):
|
|
846
|
+
stored["url"] = new_url
|
|
847
|
+
needs_rewrite = True
|
|
720
848
|
# ``text`` upgrades to the longer capture: the common turn
|
|
721
849
|
# is a search (small per-record JSON) followed by a full
|
|
722
850
|
# fetch of the same titled item — the later, richer text is
|
|
@@ -759,7 +887,7 @@ def _citation_sources_block(
|
|
|
759
887
|
Rendered *before* the tool output (leading block, not a trailing footer):
|
|
760
888
|
the agent harness spills oversized tool results to a file wholesale, and a
|
|
761
889
|
partial or programmatic read of that file only reliably sees the head — a
|
|
762
|
-
trailing marker list would be exactly what such reads miss
|
|
890
|
+
trailing marker list would be exactly what such reads miss.
|
|
763
891
|
|
|
764
892
|
Each line names the ``tool_name`` alongside the marker so the model has a
|
|
765
893
|
stronger anchor than a bare integer: ``{title} — {tool_name}`` when the item
|
|
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.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/api_resources/__init__.py
RENAMED
|
File without changes
|
{unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/api_resources/_acronyms.py
RENAMED
|
File without changes
|
{unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/api_resources/_analytics_order.py
RENAMED
|
File without changes
|
{unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/api_resources/_benchmarking.py
RENAMED
|
File without changes
|
{unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/api_resources/_briefing.py
RENAMED
|
File without changes
|
{unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/api_resources/_chat_completion.py
RENAMED
|
File without changes
|
{unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/api_resources/_content.py
RENAMED
|
File without changes
|
{unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/api_resources/_context_memory.py
RENAMED
|
File without changes
|
{unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/api_resources/_elicitation.py
RENAMED
|
File without changes
|
{unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/api_resources/_embedding.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/api_resources/_integrated.py
RENAMED
|
File without changes
|
{unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/api_resources/_llm_models.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/api_resources/_message.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/api_resources/_message_log.py
RENAMED
|
File without changes
|
{unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/api_resources/_message_tool.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/api_resources/_scheduled_task.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/api_resources/_search_string.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/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
|
{unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/cli/commands/agentic_table.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/cli/commands/cite_file.py
RENAMED
|
File without changes
|
{unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/cli/commands/elicitation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/cli/commands/navigation.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/cli/commands/scheduled_tasks.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/cli/commands/web_search.py
RENAMED
|
File without changes
|
{unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/unique_sdk/utils/analytics_order_run.py
RENAMED
|
File without changes
|
{unique_sdk-2026.40.0.dev5 → unique_sdk-2026.40.0.dev7}/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
|