unique-sdk 2026.32.0.dev7__tar.gz → 2026.32.0.dev9__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.
Files changed (91) hide show
  1. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/PKG-INFO +1 -1
  2. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/pyproject.toml +1 -1
  3. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/__init__.py +0 -3
  4. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/cli/cli.py +0 -103
  5. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/cli/commands/web_search.py +39 -2
  6. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/cli/skills/unique-cli-file-management/SKILL.md +25 -18
  7. unique_sdk-2026.32.0.dev7/unique_sdk/api_resources/_dynamic_frontend.py +0 -101
  8. unique_sdk-2026.32.0.dev7/unique_sdk/cli/commands/dynamic_frontend.py +0 -155
  9. unique_sdk-2026.32.0.dev7/unique_sdk/cli/skills/unique-cli-dynamic-frontend/SKILL.md +0 -151
  10. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/README.md +0 -0
  11. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/_api_requestor.py +0 -0
  12. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/_api_resource.py +0 -0
  13. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/_api_version.py +0 -0
  14. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/_error.py +0 -0
  15. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/_http_client.py +0 -0
  16. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/_list_object.py +0 -0
  17. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/_object_classes.py +0 -0
  18. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/_request_options.py +0 -0
  19. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/_unique_object.py +0 -0
  20. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/_unique_ql.py +0 -0
  21. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/_unique_response.py +0 -0
  22. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/_util.py +0 -0
  23. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/_version.py +0 -0
  24. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/_webhook.py +0 -0
  25. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/api_resources/__init__.py +0 -0
  26. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/api_resources/_acronyms.py +0 -0
  27. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/api_resources/_agentic_table.py +0 -0
  28. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/api_resources/_analytics_order.py +0 -0
  29. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/api_resources/_benchmarking.py +0 -0
  30. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/api_resources/_briefing.py +0 -0
  31. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/api_resources/_chat_completion.py +0 -0
  32. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/api_resources/_content.py +0 -0
  33. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/api_resources/_elicitation.py +0 -0
  34. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/api_resources/_embedding.py +0 -0
  35. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/api_resources/_event.py +0 -0
  36. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/api_resources/_folder.py +0 -0
  37. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/api_resources/_group.py +0 -0
  38. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/api_resources/_integrated.py +0 -0
  39. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/api_resources/_llm_models.py +0 -0
  40. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/api_resources/_mcp.py +0 -0
  41. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/api_resources/_message.py +0 -0
  42. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/api_resources/_message_assessment.py +0 -0
  43. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/api_resources/_message_execution.py +0 -0
  44. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/api_resources/_message_log.py +0 -0
  45. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/api_resources/_message_tool.py +0 -0
  46. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/api_resources/_module.py +0 -0
  47. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/api_resources/_scheduled_task.py +0 -0
  48. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/api_resources/_search.py +0 -0
  49. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/api_resources/_search_string.py +0 -0
  50. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/api_resources/_short_term_memory.py +0 -0
  51. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/api_resources/_space.py +0 -0
  52. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/api_resources/_user.py +0 -0
  53. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/api_resources/_web_search.py +0 -0
  54. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/cli/__init__.py +0 -0
  55. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/cli/__main__.py +0 -0
  56. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/cli/commands/__init__.py +0 -0
  57. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/cli/commands/_citation_manifest.py +0 -0
  58. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/cli/commands/agentic_table.py +0 -0
  59. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/cli/commands/browser.py +0 -0
  60. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/cli/commands/cite_file.py +0 -0
  61. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/cli/commands/elicitation.py +0 -0
  62. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/cli/commands/files.py +0 -0
  63. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/cli/commands/folders.py +0 -0
  64. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/cli/commands/mcp.py +0 -0
  65. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/cli/commands/navigation.py +0 -0
  66. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/cli/commands/read.py +0 -0
  67. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/cli/commands/scheduled_tasks.py +0 -0
  68. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/cli/commands/search.py +0 -0
  69. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/cli/commands/subagent.py +0 -0
  70. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/cli/commands/web_search_config.py +0 -0
  71. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/cli/config.py +0 -0
  72. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/cli/formatting.py +0 -0
  73. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/cli/identity.py +0 -0
  74. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/cli/metadata_filter.py +0 -0
  75. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/cli/shell.py +0 -0
  76. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/cli/skills/unique-cli-agentic-table/SKILL.md +0 -0
  77. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/cli/skills/unique-cli-elicitation/SKILL.md +0 -0
  78. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/cli/skills/unique-cli-mcp/SKILL.md +0 -0
  79. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/cli/skills/unique-cli-scheduled-tasks/SKILL.md +0 -0
  80. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/cli/skills/unique-cli-search/SKILL.md +0 -0
  81. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/cli/skills/unique-cli-subagent/SKILL.md +0 -0
  82. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/cli/skills/unique-cli-uploaded-search/SKILL.md +0 -0
  83. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/cli/skills/unique-cli-web-search/SKILL.md +0 -0
  84. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/cli/state.py +0 -0
  85. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/utils/analytics_order_run.py +0 -0
  86. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/utils/benchmarking_run.py +0 -0
  87. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/utils/chat_history.py +0 -0
  88. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/utils/chat_in_space.py +0 -0
  89. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/utils/file_io.py +0 -0
  90. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/unique_sdk/utils/sources.py +0 -0
  91. {unique_sdk-2026.32.0.dev7 → unique_sdk-2026.32.0.dev9}/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.dev7
