unique-sdk 2026.30.0.dev0__tar.gz → 2026.30.0.dev2__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.dev0 → unique_sdk-2026.30.0.dev2}/PKG-INFO +1 -1
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/pyproject.toml +1 -1
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/_unique_ql.py +2 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/cli/commands/mcp.py +312 -37
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/cli/metadata_filter.py +1 -1
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/cli/state.py +34 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/README.md +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/__init__.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/_api_requestor.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/_api_resource.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/_api_version.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/_error.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/_http_client.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/_list_object.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/_object_classes.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/_request_options.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/_unique_object.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/_unique_response.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/_util.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/_version.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/_webhook.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/api_resources/__init__.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/api_resources/_acronyms.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/api_resources/_agentic_table.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/api_resources/_analytics_order.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/api_resources/_benchmarking.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/api_resources/_briefing.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/api_resources/_chat_completion.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/api_resources/_content.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/api_resources/_dynamic_frontend.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/api_resources/_elicitation.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/api_resources/_embedding.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/api_resources/_event.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/api_resources/_folder.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/api_resources/_group.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/api_resources/_integrated.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/api_resources/_llm_models.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/api_resources/_mcp.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/api_resources/_message.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/api_resources/_message_assessment.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/api_resources/_message_execution.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/api_resources/_message_log.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/api_resources/_message_tool.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/api_resources/_module.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/api_resources/_scheduled_task.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/api_resources/_search.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/api_resources/_search_string.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/api_resources/_short_term_memory.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/api_resources/_space.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/api_resources/_user.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/api_resources/_web_search.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/cli/__init__.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/cli/__main__.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/cli/cli.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/cli/commands/__init__.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/cli/commands/_citation_manifest.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/cli/commands/browser.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/cli/commands/cite_file.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/cli/commands/dynamic_frontend.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/cli/commands/elicitation.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/cli/commands/files.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/cli/commands/folders.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/cli/commands/navigation.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/cli/commands/read.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/cli/commands/scheduled_tasks.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/cli/commands/search.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/cli/commands/subagent.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/cli/commands/web_search.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/cli/commands/web_search_config.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/cli/config.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/cli/formatting.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/cli/shell.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/cli/skills/unique-cli-dynamic-frontend/SKILL.md +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/cli/skills/unique-cli-elicitation/SKILL.md +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/cli/skills/unique-cli-file-management/SKILL.md +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/cli/skills/unique-cli-mcp/SKILL.md +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/cli/skills/unique-cli-scheduled-tasks/SKILL.md +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/cli/skills/unique-cli-search/SKILL.md +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/cli/skills/unique-cli-subagent/SKILL.md +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/cli/skills/unique-cli-web-search/SKILL.md +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/utils/analytics_order_run.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/utils/benchmarking_run.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/utils/chat_history.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/utils/chat_in_space.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/utils/file_io.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/utils/sources.py +0 -0
- {unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/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.dev2
|
|
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>
|
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
6
|
import logging
|
|
7
|
+
import re
|
|
7
8
|
import sys
|
|
8
9
|
from pathlib import Path
|
|
9
10
|
from typing import Any
|
|
@@ -27,8 +28,13 @@ _LOGGER = logging.getLogger(__name__)
|
|
|
27
28
|
# (referencing is UN-21285, tracked separately).
|
|
28
29
|
_MCP_OUTPUT_LOG_RELATIVE_PATH = Path(".unique") / "mcp-output.jsonl"
|
|
29
30
|
# Writer-side cap so a single huge/raw tool result cannot bloat the manifest.
|
|
30
|
-
#
|
|
31
|
-
|
|
31
|
+
# This manifest is the groundedness check's source of truth, so the cap is
|
|
32
|
+
# sized against the eval model's context window rather than kept minimal:
|
|
33
|
+
# GPT-4o's 128k-token input fits ~400k chars, and the runner bounds the
|
|
34
|
+
# combined per-turn payload separately (UN-22309). At the previous 50k, a
|
|
35
|
+
# large list result (e.g. a 115k-char Jira search) lost most of its items
|
|
36
|
+
# before the judge saw them, flagging well-grounded answers as hallucinations.
|
|
37
|
+
_MCP_OUTPUT_TEXT_CHAR_LIMIT = 200_000
|
|
32
38
|
|
|
33
39
|
# Per-turn manifest of citable MCP sources, consumed by the runner to stitch
|
|
34
40
|
# ``[mcpsourceN]`` markers into ``<sup>N</sup>`` footnotes + reference chips
|
|
@@ -39,7 +45,6 @@ _MCP_OUTPUT_TEXT_CHAR_LIMIT = 50_000
|
|
|
39
45
|
_MCP_REFS_LOG_RELATIVE_PATH = Path(".unique") / "mcp-refs.jsonl"
|
|
40
46
|
_MCP_REFS_LOCK_FILENAME = "mcp-refs.lock"
|
|
41
47
|
_MCP_SNIPPET_CHAR_LIMIT = 300
|
|
42
|
-
_MCP_MAX_ITEMS_PER_CALL = 8
|
|
43
48
|
|
|
44
49
|
# Keys an MCP tool's JSON result commonly uses for a record's human title.
|
|
45
50
|
_TITLE_KEYS = ("title", "name", "displayName", "subject", "summary", "key")
|
|
@@ -152,28 +157,271 @@ def _details_from_json(obj: dict[str, Any]) -> str | None:
|
|
|
152
157
|
return " - ".join(parts)[:_MCP_DETAILS_CHAR_LIMIT]
|
|
153
158
|
|
|
154
159
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
160
|
+
# Keys under which a JSON object commonly wraps the actual list of retrieved
|
|
161
|
+
# records, e.g. Jira's ``{"issues": [...]}`` or a generic ``{"results": [...]}``.
|
|
162
|
+
# When a result wraps its records this way, we iterate the list so each record
|
|
163
|
+
# becomes its own reference instead of collapsing to one title-less chip.
|
|
164
|
+
_CONTAINER_KEYS = (
|
|
165
|
+
"issues",
|
|
166
|
+
"results",
|
|
167
|
+
"items",
|
|
168
|
+
"values",
|
|
169
|
+
"data",
|
|
170
|
+
"hits",
|
|
171
|
+
"records",
|
|
172
|
+
"entries",
|
|
173
|
+
"elements",
|
|
174
|
+
"content",
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
# Subset of ``_CONTAINER_KEYS`` that are also plausible fields of a *single*
|
|
178
|
+
# titled record — e.g. a page/document payload ``{"title": ..., "content": [...]}``
|
|
179
|
+
# whose ``content`` is the body, not a list of separate results. When one of
|
|
180
|
+
# these matches on an object that carries its own top-level title, we keep the
|
|
181
|
+
# object as one record rather than splitting it into title-less inner rows.
|
|
182
|
+
_AMBIGUOUS_CONTAINER_KEYS = ("data", "content")
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _loads_embedded_json(text: str) -> Any:
|
|
186
|
+
"""Parse a JSON value from a tool-result string, tolerating a non-JSON
|
|
187
|
+
preamble.
|
|
188
|
+
|
|
189
|
+
Some MCP servers (e.g. Atlassian) prefix the JSON body with a human-readable
|
|
190
|
+
notice like ``[IMPORTANT: ...]\\n{...}``, so a direct ``json.loads`` of the
|
|
191
|
+
whole string fails. Try a direct parse first, then fall back to decoding the
|
|
192
|
+
first JSON object/array that appears. Returns ``None`` when no JSON is found.
|
|
159
193
|
"""
|
|
160
194
|
try:
|
|
161
|
-
|
|
195
|
+
return json.loads(text)
|
|
162
196
|
except (ValueError, TypeError):
|
|
197
|
+
pass
|
|
198
|
+
decoder = json.JSONDecoder()
|
|
199
|
+
for index, char in enumerate(text):
|
|
200
|
+
if char in "{[":
|
|
201
|
+
try:
|
|
202
|
+
value, _ = decoder.raw_decode(text, index)
|
|
203
|
+
except ValueError:
|
|
204
|
+
continue
|
|
205
|
+
return value
|
|
206
|
+
return None
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _records_from_parsed(parsed: Any) -> list[dict[str, Any]]:
|
|
210
|
+
"""Normalize a parsed JSON value to a flat list of record dicts, unwrapping
|
|
211
|
+
a single container key (``{"issues": [...]}``) when present."""
|
|
212
|
+
if isinstance(parsed, list):
|
|
213
|
+
return [entry for entry in parsed if isinstance(entry, dict)]
|
|
214
|
+
if isinstance(parsed, dict):
|
|
215
|
+
for key in _CONTAINER_KEYS:
|
|
216
|
+
value = parsed.get(key)
|
|
217
|
+
if isinstance(value, list):
|
|
218
|
+
records = [entry for entry in value if isinstance(entry, dict)]
|
|
219
|
+
if records:
|
|
220
|
+
# A single titled record must not be split apart by an
|
|
221
|
+
# incidental ambiguous key (a page body under ``content`` /
|
|
222
|
+
# ``data``) — keep the object as one record so its title
|
|
223
|
+
# survives instead of collapsing to a title-less chip.
|
|
224
|
+
if key in _AMBIGUOUS_CONTAINER_KEYS and _title_from_json(parsed):
|
|
225
|
+
return [parsed]
|
|
226
|
+
return records
|
|
227
|
+
return [parsed]
|
|
228
|
+
return []
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _titles_from_json(text: str) -> list[dict[str, Any]]:
|
|
232
|
+
"""Best-effort: pull a human title out of a JSON result, e.g. an Atlassian
|
|
233
|
+
page/issue returned as JSON-in-text. Tolerates a non-JSON preamble before
|
|
234
|
+
the JSON body and unwraps a container key (``{"issues": [...]}``) so each
|
|
235
|
+
retrieved record yields its own item. Returns [] when the text carries no
|
|
236
|
+
JSON or no record has a recognizable title.
|
|
237
|
+
"""
|
|
238
|
+
parsed = _loads_embedded_json(text)
|
|
239
|
+
if parsed is None:
|
|
163
240
|
return []
|
|
164
|
-
candidates = parsed if isinstance(parsed, list) else [parsed]
|
|
165
241
|
items: list[dict[str, Any]] = []
|
|
166
|
-
for entry in
|
|
167
|
-
|
|
168
|
-
|
|
242
|
+
for entry in _records_from_parsed(parsed):
|
|
243
|
+
title = _title_from_json(entry)
|
|
244
|
+
if title:
|
|
245
|
+
items.append(
|
|
246
|
+
{
|
|
247
|
+
"title": title,
|
|
248
|
+
"snippet": None,
|
|
249
|
+
"details": _details_from_json(entry),
|
|
250
|
+
}
|
|
251
|
+
)
|
|
252
|
+
return items
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def _get_by_dotted_path(obj: Any, path: str) -> Any:
|
|
256
|
+
"""Walk a dotted path over dicts and lists (numeric segments index lists),
|
|
257
|
+
e.g. ``"issues"``, ``"fields.summary"``, ``"result.items.0.key"``. Returns
|
|
258
|
+
``None`` when any segment is missing."""
|
|
259
|
+
current = obj
|
|
260
|
+
for segment in path.split("."):
|
|
261
|
+
if isinstance(current, dict):
|
|
262
|
+
current = current.get(segment)
|
|
263
|
+
elif isinstance(current, list) and segment.lstrip("-").isdigit():
|
|
264
|
+
index = int(segment)
|
|
265
|
+
try:
|
|
266
|
+
current = current[index]
|
|
267
|
+
except IndexError:
|
|
268
|
+
return None
|
|
269
|
+
else:
|
|
270
|
+
return None
|
|
271
|
+
if current is None:
|
|
272
|
+
return None
|
|
273
|
+
return current
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
_TEMPLATE_TOKEN = re.compile(r"\{([^{}]+)\}")
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def _render_title_template(template: str, record: dict[str, Any]) -> str | None:
|
|
280
|
+
"""Substitute ``{dotted.path}`` tokens from ``record`` into ``template``.
|
|
281
|
+
Missing tokens render empty; returns ``None`` if the result is blank."""
|
|
282
|
+
|
|
283
|
+
def _sub(match: re.Match[str]) -> str:
|
|
284
|
+
value = _get_by_dotted_path(record, match.group(1).strip())
|
|
285
|
+
return str(value).strip() if value is not None else ""
|
|
286
|
+
|
|
287
|
+
rendered = _TEMPLATE_TOKEN.sub(_sub, template).strip()
|
|
288
|
+
# Collapse artifacts left by empty tokens (e.g. a dangling " — ").
|
|
289
|
+
rendered = rendered.strip(" -—–|:").strip()
|
|
290
|
+
return rendered or None
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def _mapped_records(response: Any, list_path: str | None) -> list[Any]:
|
|
294
|
+
"""Locate the records a reference mapping applies to, preferring the
|
|
295
|
+
MCP-native ``structuredContent`` then any JSON-in-text block (preamble
|
|
296
|
+
tolerant). ``list_path`` (dotted) points at the array; when unset the parsed
|
|
297
|
+
value itself is used. List entries may be objects (field/template titles) or
|
|
298
|
+
plain strings (text titles), so both are kept; a single object is wrapped.
|
|
299
|
+
An empty object/list is ignored (not treated as a record) so the caller can
|
|
300
|
+
still fall back to ``titleFromText`` / the generic heuristic — e.g. an empty
|
|
301
|
+
``structuredContent`` alongside a Markdown doc."""
|
|
302
|
+
sources: list[Any] = []
|
|
303
|
+
structured = getattr(response, "structuredContent", None) or getattr(
|
|
304
|
+
response, "structured_content", None
|
|
305
|
+
)
|
|
306
|
+
if structured is not None:
|
|
307
|
+
sources.append(structured)
|
|
308
|
+
for block in getattr(response, "content", None) or []:
|
|
309
|
+
if isinstance(block, dict) and block.get("type") == "text":
|
|
310
|
+
parsed = _loads_embedded_json(block.get("text") or "")
|
|
311
|
+
if parsed is not None:
|
|
312
|
+
sources.append(parsed)
|
|
313
|
+
|
|
314
|
+
for parsed in sources:
|
|
315
|
+
target = _get_by_dotted_path(parsed, list_path) if list_path else parsed
|
|
316
|
+
if isinstance(target, list):
|
|
317
|
+
records = [entry for entry in target if isinstance(entry, (dict, str))]
|
|
318
|
+
if records:
|
|
319
|
+
return records
|
|
320
|
+
elif isinstance(target, dict) and target:
|
|
321
|
+
return [target]
|
|
322
|
+
return []
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
_MCP_TEXT_TITLE_DEFAULT_CHARS = 120
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def _leading_title_line(text: str | None, max_chars: int) -> str | None:
|
|
329
|
+
"""First non-empty line of ``text`` with leading Markdown heading/list
|
|
330
|
+
markers (``# * - >``) stripped, capped at ``max_chars``. Used to title a
|
|
331
|
+
plain-text document (or a plain-text list item) by its heading. ``None``
|
|
332
|
+
when ``text`` is not a non-empty string."""
|
|
333
|
+
if not isinstance(text, str):
|
|
334
|
+
return None
|
|
335
|
+
for line in text.splitlines():
|
|
336
|
+
stripped = line.strip().lstrip("#*->").strip()
|
|
337
|
+
if stripped:
|
|
338
|
+
return stripped[:max_chars]
|
|
339
|
+
return None
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def _first_text_title(response: Any, max_chars: int) -> str | None:
|
|
343
|
+
"""Title from the first non-empty line of the first text block — for a
|
|
344
|
+
non-JSON result such as a fetched Markdown document (e.g. ``read_doc``)."""
|
|
345
|
+
for block in getattr(response, "content", None) or []:
|
|
346
|
+
if isinstance(block, dict) and block.get("type") == "text":
|
|
347
|
+
title = _leading_title_line(block.get("text"), max_chars)
|
|
169
348
|
if title:
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
349
|
+
return title
|
|
350
|
+
return None
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def _extract_with_reference_mapping(
|
|
354
|
+
response: Any, mapping: dict[str, Any]
|
|
355
|
+
) -> list[dict[str, Any]]:
|
|
356
|
+
"""Destructure a result into ``{title, snippet, details}`` items using an
|
|
357
|
+
admin-configured reference mapping. For a list result, one item per record;
|
|
358
|
+
for a non-JSON text result with ``titleFromText`` set, a single item titled
|
|
359
|
+
from the leading text. Returns [] when the mapping locates nothing, so the
|
|
360
|
+
caller can fall back to the generic heuristic."""
|
|
361
|
+
list_path = mapping.get("listPath") or mapping.get("list_path")
|
|
362
|
+
title_path = mapping.get("titlePath") or mapping.get("title_path")
|
|
363
|
+
title_template = mapping.get("titleTemplate") or mapping.get("title_template")
|
|
364
|
+
details_path = mapping.get("detailsPath") or mapping.get("details_path")
|
|
365
|
+
title_from_text = mapping.get("titleFromText") or mapping.get("title_from_text")
|
|
366
|
+
# When titling from text and the list items are objects, this dotted path
|
|
367
|
+
# points at the text field within each item (e.g. ``content``); leave unset
|
|
368
|
+
# when each item is itself a plain string.
|
|
369
|
+
title_text_path = mapping.get("titleTextPath") or mapping.get("title_text_path")
|
|
370
|
+
try:
|
|
371
|
+
title_max_chars = int(
|
|
372
|
+
mapping.get("titleMaxChars")
|
|
373
|
+
or mapping.get("title_max_chars")
|
|
374
|
+
or _MCP_TEXT_TITLE_DEFAULT_CHARS
|
|
375
|
+
)
|
|
376
|
+
except (TypeError, ValueError):
|
|
377
|
+
title_max_chars = _MCP_TEXT_TITLE_DEFAULT_CHARS
|
|
378
|
+
|
|
379
|
+
records = _mapped_records(response, list_path)
|
|
380
|
+
items: list[dict[str, Any]] = []
|
|
381
|
+
for record in records:
|
|
382
|
+
is_dict = isinstance(record, dict)
|
|
383
|
+
if title_from_text:
|
|
384
|
+
# Title from the item's text: the item itself when it's a plain
|
|
385
|
+
# string, else its ``title_text_path`` field.
|
|
386
|
+
if isinstance(record, str):
|
|
387
|
+
text = record
|
|
388
|
+
elif is_dict and title_text_path:
|
|
389
|
+
text = _get_by_dotted_path(record, title_text_path)
|
|
390
|
+
else:
|
|
391
|
+
text = None
|
|
392
|
+
title = _leading_title_line(text, title_max_chars)
|
|
393
|
+
elif title_template and is_dict:
|
|
394
|
+
title = _render_title_template(title_template, record)
|
|
395
|
+
elif title_path and is_dict:
|
|
396
|
+
value = _get_by_dotted_path(record, title_path)
|
|
397
|
+
title = str(value).strip() if isinstance(value, (str, int, float)) else None
|
|
398
|
+
else:
|
|
399
|
+
title = None
|
|
400
|
+
if not title:
|
|
401
|
+
continue
|
|
402
|
+
details = (
|
|
403
|
+
_get_by_dotted_path(record, details_path)
|
|
404
|
+
if details_path and is_dict
|
|
405
|
+
else None
|
|
406
|
+
)
|
|
407
|
+
items.append(
|
|
408
|
+
{
|
|
409
|
+
"title": title,
|
|
410
|
+
"snippet": None,
|
|
411
|
+
"details": str(details).strip() if details else None,
|
|
412
|
+
}
|
|
413
|
+
)
|
|
414
|
+
if items:
|
|
415
|
+
return items
|
|
416
|
+
# Non-list / non-JSON result (e.g. a fetched Markdown doc): title the single
|
|
417
|
+
# chip from the leading text when the tool opts in via ``titleFromText``.
|
|
418
|
+
# Only when *no* records were located — if list records were found but
|
|
419
|
+
# yielded no usable title, defer to the generic heuristic (e.g. per-issue
|
|
420
|
+
# references) instead of a bogus text-derived chip.
|
|
421
|
+
if not records and title_from_text:
|
|
422
|
+
title = _first_text_title(response, title_max_chars)
|
|
423
|
+
if title:
|
|
424
|
+
return [{"title": title, "snippet": None, "details": None}]
|
|
177
425
|
return items
|
|
178
426
|
|
|
179
427
|
|
|
@@ -182,15 +430,23 @@ def _extract_mcp_citation_items(
|
|
|
182
430
|
*,
|
|
183
431
|
tool_name: str,
|
|
184
432
|
server_name: str | None,
|
|
433
|
+
reference_mapping: dict[str, Any] | None = None,
|
|
185
434
|
) -> list[dict[str, Any]]:
|
|
186
435
|
"""Context for what the tool retrieved: ``{title, snippet}`` per item.
|
|
187
436
|
|
|
188
|
-
|
|
437
|
+
An optional admin ``reference_mapping`` is applied first (deterministic
|
|
438
|
+
destructuring of a list result); when it yields nothing we fall back to the
|
|
439
|
+
generic heuristic: MCP ``resource_link`` names (spec-native) or a best-effort
|
|
189
440
|
JSON-title heuristic over text blocks (for tools like Atlassian that return
|
|
190
441
|
JSON-in-text). No URLs are extracted — the chip is display-only. Falls back
|
|
191
442
|
to a single title-less item (the runner names it after the tool) when the
|
|
192
443
|
result carries no recognizable title.
|
|
193
444
|
"""
|
|
445
|
+
if reference_mapping:
|
|
446
|
+
mapped = _extract_with_reference_mapping(response, reference_mapping)
|
|
447
|
+
if mapped:
|
|
448
|
+
return mapped
|
|
449
|
+
|
|
194
450
|
content = getattr(response, "content", None) or []
|
|
195
451
|
items: list[dict[str, Any]] = []
|
|
196
452
|
|
|
@@ -213,7 +469,7 @@ def _extract_mcp_citation_items(
|
|
|
213
469
|
# No recognizable title — one chip named after the tool itself.
|
|
214
470
|
items.append({"title": None, "snippet": None})
|
|
215
471
|
|
|
216
|
-
return items
|
|
472
|
+
return items
|
|
217
473
|
|
|
218
474
|
|
|
219
475
|
def _next_mcp_source_number(entries: list[dict[str, Any]]) -> int:
|
|
@@ -239,19 +495,23 @@ def _annotate_mcp_results_for_citations(
|
|
|
239
495
|
tool_name: str,
|
|
240
496
|
server_name: str | None,
|
|
241
497
|
refs_log_path: Path | None = None,
|
|
498
|
+
reference_mapping: dict[str, Any] | None = None,
|
|
242
499
|
) -> list[tuple[int, dict[str, Any]]]:
|
|
243
500
|
"""Assign per-turn ``[mcpsourceN]`` numbers to each retrieved item and append
|
|
244
|
-
the refs manifest. Returns ``[(sourceNumber, item)]`` for the
|
|
501
|
+
the refs manifest. Returns ``[(sourceNumber, item)]`` for the Sources block.
|
|
245
502
|
|
|
246
503
|
Items dedup by title across the turn (same item keeps one number), or by
|
|
247
504
|
tool for the title-less fallback. Best-effort — returns ``[]`` on any failure
|
|
248
|
-
(the tool result is unaffected; only the citation
|
|
505
|
+
(the tool result is unaffected; only the citation Sources block is skipped).
|
|
249
506
|
"""
|
|
250
507
|
refs_log_path = refs_log_path or (Path.cwd() / _MCP_REFS_LOG_RELATIVE_PATH)
|
|
251
508
|
annotated: list[tuple[int, dict[str, Any]]] = []
|
|
252
509
|
try:
|
|
253
510
|
items = _extract_mcp_citation_items(
|
|
254
|
-
response,
|
|
511
|
+
response,
|
|
512
|
+
tool_name=tool_name,
|
|
513
|
+
server_name=server_name,
|
|
514
|
+
reference_mapping=reference_mapping,
|
|
255
515
|
)
|
|
256
516
|
with _locked_turn_refs_manifest(
|
|
257
517
|
refs_log_path, lock_filename=_MCP_REFS_LOCK_FILENAME
|
|
@@ -320,15 +580,20 @@ def _annotate_mcp_results_for_citations(
|
|
|
320
580
|
return annotated
|
|
321
581
|
|
|
322
582
|
|
|
323
|
-
def
|
|
324
|
-
"""Tell the agent which marker to cite each retrieved item with.
|
|
583
|
+
def _citation_sources_block(annotated: list[tuple[int, dict[str, Any]]]) -> str:
|
|
584
|
+
"""Tell the agent which marker to cite each retrieved item with.
|
|
585
|
+
|
|
586
|
+
Rendered *before* the tool output (leading block, not a trailing footer):
|
|
587
|
+
the agent harness spills oversized tool results to a file wholesale, and a
|
|
588
|
+
partial or programmatic read of that file only reliably sees the head — a
|
|
589
|
+
trailing marker list would be exactly what such reads miss (UN-22309).
|
|
590
|
+
"""
|
|
325
591
|
if not annotated:
|
|
326
592
|
return ""
|
|
327
593
|
lines = [
|
|
328
|
-
"",
|
|
329
594
|
"Sources — MANDATORY: every fact you take from this result MUST be "
|
|
330
|
-
"cited inline with its [mcpsourceN] marker
|
|
331
|
-
"referenced in the answer:",
|
|
595
|
+
"cited inline with its [mcpsourceN] marker from this list, or it "
|
|
596
|
+
"will not be referenced in the answer:",
|
|
332
597
|
]
|
|
333
598
|
for source_number, item in annotated:
|
|
334
599
|
label = item.get("title") or "this MCP tool result"
|
|
@@ -375,14 +640,18 @@ def record_mcp_citations(
|
|
|
375
640
|
server_name: str | None,
|
|
376
641
|
unique_dir: Path,
|
|
377
642
|
formatted_text: str,
|
|
643
|
+
reference_mapping: dict[str, Any] | None = None,
|
|
378
644
|
) -> str:
|
|
379
645
|
"""Write both per-turn MCP manifests under ``unique_dir`` and return the
|
|
380
|
-
``[mcpsourceN]`` citation
|
|
646
|
+
``[mcpsourceN]`` citation Sources block for the agent.
|
|
381
647
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
(
|
|
648
|
+
Callers must place the block *before* the tool output (leading block, not
|
|
649
|
+
a trailing footer) so the markers survive harness-side spilling/truncation
|
|
650
|
+
of large results (UN-22309). Shared by the ``unique-cli mcp`` skills flow
|
|
651
|
+
(``cmd_mcp``) and the in-process tools-mode proxy in assistants-core, so
|
|
652
|
+
both write identical manifests and blocks. ``unique_dir`` is the workspace
|
|
653
|
+
``.unique`` directory (its filenames are joined directly here — do not
|
|
654
|
+
pass the workspace root).
|
|
386
655
|
|
|
387
656
|
- ``response`` is the raw ``unique_sdk.MCP`` result, used for citation
|
|
388
657
|
extraction (titles from ``resource_link`` names / JSON bodies).
|
|
@@ -405,8 +674,9 @@ def record_mcp_citations(
|
|
|
405
674
|
tool_name=tool_name,
|
|
406
675
|
server_name=server_name,
|
|
407
676
|
refs_log_path=unique_dir / _MCP_REFS_LOG_RELATIVE_PATH.name,
|
|
677
|
+
reference_mapping=reference_mapping,
|
|
408
678
|
)
|
|
409
|
-
return
|
|
679
|
+
return _citation_sources_block(annotated)
|
|
410
680
|
|
|
411
681
|
|
|
412
682
|
def _read_payload(
|
|
@@ -490,11 +760,16 @@ def cmd_mcp(
|
|
|
490
760
|
formatted = f"mcp: formatter error ({fmt_exc}); raw response:\n{fallback}"
|
|
491
761
|
|
|
492
762
|
server_name = _server_name_from_tool(name) or getattr(response, "mcpServerId", None)
|
|
493
|
-
|
|
763
|
+
sources_block = record_mcp_citations(
|
|
494
764
|
response,
|
|
495
765
|
tool_name=name,
|
|
496
766
|
server_name=server_name,
|
|
497
767
|
unique_dir=Path.cwd() / ".unique",
|
|
498
768
|
formatted_text=formatted,
|
|
769
|
+
reference_mapping=state.mcp_tool_reference_mappings.get(name),
|
|
499
770
|
)
|
|
500
|
-
|
|
771
|
+
# Sources block FIRST: large outputs get spilled/truncated tail-first by
|
|
772
|
+
# the agent harness, and a trailing block is what partial reads miss.
|
|
773
|
+
if sources_block:
|
|
774
|
+
return sources_block + "\n\n" + formatted
|
|
775
|
+
return formatted
|
|
@@ -32,7 +32,7 @@ _SCOPE_ID_RE = re.compile(r"scope_[a-z0-9_]+", re.IGNORECASE)
|
|
|
32
32
|
# ``startswith("not")`` heuristic: that heuristic would also catch a future
|
|
33
33
|
# ``notEmpty``-style operator and invert it wrongly. Mirrors the Operator enum
|
|
34
34
|
# in unique_toolkit.content.smart_rules. See UN-21780.
|
|
35
|
-
_NEGATED_OPERATORS = frozenset({"notequals", "notin", "notcontains"})
|
|
35
|
+
_NEGATED_OPERATORS = frozenset({"notequals", "notin", "notcontains", "notoverlaps"})
|
|
36
36
|
|
|
37
37
|
ScopePathResolver = Callable[[str], "str | None"]
|
|
38
38
|
ContentOwnerPathResolver = Callable[[str], "str | None"]
|
|
@@ -13,6 +13,7 @@ from unique_sdk.cli.metadata_filter import MetadataFilter
|
|
|
13
13
|
|
|
14
14
|
_SEARCH_CONFIG_FILENAME = ".unique-search.json"
|
|
15
15
|
_UPLOADED_CONFIG_FILENAME = ".unique-uploaded.json"
|
|
16
|
+
_MCP_TOOLS_CONFIG_FILENAME = ".unique-mcp-tools.json"
|
|
16
17
|
_CHAT_FILES_MANIFEST_PATH = Path(".unique") / "chat-files.json"
|
|
17
18
|
|
|
18
19
|
|
|
@@ -48,6 +49,33 @@ def _load_uploaded_config() -> dict[str, Any]:
|
|
|
48
49
|
return data if isinstance(data, dict) else {}
|
|
49
50
|
|
|
50
51
|
|
|
52
|
+
def _load_mcp_tool_configs() -> dict[str, dict[str, Any]]:
|
|
53
|
+
"""Load per-MCP-tool reference mappings from ``.unique-mcp-tools.json``.
|
|
54
|
+
|
|
55
|
+
Written by the Swappable Intelligence runner from each configured tool's
|
|
56
|
+
``referenceMapping`` (a Unique admin override describing how to destructure
|
|
57
|
+
a list-returning result into individual citations). Shape:
|
|
58
|
+
``{"toolConfigs": {<toolName>: {listPath, titlePath, titleTemplate, ...}}}``.
|
|
59
|
+
Keyed by the same namespaced tool name the agent passes to ``unique-cli
|
|
60
|
+
mcp``. Returns ``{}`` when absent or invalid.
|
|
61
|
+
"""
|
|
62
|
+
config_path = Path.cwd() / _MCP_TOOLS_CONFIG_FILENAME
|
|
63
|
+
if not config_path.is_file():
|
|
64
|
+
return {}
|
|
65
|
+
try:
|
|
66
|
+
data = json.loads(config_path.read_text(encoding="utf-8"))
|
|
67
|
+
except (json.JSONDecodeError, OSError):
|
|
68
|
+
return {}
|
|
69
|
+
configs = data.get("toolConfigs") if isinstance(data, dict) else None
|
|
70
|
+
if not isinstance(configs, dict):
|
|
71
|
+
return {}
|
|
72
|
+
return {
|
|
73
|
+
name: mapping
|
|
74
|
+
for name, mapping in configs.items()
|
|
75
|
+
if isinstance(name, str) and isinstance(mapping, dict)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
51
79
|
def _load_uploaded_content_ids(data: dict[str, Any]) -> list[str]:
|
|
52
80
|
content_ids = data.get("contentIds")
|
|
53
81
|
if isinstance(content_ids, list) and all(isinstance(c, str) for c in content_ids):
|
|
@@ -145,6 +173,12 @@ class ShellState:
|
|
|
145
173
|
self.uploaded_search_content_ids: list[str] = _load_uploaded_content_ids(
|
|
146
174
|
_uploaded_config
|
|
147
175
|
)
|
|
176
|
+
# Per-MCP-tool reference mappings (``.unique-mcp-tools.json``), keyed by
|
|
177
|
+
# the namespaced tool name the agent passes to ``unique-cli mcp``. Used
|
|
178
|
+
# by ``cmd_mcp`` to destructure list results into individual citations.
|
|
179
|
+
self.mcp_tool_reference_mappings: dict[str, dict[str, Any]] = (
|
|
180
|
+
_load_mcp_tool_configs()
|
|
181
|
+
)
|
|
148
182
|
|
|
149
183
|
@property
|
|
150
184
|
def uploaded_search_available(self) -> bool:
|
|
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.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/api_resources/__init__.py
RENAMED
|
File without changes
|
{unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/api_resources/_acronyms.py
RENAMED
|
File without changes
|
{unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/api_resources/_agentic_table.py
RENAMED
|
File without changes
|
{unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/api_resources/_analytics_order.py
RENAMED
|
File without changes
|
{unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/api_resources/_benchmarking.py
RENAMED
|
File without changes
|
{unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/api_resources/_briefing.py
RENAMED
|
File without changes
|
{unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/api_resources/_chat_completion.py
RENAMED
|
File without changes
|
{unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/api_resources/_content.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/api_resources/_elicitation.py
RENAMED
|
File without changes
|
{unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/api_resources/_embedding.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/api_resources/_integrated.py
RENAMED
|
File without changes
|
{unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/api_resources/_llm_models.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/api_resources/_message.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/api_resources/_message_log.py
RENAMED
|
File without changes
|
{unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/api_resources/_message_tool.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/api_resources/_scheduled_task.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/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.dev0 → unique_sdk-2026.30.0.dev2}/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.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/cli/commands/cite_file.py
RENAMED
|
File without changes
|
{unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/cli/commands/dynamic_frontend.py
RENAMED
|
File without changes
|
{unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/cli/commands/elicitation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/cli/commands/navigation.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/cli/commands/scheduled_tasks.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/cli/commands/web_search.py
RENAMED
|
File without changes
|
{unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/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.dev0 → unique_sdk-2026.30.0.dev2}/unique_sdk/utils/analytics_order_run.py
RENAMED
|
File without changes
|
{unique_sdk-2026.30.0.dev0 → unique_sdk-2026.30.0.dev2}/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
|