unique-sdk 2026.32.0.dev2__tar.gz → 2026.32.0.dev4__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.32.0.dev2 → unique_sdk-2026.32.0.dev4}/PKG-INFO +1 -1
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/pyproject.toml +1 -1
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/api_resources/_web_search.py +8 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/cli/cli.py +28 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/cli/commands/mcp.py +107 -8
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/cli/commands/web_search.py +10 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/README.md +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/__init__.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/_api_requestor.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/_api_resource.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/_api_version.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/_error.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/_http_client.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/_list_object.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/_object_classes.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/_request_options.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/_unique_object.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/_unique_ql.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/_unique_response.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/_util.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/_version.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/_webhook.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/api_resources/__init__.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/api_resources/_acronyms.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/api_resources/_agentic_table.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/api_resources/_analytics_order.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/api_resources/_benchmarking.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/api_resources/_briefing.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/api_resources/_chat_completion.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/api_resources/_content.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/api_resources/_dynamic_frontend.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/api_resources/_elicitation.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/api_resources/_embedding.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/api_resources/_event.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/api_resources/_folder.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/api_resources/_group.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/api_resources/_integrated.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/api_resources/_llm_models.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/api_resources/_mcp.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/api_resources/_message.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/api_resources/_message_assessment.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/api_resources/_message_execution.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/api_resources/_message_log.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/api_resources/_message_tool.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/api_resources/_module.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/api_resources/_scheduled_task.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/api_resources/_search.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/api_resources/_search_string.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/api_resources/_short_term_memory.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/api_resources/_space.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/api_resources/_user.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/cli/__init__.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/cli/__main__.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/cli/commands/__init__.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/cli/commands/_citation_manifest.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/cli/commands/agentic_table.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/cli/commands/browser.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/cli/commands/cite_file.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/cli/commands/dynamic_frontend.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/cli/commands/elicitation.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/cli/commands/files.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/cli/commands/folders.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/cli/commands/navigation.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/cli/commands/read.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/cli/commands/scheduled_tasks.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/cli/commands/search.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/cli/commands/subagent.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/cli/commands/web_search_config.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/cli/config.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/cli/formatting.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/cli/identity.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/cli/metadata_filter.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/cli/shell.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/cli/skills/unique-cli-agentic-table/SKILL.md +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/cli/skills/unique-cli-dynamic-frontend/SKILL.md +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/cli/skills/unique-cli-elicitation/SKILL.md +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/cli/skills/unique-cli-file-management/SKILL.md +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/cli/skills/unique-cli-mcp/SKILL.md +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/cli/skills/unique-cli-scheduled-tasks/SKILL.md +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/cli/skills/unique-cli-search/SKILL.md +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/cli/skills/unique-cli-subagent/SKILL.md +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/cli/skills/unique-cli-uploaded-search/SKILL.md +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/cli/skills/unique-cli-web-search/SKILL.md +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/cli/state.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/utils/analytics_order_run.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/utils/benchmarking_run.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/utils/chat_history.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/utils/chat_in_space.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/utils/file_io.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/utils/sources.py +0 -0
- {unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/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.32.0.
|
|
3
|
+
Version: 2026.32.0.dev4
|
|
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.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/api_resources/_web_search.py
RENAMED
|
@@ -58,6 +58,10 @@ class WebSearch(APIResource["WebSearch"]):
|
|
|
58
58
|
searchEngineConfig: NotRequired[dict[str, Any] | None]
|
|
59
59
|
# Used only when includeContent=True and the engine needs scraping.
|
|
60
60
|
crawlerConfig: NotRequired[dict[str, Any] | None]
|
|
61
|
+
# Optional: when set, the space's Web Search toggle is enforced
|
|
62
|
+
# server-side for this call. Omit for callers not acting on
|
|
63
|
+
# behalf of a specific chat (unchanged legacy behavior).
|
|
64
|
+
chatId: NotRequired[str | None]
|
|
61
65
|
|
|
62
66
|
# Response envelope.
|
|
63
67
|
engine: str
|
|
@@ -130,6 +134,10 @@ class WebCrawl(APIResource["WebCrawl"]):
|
|
|
130
134
|
urls: list[str]
|
|
131
135
|
parallel: NotRequired[int | None]
|
|
132
136
|
crawlerConfig: NotRequired[dict[str, Any] | None]
|
|
137
|
+
# Optional: when set, the space's Web Search toggle is enforced
|
|
138
|
+
# server-side for this call. Omit for callers not acting on
|
|
139
|
+
# behalf of a specific chat (unchanged legacy behavior).
|
|
140
|
+
chatId: NotRequired[str | None]
|
|
133
141
|
|
|
134
142
|
# Response envelope.
|
|
135
143
|
crawler: str
|
|
@@ -2145,6 +2145,18 @@ Examples:
|
|
|
2145
2145
|
is_flag=True,
|
|
2146
2146
|
help="Output results as JSON (suitable for piping into web-search crawl --stdin).",
|
|
2147
2147
|
)
|
|
2148
|
+
@click.option(
|
|
2149
|
+
"--chat-id",
|
|
2150
|
+
"chat_id",
|
|
2151
|
+
default=None,
|
|
2152
|
+
envvar="UNIQUE_CHAT_ID",
|
|
2153
|
+
help=(
|
|
2154
|
+
"Chat id this call is made on behalf of. Defaults to $UNIQUE_CHAT_ID "
|
|
2155
|
+
"(auto-set in Conduct sandboxes). When set, the space's Web Search "
|
|
2156
|
+
"toggle is enforced server-side and the call is rejected if Web "
|
|
2157
|
+
"Search is disabled for that space."
|
|
2158
|
+
),
|
|
2159
|
+
)
|
|
2148
2160
|
@click.pass_context
|
|
2149
2161
|
def web_search_search_cmd(
|
|
2150
2162
|
ctx: click.Context,
|
|
@@ -2155,6 +2167,7 @@ def web_search_search_cmd(
|
|
|
2155
2167
|
crawler_config_raw: str | None,
|
|
2156
2168
|
config_path: str | None,
|
|
2157
2169
|
output_json: bool,
|
|
2170
|
+
chat_id: str | None,
|
|
2158
2171
|
) -> None:
|
|
2159
2172
|
"""Search the web via the Unique public API."""
|
|
2160
2173
|
resolved_config = _resolve_web_search_config_path(ctx, config_path)
|
|
@@ -2167,6 +2180,7 @@ def web_search_search_cmd(
|
|
|
2167
2180
|
crawler_config_raw=crawler_config_raw,
|
|
2168
2181
|
output_json=output_json,
|
|
2169
2182
|
config_path=resolved_config,
|
|
2183
|
+
chat_id=chat_id,
|
|
2170
2184
|
)
|
|
2171
2185
|
_emit_web_search(ctx, output)
|
|
2172
2186
|
|
|
@@ -2222,6 +2236,18 @@ Examples:
|
|
|
2222
2236
|
is_flag=True,
|
|
2223
2237
|
help="Output results as JSON.",
|
|
2224
2238
|
)
|
|
2239
|
+
@click.option(
|
|
2240
|
+
"--chat-id",
|
|
2241
|
+
"chat_id",
|
|
2242
|
+
default=None,
|
|
2243
|
+
envvar="UNIQUE_CHAT_ID",
|
|
2244
|
+
help=(
|
|
2245
|
+
"Chat id this call is made on behalf of. Defaults to $UNIQUE_CHAT_ID "
|
|
2246
|
+
"(auto-set in Conduct sandboxes). When set, the space's Web Search "
|
|
2247
|
+
"toggle is enforced server-side and the call is rejected if Web "
|
|
2248
|
+
"Search is disabled for that space."
|
|
2249
|
+
),
|
|
2250
|
+
)
|
|
2225
2251
|
@click.pass_context
|
|
2226
2252
|
def web_search_crawl_cmd(
|
|
2227
2253
|
ctx: click.Context,
|
|
@@ -2231,6 +2257,7 @@ def web_search_crawl_cmd(
|
|
|
2231
2257
|
crawler_config_raw: str | None,
|
|
2232
2258
|
config_path: str | None,
|
|
2233
2259
|
output_json: bool,
|
|
2260
|
+
chat_id: str | None,
|
|
2234
2261
|
) -> None:
|
|
2235
2262
|
"""Crawl URLs via the Unique public API."""
|
|
2236
2263
|
url_list: list[str] = list(urls)
|
|
@@ -2248,6 +2275,7 @@ def web_search_crawl_cmd(
|
|
|
2248
2275
|
crawler_config_raw=crawler_config_raw,
|
|
2249
2276
|
output_json=output_json,
|
|
2250
2277
|
config_path=resolved_config,
|
|
2278
|
+
chat_id=chat_id,
|
|
2251
2279
|
)
|
|
2252
2280
|
_emit_web_search(ctx, output)
|
|
2253
2281
|
|
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import hashlib
|
|
5
6
|
import json
|
|
6
7
|
import logging
|
|
8
|
+
import os
|
|
7
9
|
import re
|
|
8
10
|
import sys
|
|
9
11
|
from pathlib import Path
|
|
@@ -44,6 +46,14 @@ _MCP_OUTPUT_TEXT_CHAR_LIMIT = 200_000
|
|
|
44
46
|
# the result carries no recognizable title.
|
|
45
47
|
_MCP_REFS_LOG_RELATIVE_PATH = Path(".unique") / "mcp-refs.jsonl"
|
|
46
48
|
_MCP_REFS_LOCK_FILENAME = "mcp-refs.lock"
|
|
49
|
+
# Persistent per-chat seed for ``[mcpsourceN]`` numbering. The SI runner wipes
|
|
50
|
+
# ``mcp-refs.jsonl`` between turns, so manifest-derived numbering restarts at 1
|
|
51
|
+
# every turn; this sibling file records the highest source number ever assigned
|
|
52
|
+
# in the chat so numbering stays monotonic across turns (UN-23199). Body is
|
|
53
|
+
# ``{"maxSourceNumber": <int>}``; the monorepo runner preserves this file across
|
|
54
|
+
# its per-turn reset. Absent/unreadable → seed 0 → identical to pre-seed
|
|
55
|
+
# behavior (forward/backward compatible).
|
|
56
|
+
_MCP_REFS_SEED_FILENAME = "mcp-refs-seed.json"
|
|
47
57
|
_MCP_SNIPPET_CHAR_LIMIT = 300
|
|
48
58
|
# Writer-side cap on the per-item ``text`` recorded in the refs manifest — the
|
|
49
59
|
# cited item's underlying text, consumed by the runner's hallucination check to
|
|
@@ -533,12 +543,75 @@ def _next_mcp_source_number(entries: list[dict[str, Any]]) -> int:
|
|
|
533
543
|
return max(numbers, default=0) + 1
|
|
534
544
|
|
|
535
545
|
|
|
546
|
+
def _read_mcp_refs_seed(seed_path: Path) -> int:
|
|
547
|
+
"""Read the persisted chat-wide ``maxSourceNumber`` from the seed file.
|
|
548
|
+
|
|
549
|
+
Best-effort: returns ``0`` when the file is absent, unreadable, or malformed
|
|
550
|
+
(never raises), so a missing seed reproduces the pre-seed numbering exactly.
|
|
551
|
+
"""
|
|
552
|
+
try:
|
|
553
|
+
raw = seed_path.read_text(encoding="utf-8")
|
|
554
|
+
value = json.loads(raw).get("maxSourceNumber")
|
|
555
|
+
if isinstance(value, int) and not isinstance(value, bool):
|
|
556
|
+
return value
|
|
557
|
+
except (OSError, ValueError, TypeError, AttributeError):
|
|
558
|
+
return 0
|
|
559
|
+
return 0
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
def _write_mcp_refs_seed(seed_path: Path, value: int) -> None:
|
|
563
|
+
"""Persist the chat-wide ``maxSourceNumber`` seed (best-effort, never raises).
|
|
564
|
+
|
|
565
|
+
Only writes when ``value`` exceeds the currently persisted seed, so numbers
|
|
566
|
+
never regress across turns.
|
|
567
|
+
|
|
568
|
+
The temp file is opened with ``O_NOFOLLOW`` so a symlink pre-planted at the
|
|
569
|
+
predictable temp path cannot redirect the write outside the refs directory
|
|
570
|
+
(defense in depth on top of the per-user workspace sandbox). ``os.replace``
|
|
571
|
+
operates on the directory entry and does not follow a symlink at the target.
|
|
572
|
+
``O_NOFOLLOW`` is POSIX-only; on platforms lacking it (e.g. Windows) it
|
|
573
|
+
degrades to ``0`` via ``getattr`` rather than raising ``AttributeError`` —
|
|
574
|
+
which, being outside the ``OSError`` handler, would otherwise bubble out of
|
|
575
|
+
``_annotate_mcp_results_for_citations`` after entries were written and blank
|
|
576
|
+
the Sources block.
|
|
577
|
+
"""
|
|
578
|
+
try:
|
|
579
|
+
if value <= _read_mcp_refs_seed(seed_path):
|
|
580
|
+
return
|
|
581
|
+
seed_path.parent.mkdir(parents=True, exist_ok=True)
|
|
582
|
+
tmp_path = seed_path.with_suffix(seed_path.suffix + ".tmp")
|
|
583
|
+
flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC | getattr(os, "O_NOFOLLOW", 0)
|
|
584
|
+
fd = os.open(tmp_path, flags, 0o600)
|
|
585
|
+
with os.fdopen(fd, "w", encoding="utf-8") as handle:
|
|
586
|
+
handle.write(json.dumps({"maxSourceNumber": value}))
|
|
587
|
+
os.replace(tmp_path, seed_path)
|
|
588
|
+
except OSError as exc:
|
|
589
|
+
_LOGGER.warning("mcp: failed to write refs seed: %s", exc)
|
|
590
|
+
|
|
591
|
+
|
|
536
592
|
def _item_dedup_key(tool_name: str, item: dict[str, Any]) -> str:
|
|
537
|
-
"""Dedup by title when present (same item = one chip), else by tool
|
|
593
|
+
"""Dedup by title when present (same item = one chip), else by tool + a short
|
|
594
|
+
content hash of the item's text.
|
|
595
|
+
|
|
596
|
+
Two distinct title-less fetches through one tool return different bodies, so
|
|
597
|
+
hashing the text keeps them as separate sources instead of collapsing onto
|
|
598
|
+
one number (identical bodies still merge). NOTE: this intentionally weakens
|
|
599
|
+
the search-then-fetch text-upgrade merge for title-less items only — titled
|
|
600
|
+
items still merge by title as before.
|
|
601
|
+
|
|
602
|
+
The text is capped at ``_MCP_REF_TEXT_CHAR_LIMIT`` BEFORE hashing — the same
|
|
603
|
+
cap the manifest stores under ``text``. Without it, the first call (live,
|
|
604
|
+
full-length item text) and a later call rebuilding this key from the
|
|
605
|
+
truncated manifest entry would hash to different values, so an oversized
|
|
606
|
+
title-less result would be re-assigned a duplicate ``[mcpsourceN]`` instead
|
|
607
|
+
of deduping.
|
|
608
|
+
"""
|
|
538
609
|
title = item.get("title")
|
|
539
610
|
if isinstance(title, str) and title.strip():
|
|
540
611
|
return f"title:{tool_name}:{title.strip()}"
|
|
541
|
-
|
|
612
|
+
capped_text = (item.get("text") or "")[:_MCP_REF_TEXT_CHAR_LIMIT]
|
|
613
|
+
text_hash = hashlib.sha256(capped_text.encode("utf-8")).hexdigest()[:12]
|
|
614
|
+
return f"tool:{tool_name}:{text_hash}"
|
|
542
615
|
|
|
543
616
|
|
|
544
617
|
def _ref_text(item: dict[str, Any]) -> str | None:
|
|
@@ -563,8 +636,11 @@ def _annotate_mcp_results_for_citations(
|
|
|
563
636
|
"""Assign per-turn ``[mcpsourceN]`` numbers to each retrieved item and append
|
|
564
637
|
the refs manifest. Returns ``[(sourceNumber, item)]`` for the Sources block.
|
|
565
638
|
|
|
566
|
-
Items dedup by title across the turn (same item keeps one number)
|
|
567
|
-
|
|
639
|
+
Items dedup by title across the turn (same item keeps one number); a
|
|
640
|
+
title-less item dedups by tool plus a content hash of its (capped) text, so
|
|
641
|
+
two distinct title-less results through one tool get distinct numbers while
|
|
642
|
+
identical bodies still merge (this weakens the search-then-fetch text-upgrade
|
|
643
|
+
merge for title-less items only). Best-effort — returns ``[]`` on any failure
|
|
568
644
|
(the tool result is unaffected; only the citation Sources block is skipped).
|
|
569
645
|
"""
|
|
570
646
|
refs_log_path = refs_log_path or (Path.cwd() / _MCP_REFS_LOG_RELATIVE_PATH)
|
|
@@ -581,6 +657,14 @@ def _annotate_mcp_results_for_citations(
|
|
|
581
657
|
refs_log_path, lock_filename=_MCP_REFS_LOCK_FILENAME
|
|
582
658
|
):
|
|
583
659
|
entries = _read_turn_refs_manifest(refs_log_path)
|
|
660
|
+
# Chat-wide monotonic seed: the runner wipes ``mcp-refs.jsonl``
|
|
661
|
+
# between turns, so manifest-derived numbering restarts at 1 each
|
|
662
|
+
# turn. The seed file (preserved across the runner's per-turn reset)
|
|
663
|
+
# records the highest number ever assigned in the chat so a fresh
|
|
664
|
+
# number exceeds both the current manifest max AND the chat-wide max.
|
|
665
|
+
seed_path = refs_log_path.parent / _MCP_REFS_SEED_FILENAME
|
|
666
|
+
seed = _read_mcp_refs_seed(seed_path)
|
|
667
|
+
highest_assigned = seed
|
|
584
668
|
numbers_by_key: dict[str, int] = {}
|
|
585
669
|
for entry in entries:
|
|
586
670
|
if isinstance(entry.get("sourceNumber"), int):
|
|
@@ -598,7 +682,7 @@ def _annotate_mcp_results_for_citations(
|
|
|
598
682
|
key = _item_dedup_key(tool_name, item)
|
|
599
683
|
source_number = numbers_by_key.get(key)
|
|
600
684
|
if source_number is None:
|
|
601
|
-
source_number = _next_mcp_source_number(entries)
|
|
685
|
+
source_number = max(_next_mcp_source_number(entries), seed + 1)
|
|
602
686
|
manifest_entry = {
|
|
603
687
|
"sourceNumber": source_number,
|
|
604
688
|
"toolName": tool_name,
|
|
@@ -618,6 +702,7 @@ def _annotate_mcp_results_for_citations(
|
|
|
618
702
|
numbers_by_key[key] = source_number
|
|
619
703
|
entries.append(manifest_entry)
|
|
620
704
|
entries_by_number[source_number] = manifest_entry
|
|
705
|
+
highest_assigned = max(highest_assigned, source_number)
|
|
621
706
|
else:
|
|
622
707
|
# Deduped: a prior call already claimed this source number.
|
|
623
708
|
# Backfill ``details`` the first call may have lacked (the
|
|
@@ -649,6 +734,10 @@ def _annotate_mcp_results_for_citations(
|
|
|
649
734
|
_LOGGER.warning(
|
|
650
735
|
"mcp: failed to backfill refs manifest enrichment: %s", exc
|
|
651
736
|
)
|
|
737
|
+
# Persist the chat-wide max so next turn's numbering continues above
|
|
738
|
+
# it even after the runner wipes ``mcp-refs.jsonl``.
|
|
739
|
+
if highest_assigned > seed:
|
|
740
|
+
_write_mcp_refs_seed(seed_path, highest_assigned)
|
|
652
741
|
except (UnsafeRefsLogPathError, OSError) as exc:
|
|
653
742
|
_LOGGER.warning("mcp: failed to append refs manifest: %s", exc)
|
|
654
743
|
return []
|
|
@@ -658,13 +747,19 @@ def _annotate_mcp_results_for_citations(
|
|
|
658
747
|
return annotated
|
|
659
748
|
|
|
660
749
|
|
|
661
|
-
def _citation_sources_block(
|
|
750
|
+
def _citation_sources_block(
|
|
751
|
+
annotated: list[tuple[int, dict[str, Any]]], *, tool_name: str
|
|
752
|
+
) -> str:
|
|
662
753
|
"""Tell the agent which marker to cite each retrieved item with.
|
|
663
754
|
|
|
664
755
|
Rendered *before* the tool output (leading block, not a trailing footer):
|
|
665
756
|
the agent harness spills oversized tool results to a file wholesale, and a
|
|
666
757
|
partial or programmatic read of that file only reliably sees the head — a
|
|
667
758
|
trailing marker list would be exactly what such reads miss (UN-22309).
|
|
759
|
+
|
|
760
|
+
Each line names the ``tool_name`` alongside the marker so the model has a
|
|
761
|
+
stronger anchor than a bare integer: ``{title} — {tool_name}`` when the item
|
|
762
|
+
carries a title, or ``{tool_name}`` for the title-less fallback.
|
|
668
763
|
"""
|
|
669
764
|
if not annotated:
|
|
670
765
|
return ""
|
|
@@ -674,7 +769,11 @@ def _citation_sources_block(annotated: list[tuple[int, dict[str, Any]]]) -> str:
|
|
|
674
769
|
"will not be referenced in the answer:",
|
|
675
770
|
]
|
|
676
771
|
for source_number, item in annotated:
|
|
677
|
-
|
|
772
|
+
title = item.get("title")
|
|
773
|
+
if isinstance(title, str) and title.strip():
|
|
774
|
+
label = f"{title.strip()} — {tool_name}"
|
|
775
|
+
else:
|
|
776
|
+
label = tool_name
|
|
678
777
|
lines.append(f" [mcpsource{source_number}] {label}")
|
|
679
778
|
return "\n".join(lines)
|
|
680
779
|
|
|
@@ -758,7 +857,7 @@ def record_mcp_citations(
|
|
|
758
857
|
reference_mapping=reference_mapping,
|
|
759
858
|
fallback_text=formatted_text,
|
|
760
859
|
)
|
|
761
|
-
return _citation_sources_block(annotated)
|
|
860
|
+
return _citation_sources_block(annotated, tool_name=tool_name)
|
|
762
861
|
|
|
763
862
|
|
|
764
863
|
def _read_payload(
|
{unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/cli/commands/web_search.py
RENAMED
|
@@ -317,6 +317,7 @@ def cmd_web_search(
|
|
|
317
317
|
crawler_config_raw: str | None = None,
|
|
318
318
|
output_json: bool = False,
|
|
319
319
|
config_path: str | None = None,
|
|
320
|
+
chat_id: str | None = None,
|
|
320
321
|
) -> str:
|
|
321
322
|
"""Run a web search via the public API.
|
|
322
323
|
|
|
@@ -341,6 +342,8 @@ def cmd_web_search(
|
|
|
341
342
|
then ``~/.unique-websearch.json``.
|
|
342
343
|
output_json: When ``True``, return a JSON envelope instead of a
|
|
343
344
|
human-friendly table.
|
|
345
|
+
chat_id: Optional chat id this call is made on behalf of. When
|
|
346
|
+
set, the space's Web Search toggle is enforced server-side.
|
|
344
347
|
"""
|
|
345
348
|
file_overrides = _load_file_overrides(config_path)
|
|
346
349
|
if isinstance(file_overrides, str):
|
|
@@ -368,6 +371,8 @@ def cmd_web_search(
|
|
|
368
371
|
params["searchEngineConfig"] = engine_override
|
|
369
372
|
if crawler_override is not None:
|
|
370
373
|
params["crawlerConfig"] = crawler_override
|
|
374
|
+
if chat_id:
|
|
375
|
+
params["chatId"] = chat_id
|
|
371
376
|
|
|
372
377
|
try:
|
|
373
378
|
resource = unique_sdk.WebSearch.search(
|
|
@@ -394,6 +399,7 @@ def cmd_web_crawl(
|
|
|
394
399
|
crawler_config_raw: str | None = None,
|
|
395
400
|
output_json: bool = False,
|
|
396
401
|
config_path: str | None = None,
|
|
402
|
+
chat_id: str | None = None,
|
|
397
403
|
) -> str:
|
|
398
404
|
"""Crawl a list of URLs via the public API.
|
|
399
405
|
|
|
@@ -410,6 +416,8 @@ def cmd_web_crawl(
|
|
|
410
416
|
:func:`cmd_web_search` for resolution rules.
|
|
411
417
|
output_json: When ``True``, return a JSON envelope instead of a
|
|
412
418
|
human-friendly table.
|
|
419
|
+
chat_id: Optional chat id this call is made on behalf of. When
|
|
420
|
+
set, the space's Web Search toggle is enforced server-side.
|
|
413
421
|
"""
|
|
414
422
|
if not urls:
|
|
415
423
|
return f"{WEB_CRAWL_ERROR_PREFIX} no URLs provided. Pass URLs as arguments or use --stdin."
|
|
@@ -432,6 +440,8 @@ def cmd_web_crawl(
|
|
|
432
440
|
params: dict[str, Any] = {"urls": list(urls), "parallel": parallel}
|
|
433
441
|
if crawler_override is not None:
|
|
434
442
|
params["crawlerConfig"] = crawler_override
|
|
443
|
+
if chat_id:
|
|
444
|
+
params["chatId"] = chat_id
|
|
435
445
|
|
|
436
446
|
try:
|
|
437
447
|
resource = unique_sdk.WebCrawl.crawl(
|
|
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.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/api_resources/__init__.py
RENAMED
|
File without changes
|
{unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/api_resources/_acronyms.py
RENAMED
|
File without changes
|
{unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/api_resources/_agentic_table.py
RENAMED
|
File without changes
|
{unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/api_resources/_analytics_order.py
RENAMED
|
File without changes
|
{unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/api_resources/_benchmarking.py
RENAMED
|
File without changes
|
{unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/api_resources/_briefing.py
RENAMED
|
File without changes
|
{unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/api_resources/_chat_completion.py
RENAMED
|
File without changes
|
{unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/api_resources/_content.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/api_resources/_elicitation.py
RENAMED
|
File without changes
|
{unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/api_resources/_embedding.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/api_resources/_integrated.py
RENAMED
|
File without changes
|
{unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/api_resources/_llm_models.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/api_resources/_message.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/api_resources/_message_log.py
RENAMED
|
File without changes
|
{unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/api_resources/_message_tool.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/api_resources/_scheduled_task.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/api_resources/_search_string.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
|
{unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/cli/commands/agentic_table.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/cli/commands/cite_file.py
RENAMED
|
File without changes
|
{unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/cli/commands/dynamic_frontend.py
RENAMED
|
File without changes
|
{unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/cli/commands/elicitation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/cli/commands/navigation.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/cli/commands/scheduled_tasks.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/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.32.0.dev2 → unique_sdk-2026.32.0.dev4}/unique_sdk/utils/analytics_order_run.py
RENAMED
|
File without changes
|
{unique_sdk-2026.32.0.dev2 → unique_sdk-2026.32.0.dev4}/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
|