3
+ Version: 2026.32.0.dev9
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>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "unique_sdk"
3
- version = "2026.32.0.dev7"
3
+ version = "2026.32.0.dev9"
4
4
  description = ""
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }
@@ -77,9 +77,6 @@ from unique_sdk.api_resources._message import Message as Message
77
77
  from unique_sdk.api_resources._integrated import Integrated as Integrated
78
78
  from unique_sdk.api_resources._search import Search as Search
79
79
  from unique_sdk.api_resources._content import Content as Content
80
- from unique_sdk.api_resources._dynamic_frontend import (
81
- DynamicFrontend as DynamicFrontend,
82
- )
83
80
  from unique_sdk.api_resources._search_string import SearchString as SearchString
84
81
  from unique_sdk.api_resources._short_term_memory import (
85
82
  ShortTermMemory as ShortTermMemory,
@@ -31,11 +31,6 @@ from unique_sdk.cli.commands.cite_file import cmd_cite_file
31
31
  from unique_sdk.cli.commands.cite_file import (
32
32
  is_error_output as _is_cite_error_output,
33
33
  )
34
- from unique_sdk.cli.commands.dynamic_frontend import (
35
- cmd_dynamic_frontend_delete,
36
- cmd_dynamic_frontend_deploy,
37
- cmd_dynamic_frontend_list,
38
- )
39
34
  from unique_sdk.cli.commands.elicitation import (
40
35
  DEFAULT_WAIT_TIMEOUT_SECONDS,
41
36
  cmd_elicit_ask,
@@ -97,8 +92,6 @@ from unique_sdk.cli.identity import TurnIdentityError, resolve_message_id
97
92
  from unique_sdk.cli.shell import UniqueShell
98
93
  from unique_sdk.cli.state import ShellState
99
94
 
100
- _DYNAMIC_FRONTEND_ERROR_PREFIX = "dynamic-frontend "
101
-
102
95
 
103
96
  def _resolve_cli_message_id(
104
97
  ctx: click.Context,
@@ -174,7 +167,6 @@ Examples:
174
167
  unique-cli subagent Legal "Review" Invoke a connected space/subagent
175
168
  unique-cli web-search search "x" Search the web via the public API
176
169
  unique-cli web-search crawl URL Crawl a URL via the public API
177
- unique-cli dynamic-frontend list List manageable Dynamic Frontend spaces
178
170
  unique-cli browser get-dom Read the user's live Chrome tab (a11y tree)
179
171
  unique-cli agentic-table get-sheet mt_abc123 Show a magic-table sheet summary
180
172
  """
@@ -582,101 +574,6 @@ def read_cmd(
582
574
  emit(output, is_error=_is_read_error_output)
583
575
 
584
576
 
585
- @main.group(name="dynamic-frontend")
586
- def dynamic_frontend() -> None:
587
- """Deploy, list, and delete Dynamic Frontend spaces."""
588
-
589
-
590
- @dynamic_frontend.command(name="deploy")
591
- @click.option(
592
- "--file",
593
- "file_path",
594
- default=None,
595
- type=click.Path(exists=True),
596
- help="Path to an upload-ready Dynamic Frontend ZIP bundle.",
597
- )
598
- @click.option(
599
- "--content-id",
600
- default=None,
601
- help="Existing Knowledge Base content id for the ZIP bundle.",
602
- )
603
- @click.option(
604
- "--name",
605
- default=None,
606
- help="Space display name. Required when creating; optional rename when updating.",
607
- )
608
- @click.option(
609
- "--space-id", default=None, help="Existing Dynamic Frontend space id to update."
610
- )
611
- @click.option(
612
- "--json", "output_json", is_flag=True, default=False, help="Print raw JSON."
613
- )
614
- @click.pass_context
615
- def dynamic_frontend_deploy(
616
- ctx: click.Context,
617
- file_path: str | None,
618
- content_id: str | None,
619
- name: str | None,
620
- space_id: str | None,
621
- output_json: bool,
622
- ) -> None:
623
- """Create or update a Dynamic Frontend space.
624
-
625
- \b
626
- Examples:
627
- unique-cli dynamic-frontend deploy --file ./app.zip --name "Revenue Dashboard"
628
- unique-cli dynamic-frontend deploy --content-id content_123 --name "Revenue Dashboard"
629
- unique-cli dynamic-frontend deploy --space-id assistant_123 --file ./app.zip
630
- """
631
- output = cmd_dynamic_frontend_deploy(
632
- LazyState.get(ctx),
633
- file=file_path,
634
- content_id=content_id,
635
- name=name,
636
- space_id=space_id,
637
- output_json=output_json,
638
- )
639
- click.echo(output)
640
- if output.startswith(_DYNAMIC_FRONTEND_ERROR_PREFIX):
641
- ctx.exit(1)
642
-
643
-
644
- @dynamic_frontend.command(name="list")
645
- @click.option(
646
- "--json", "output_json", is_flag=True, default=False, help="Print raw JSON."
647
- )
648
- @click.pass_context
649
- def dynamic_frontend_list(ctx: click.Context, output_json: bool) -> None:
650
- """List Dynamic Frontend spaces the current user can manage."""
651
- output = cmd_dynamic_frontend_list(LazyState.get(ctx), output_json=output_json)
652
- click.echo(output)
653
- if output.startswith(_DYNAMIC_FRONTEND_ERROR_PREFIX):
654
- ctx.exit(1)
655
-
656
-
657
- @dynamic_frontend.command(name="delete")
658
- @click.argument("space_id")
659
- @click.option(
660
- "--json", "output_json", is_flag=True, default=False, help="Print raw JSON."
661
- )
662
- @click.pass_context
663
- def dynamic_frontend_delete(
664
- ctx: click.Context, space_id: str, output_json: bool
665
- ) -> None:
666
- """Delete a deployed Dynamic Frontend space by its space id.
667
-
668
- \b
669
- Example:
670
- unique-cli dynamic-frontend delete assistant_123
671
- """
672
- output = cmd_dynamic_frontend_delete(
673
- LazyState.get(ctx), space_id, output_json=output_json
674
- )
675
- click.echo(output)
676
- if output.startswith(_DYNAMIC_FRONTEND_ERROR_PREFIX):
677
- ctx.exit(1)
678
-
679
-
680
577
  @main.command()
681
578
  @click.argument("name_or_id")
682
579
  @click.pass_context
@@ -67,6 +67,29 @@ def _next_source_number(entries: list[dict[str, Any]]) -> int:
67
67
  return max(source_numbers, default=0) + 1
68
68
 
69
69
 
70
+ def _fetch_error_message(content: Any) -> str | None:
71
+ """Extract the failure message when ``content`` is a crawl-error payload.
72
+
73
+ The web-search backend reports per-URL fetch failures in-band: proxy
74
+ crawlers return ``"URL: <url>\\n\\nError: <message>"``, Tavily/Jina a
75
+ bare ``"Error: <message>"``. Returns the message for error payloads and
76
+ ``None`` for real page content.
77
+
78
+ Any leading ``URL: …`` line is skipped without matching it against the
79
+ result's own URL — redirects or normalization can make the reported URL
80
+ differ from the requested one.
81
+ """
82
+ if not isinstance(content, str):
83
+ return None
84
+ stripped = content.strip()
85
+ first_line, _, rest = stripped.partition("\n")
86
+ if first_line.startswith("URL:"):
87
+ stripped = rest.lstrip()
88
+ if stripped.startswith("Error:"):
89
+ return stripped[len("Error:") :].strip() or "unknown crawl error"
90
+ return None
91
+
92
+
70
93
  def _annotate_web_results_for_citations(
71
94
  payload: dict[str, Any],
72
95
  *,
@@ -78,6 +101,14 @@ def _annotate_web_results_for_citations(
78
101
  ``sourceNumber`` across consecutive ``search`` / ``crawl`` calls in
79
102
  the same turn, so the crawled-content row carries the same citation
80
103
  marker the search-snippet row already advertised.
104
+
105
+ Crawl-error payloads (``_fetch_error_message``) are kept out of the
106
+ manifest's ``content`` field and recorded under ``error`` instead: the
107
+ platform grounds the hallucination judge on manifest ``content``, so an
108
+ in-band fetch error stored as content turns a correctly cited,
109
+ snippet-verifiable source into a false "high hallucination" verdict
110
+ (UN-23356). The on-screen result still shows the error text so the
111
+ agent can react to the failed fetch.
81
112
  """
82
113
  refs_log_path = refs_log_path or (Path.cwd() / _WEB_REFS_LOG_RELATIVE_PATH)
83
114
  with _locked_turn_refs_manifest(
@@ -109,13 +140,19 @@ def _annotate_web_results_for_citations(
109
140
 
110
141
  result["sourceNumber"] = source_number
111
142
  result["citation"] = f"websource{source_number}"
143
+ content = result.get("content")
144
+ error = result.get("error")
145
+ fetch_error = _fetch_error_message(content)
146
+ if fetch_error is not None:
147
+ content = None
148
+ error = error or fetch_error
112
149
  manifest_entry = {
113
150
  "sourceNumber": source_number,
114
151
  "url": url,
115
152
  "title": result.get("title"),
116
153
  "snippet": result.get("snippet"),
117
- "content": result.get("content"),
118
- "error": result.get("error"),
154
+ "content": content,
155
+ "error": error,
119
156
  }
120
157
  _append_turn_refs_manifest_entry(refs_log_path, manifest_entry)
121
158
  annotated_results.append(result)
@@ -235,24 +235,29 @@ If you obtained the content some other way (you `download`ed the raw bytes and p
235
235
  CSV, `.txt`, HTML, images) have no pages — always omit `--pages`** and cite the
236
236
  whole file.
237
237
 
238
- **Choosing `--read-method`** (declare the *representation* of the source you actually read):
238
+ **Choosing `--read-method`** (declare the *representation* you actually read the
239
+ cited value from). Pick it by the **modality you read** — NOT by how you *located*
240
+ the page. Locating a page with `unique-cli read` does **not** force `indexed`: if you
241
+ then rendered that page and read a chart/figure with vision, the method is `vision`.
239
242
 
240
243
  - `text` → you used **extracted text** (`pdftotext`, PyMuPDF / `fitz` `page.get_text()`, MarkItDown, or any text extraction).
241
- - `vision` → you read a **rendered image** of the page/slide (e.g. `get_pixmap()`) with your vision capability.
242
- - `indexed` → you relied on **`unique-cli read`** output (the platform's indexed chunks).
244
+ - `vision` → you read a **rendered image** of the page/slide (e.g. `get_pixmap()`) with your vision capability — including when you located the page via `unique-cli read` but then rendered it to read a chart, figure, table, or scanned page.
245
+ - `indexed` → you used the **text returned by `unique-cli read`** (the platform's indexed chunks) as your answer source.
243
246
 
244
247
  | Value | When to use |
245
248
  |-------|-------------|
246
- | `text` | You read the page/document as extracted text and used that text. |
247
- | `vision` | You rendered the page to an image and read it with your vision capability. |
248
- | `indexed` | You read the content via `unique-cli read` (indexed chunks). |
249
-
250
- **Verify page numbers before citing — unless you already have them from `unique-cli read`.**
251
- If you cited straight from `unique-cli read` output (`--read-method indexed`), its
252
- `[p.N]` / `[p.N-M]` markers are already physical positions — skip the checks below
253
- and cite them directly. Otherwise — you read the raw bytes yourself
254
- (`--read-method text`/`vision`) — pick the row matching the file you read and
255
- verify the cited content really is where you claim before calling `cite`:
249
+ | `text` | You extracted the page/document text yourself and used that text. |
250
+ | `vision` | You read a rendered image of the page with your vision capability — even if you located the page via `unique-cli read`. |
251
+ | `indexed` | You used the text returned by `unique-cli read` (indexed chunks) as your source. |
252
+
253
+ **Verify page numbers before citing — unless you located them via `unique-cli read`.**
254
+ This is about the *page number* only, and is **independent of `--read-method`**. If you
255
+ located the page with `unique-cli read`, its `[p.N]` / `[p.N-M]` markers are already
256
+ physical positions — trust them and skip the checks below, even if you then rendered the
257
+ page and read it with vision (in that case still cite `--read-method vision`). Only when
258
+ you obtained the page some other way — you `download`ed the raw bytes and parsed them
259
+ yourself, or `read` returned no `[p.N]` markers — pick the row matching the file you read
260
+ and verify the cited content really is where you claim before calling `cite`:
256
261
 
257
262
  - **PDF** — `pdfinfo file.pdf | grep Pages` for the total physical page count, then
258
263
  for **each** page run `pdftotext -f N -l N file.pdf -` and confirm the content is
@@ -265,11 +270,13 @@ verify the cited content really is where you claim before calling `cite`:
265
270
  - **Non-paginated (XLSX/CSV/TXT/HTML/images)** — there are no pages. Do **NOT**
266
271
  pass `--pages`; cite the whole file and verify the content exists in it.
267
272
 
268
- Then determine `--read-method`: report the representation you actually read. In a
269
- fallback chain (e.g. text extraction returned nothing → render + read visually),
270
- report `text` if you used extracted text or `vision` if you read a rendered image.
271
- Only after verifying, call `unique-cli cite` with the verified page numbers (if any)
272
- and `--read-method`.
273
+ Then determine `--read-method` by the **modality you actually read** — independent of
274
+ how you located the page. In a fallback chain (e.g. text extraction returned nothing →
275
+ render + read visually), report `text` if you used extracted text or `vision` if you read
276
+ a rendered image. If you located the page via `unique-cli read` but read the cited value
277
+ off a rendered image (a chart, figure, table, or scanned page), report `vision`, not
278
+ `indexed`. Only after confirming the page numbers, call `unique-cli cite` with the
279
+ verified page numbers (if any) and `--read-method`.
273
280
 
274
281
  - **One method per `cite` call.** If different pages were read with different methods, issue separate `cite` calls — one per method.
275
282
  - Numbers are **per-turn only**; do not reuse from prior turns.
@@ -1,101 +0,0 @@
1
- from typing import Literal, cast
2
-
3
- from typing_extensions import NotRequired, Unpack
4
-
5
- from unique_sdk._api_resource import APIResource
6
- from unique_sdk._request_options import RequestOptions
7
- from unique_sdk._util import classproperty
8
-
9
-
10
- class DynamicFrontend(APIResource["DynamicFrontend"]):
11
- @classproperty
12
- def OBJECT_NAME(cls) -> Literal["dynamic-frontend"]:
13
- return "dynamic-frontend"
14
-
15
- id: str
16
- spaceId: str
17
- name: str
18
- contentId: str
19
- url: str | None
20
- configUrl: str | None
21
- status: dict[str, object] | None
22
-
23
- class CreateParams(RequestOptions):
24
- name: str
25
- contentId: str
26
-
27
- class UpdateParams(RequestOptions):
28
- contentId: str
29
- name: NotRequired[str | None]
30
-
31
- @classmethod
32
- def create(
33
- cls,
34
- user_id: str,
35
- company_id: str,
36
- **params: Unpack["DynamicFrontend.CreateParams"],
37
- ) -> "DynamicFrontend":
38
- return cast(
39
- DynamicFrontend,
40
- cls._static_request(
41
- "post",
42
- "/dynamic-frontend",
43
- user_id,
44
- company_id,
45
- params=params,
46
- ),
47
- )
48
-
49
- @classmethod
50
- def modify(
51
- cls,
52
- space_id: str,
53
- user_id: str,
54
- company_id: str,
55
- **params: Unpack["DynamicFrontend.UpdateParams"],
56
- ) -> "DynamicFrontend":
57
- return cast(
58
- DynamicFrontend,
59
- cls._static_request(
60
- "patch",
61
- f"/dynamic-frontend/{space_id}",
62
- user_id,
63
- company_id,
64
- params=params,
65
- ),
66
- )
67
-
68
- @classmethod
69
- def delete(
70
- cls,
71
- space_id: str,
72
- user_id: str,
73
- company_id: str,
74
- ) -> "DynamicFrontend":
75
- """Delete a deployed Dynamic Frontend space."""
76
- return cast(
77
- DynamicFrontend,
78
- cls._static_request(
79
- "delete",
80
- f"/dynamic-frontend/{space_id}",
81
- user_id,
82
- company_id,
83
- ),
84
- )
85
-
86
- @classmethod
87
- def list(
88
- cls,
89
- user_id: str,
90
- company_id: str,
91
- ) -> list["DynamicFrontend"]:
92
- response = cls._static_request(
93
- "get",
94
- "/dynamic-frontend",
95
- user_id,
96
- company_id,
97
- )
98
- if isinstance(response, list):
99
- return cast(list[DynamicFrontend], response)
100
- data = response.get("data", []) if isinstance(response, dict) else []
101
- return cast(list[DynamicFrontend], data)
@@ -1,155 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import json
4
- from pathlib import Path
5
-
6
- import unique_sdk
7
- from unique_sdk.cli.state import ShellState
8
- from unique_sdk.utils.file_io import upload_file
9
-
10
-
11
- def _upload_bundle(state: ShellState, file_path: str) -> str:
12
- path = Path(file_path).expanduser().resolve()
13
- if not path.is_file():
14
- raise ValueError(f"local file not found: {file_path}")
15
-
16
- if not state.scope_id:
17
- raise ValueError("cannot upload bundle to root. cd into a folder first.")
18
-
19
- result = upload_file(
20
- userId=state.config.user_id,
21
- companyId=state.config.company_id,
22
- path_to_file=str(path),
23
- displayed_filename=path.name,
24
- mime_type="application/zip",
25
- scope_or_unique_path=state.scope_id,
26
- )
27
- content_id = getattr(result, "id", None)
28
- if not isinstance(content_id, str) or not content_id:
29
- raise ValueError("upload did not return a content id")
30
- return content_id
31
-
32
-
33
- def _format_space(space: object) -> str:
34
- space_id = getattr(space, "spaceId", None) or getattr(space, "id", "")
35
- name = getattr(space, "name", "")
36
- content_id = getattr(space, "contentId", "")
37
- status = getattr(space, "status", None)
38
- phase = ""
39
- url = str(getattr(space, "url", "") or "")
40
- config_url = str(getattr(space, "configUrl", "") or "")
41
- if isinstance(status, dict):
42
- phase = str(status.get("phase") or "")
43
- url = url or str(status.get("url") or "")
44
- return "\t".join(
45
- part
46
- for part in [str(space_id), str(name), str(content_id), phase, url, config_url]
47
- if part
48
- )
49
-
50
-
51
- def cmd_dynamic_frontend_deploy(
52
- state: ShellState,
53
- *,
54
- file: str | None = None,
55
- content_id: str | None = None,
56
- name: str | None = None,
57
- space_id: str | None = None,
58
- output_json: bool = False,
59
- ) -> str:
60
- try:
61
- if not file and not content_id:
62
- return "dynamic-frontend deploy: provide either --file or --content-id."
63
- if file and content_id:
64
- return "dynamic-frontend deploy: --file and --content-id are mutually exclusive."
65
- if not space_id and not name:
66
- return "dynamic-frontend deploy: provide --name when creating a Dynamic Frontend space."
67
-
68
- resolved_content_id = _upload_bundle(state, file) if file else content_id
69
- if not resolved_content_id:
70
- return "dynamic-frontend deploy: expected a content id after resolving deploy input."
71
-
72
- if space_id:
73
- update_params: unique_sdk.DynamicFrontend.UpdateParams = {
74
- "contentId": resolved_content_id,
75
- }
76
- if name is not None:
77
- update_params["name"] = name
78
- space = unique_sdk.DynamicFrontend.modify(
79
- space_id,
80
- user_id=state.config.user_id,
81
- company_id=state.config.company_id,
82
- **update_params,
83
- )
84
- action = "Updated"
85
- else:
86
- space = unique_sdk.DynamicFrontend.create(
87
- user_id=state.config.user_id,
88
- company_id=state.config.company_id,
89
- name=name or "",
90
- contentId=resolved_content_id,
91
- )
92
- action = "Created"
93
-
94
- if output_json:
95
- return json.dumps(dict(space), indent=2, default=str)
96
-
97
- space_id_value = getattr(space, "spaceId", None) or getattr(space, "id", "")
98
- name_value = getattr(space, "name", name or "")
99
- url_value = getattr(space, "url", None)
100
- config_url_value = getattr(space, "configUrl", None)
101
- url_line = (
102
- f"\nURL: {url_value}" if isinstance(url_value, str) and url_value else ""
103
- )
104
- config_url_line = (
105
- f"\nConfig URL: {config_url_value}"
106
- if isinstance(config_url_value, str) and config_url_value
107
- else ""
108
- )
109
- return (
110
- f'{action} Dynamic Frontend space "{name_value}" ({space_id_value})\n'
111
- f"Content: {resolved_content_id}"
112
- f"{url_line}"
113
- f"{config_url_line}"
114
- )
115
- except (ValueError, unique_sdk.APIError, OSError) as e:
116
- return f"dynamic-frontend deploy: {e}"
117
-
118
-
119
- def cmd_dynamic_frontend_delete(
120
- state: ShellState,
121
- space_id: str,
122
- *,
123
- output_json: bool = False,
124
- ) -> str:
125
- try:
126
- if not space_id:
127
- return "dynamic-frontend delete: provide a space id."
128
- result = unique_sdk.DynamicFrontend.delete(
129
- space_id,
130
- user_id=state.config.user_id,
131
- company_id=state.config.company_id,
132
- )
133
- if output_json:
134
- return json.dumps(dict(result), indent=2, default=str)
135
- deleted_id = (
136
- getattr(result, "spaceId", None) or getattr(result, "id", None) or space_id
137
- )
138
- return f"Deleted Dynamic Frontend space {deleted_id}"
139
- except (ValueError, unique_sdk.APIError) as e:
140
- return f"dynamic-frontend delete: {e}"
141
-
142
-
143
- def cmd_dynamic_frontend_list(state: ShellState, *, output_json: bool = False) -> str:
144
- try:
145
- spaces = unique_sdk.DynamicFrontend.list(
146
- user_id=state.config.user_id,
147
- company_id=state.config.company_id,
148
- )
149
- if output_json:
150
- return json.dumps(spaces, indent=2, default=str)
151
- if not spaces:
152
- return "No manageable Dynamic Frontend spaces found."
153
- return "\n".join(_format_space(space) for space in spaces)
154
- except unique_sdk.APIError as e:
155
- return f"dynamic-frontend list: {e}"
@@ -1,151 +0,0 @@
1
- ---
2
- name: unique-cli-dynamic-frontend
3
- description: >-
4
- Create, update, list, and delete Unique Dynamic Frontend Spaces using the
5
- `unique-cli dynamic-frontend` command. Use when deploying a generated
6
- Dynamic Frontend ZIP, updating an existing Dynamic Frontend Space bundle,
7
- listing manageable Dynamic Frontend Spaces, deleting a deployed Dynamic
8
- Frontend Space, or when the user mentions Dynamic Frontend Space deployment
9
- through the CLI.
10
- ---
11
-
12
- # Unique CLI -- Dynamic Frontend Spaces
13
-
14
- Use `unique-cli dynamic-frontend` to create, update, list, and delete Dynamic
15
- Frontend Spaces from upload-ready ZIP bundles or existing Knowledge Base
16
- content IDs.
17
-
18
- The CLI is installed via `pip install unique-sdk` and uses the same
19
- `UNIQUE_USER_ID`, `UNIQUE_COMPANY_ID`, `UNIQUE_API_KEY`, `UNIQUE_APP_ID`, and
20
- `UNIQUE_API_BASE` environment variables as the rest of `unique-cli`.
21
-
22
- ## Create a New Space
23
-
24
- To upload a ZIP and create a new Dynamic Frontend Space:
25
-
26
- ```bash
27
- unique-cli cd /Apps
28
- unique-cli dynamic-frontend deploy \
29
- --file ./revenue-dashboard.zip \
30
- --name "Revenue Dashboard"
31
- ```
32
-
33
- To create from an already uploaded KB content ID:
34
-
35
- ```bash
36
- unique-cli dynamic-frontend deploy \
37
- --content-id cont_abc123 \
38
- --name "Revenue Dashboard"
39
- ```
40
-
41
- The command prints the created `spaceId`, `contentId`, and **two** URLs when the
42
- API returns them — the user-facing view `URL` (jump into the Space in the chat
43
- app) and the `Config URL` (configure and share the Space in the admin app):
44
-
45
- ```text
46
- Created Dynamic Frontend space "Revenue Dashboard" (space_abc123)
47
- Content: cont_abc123
48
- URL: https://next.qa.unique.app/chat/space/space_abc123
49
- Config URL: https://next.qa.unique.app/admin/dynamic-frontend-space/space_abc123
50
- ```
51
-
52
- If the installed SDK/CLI version does not print one of these URLs yet, construct
53
- it from the known frontend base URLs and `spaceId`:
54
-
55
- ```text
56
- # View URL (chat app)
57
- <chat-frontend-url>/space/<spaceId>
58
- # Config/share URL (admin app)
59
- <admin-frontend-url>/dynamic-frontend-space/<spaceId>
60
- ```
61
-
62
- On QA, those are:
63
-
64
- ```text
65
- https://next.qa.unique.app/chat/space/<spaceId>
66
- https://next.qa.unique.app/admin/dynamic-frontend-space/<spaceId>
67
- ```
68
-
69
- ## Update an Existing Space
70
-
71
- Use `--space-id` to update an existing Dynamic Frontend Space instead of
72
- creating a new one.
73
-
74
- Upload a new ZIP and point the existing Space at it:
75
-
76
- ```bash
77
- unique-cli cd /Apps
78
- unique-cli dynamic-frontend deploy \
79
- --space-id space_abc123 \
80
- --file ./revenue-dashboard-1.0.1.zip
81
- ```
82
-
83
- Update from an already uploaded KB content ID:
84
-
85
- ```bash
86
- unique-cli dynamic-frontend deploy \
87
- --space-id space_abc123 \
88
- --content-id cont_newbundle123
89
- ```
90
-
91
- Rename while updating the bundle:
92
-
93
- ```bash
94
- unique-cli dynamic-frontend deploy \
95
- --space-id space_abc123 \
96
- --file ./revenue-dashboard-1.0.1.zip \
97
- --name "Revenue Dashboard"
98
- ```
99
-
100
- The update command prints the same `spaceId`, `contentId`, view `URL`, and
101
- `Config URL` fields as create.
102
-
103
- ## Delete a Space
104
-
105
- Remove a deployed Dynamic Frontend Space by its space id. This deletes the
106
- backing BYOC app and the owning space (and its access grants):
107
-
108
- ```bash
109
- unique-cli dynamic-frontend delete space_abc123
110
- ```
111
-
112
- The command prints a confirmation:
113
-
114
- ```text
115
- Deleted Dynamic Frontend space space_abc123
116
- ```
117
-
118
- Deletion is permanent and requires manage access on the space (or a
119
- company-wide space-admin role). Always confirm the correct `spaceId` with the
120
- user before deleting — there is no undo.
121
-
122
- ## List Spaces
123
-
124
- List Dynamic Frontend Spaces the current user can manage:
125
-
126
- ```bash
127
- unique-cli dynamic-frontend list
128
- ```
129
-
130
- Use JSON output for scripts:
131
-
132
- ```bash
133
- unique-cli dynamic-frontend list --json
134
- unique-cli dynamic-frontend deploy --space-id space_abc123 --file ./app.zip --json
135
- unique-cli dynamic-frontend delete space_abc123 --json
136
- ```
137
-
138
- ## Rules
139
-
140
- - For `--file`, first `unique-cli cd` into the KB folder where the ZIP should be
141
- uploaded; uploading to root is rejected.
142
- - Use `--space-id` for updates. Without `--space-id`, `deploy` creates a new
143
- Dynamic Frontend Space and requires `--name`.
144
- - `--file` and `--content-id` are mutually exclusive.
145
- - `delete` is permanent and has no undo — confirm the `spaceId` with the user
146
- first, and never guess which space to delete.
147
- - After create or update, return the CLI output to the user, especially both the
148
- view `URL` (jump to the Space) and the `Config URL` (configure/share the Space).
149
- - Never report the BYOC iframe runtime URL
150
- (`https://byoc.../serve/df-assistant-...`) as the Space URL; it is an
151
- internal iframe launch URL, not the navigation link users need.