unique-sdk 2026.24.0.dev0__tar.gz → 2026.24.0.dev1__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.24.0.dev0 → unique_sdk-2026.24.0.dev1}/PKG-INFO +1 -1
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/pyproject.toml +1 -1
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/cli/cli.py +86 -0
- unique_sdk-2026.24.0.dev1/unique_sdk/cli/commands/subagent.py +298 -0
- unique_sdk-2026.24.0.dev1/unique_sdk/cli/skills/unique-cli-subagent/SKILL.md +56 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/README.md +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/__init__.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/_api_requestor.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/_api_resource.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/_api_version.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/_error.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/_http_client.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/_list_object.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/_object_classes.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/_request_options.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/_unique_object.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/_unique_ql.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/_unique_response.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/_util.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/_version.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/_webhook.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/api_resources/__init__.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/api_resources/_acronyms.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/api_resources/_agentic_table.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/api_resources/_analytics_order.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/api_resources/_benchmarking.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/api_resources/_briefing.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/api_resources/_chat_completion.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/api_resources/_content.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/api_resources/_elicitation.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/api_resources/_embedding.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/api_resources/_event.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/api_resources/_folder.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/api_resources/_group.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/api_resources/_integrated.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/api_resources/_llm_models.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/api_resources/_mcp.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/api_resources/_message.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/api_resources/_message_assessment.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/api_resources/_message_execution.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/api_resources/_message_log.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/api_resources/_message_tool.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/api_resources/_module.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/api_resources/_scheduled_task.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/api_resources/_search.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/api_resources/_search_string.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/api_resources/_short_term_memory.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/api_resources/_space.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/api_resources/_user.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/api_resources/_web_search.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/cli/__init__.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/cli/__main__.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/cli/commands/__init__.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/cli/commands/elicitation.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/cli/commands/files.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/cli/commands/folders.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/cli/commands/mcp.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/cli/commands/navigation.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/cli/commands/scheduled_tasks.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/cli/commands/search.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/cli/commands/web_search.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/cli/commands/web_search_config.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/cli/config.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/cli/formatting.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/cli/shell.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/cli/skills/unique-cli-elicitation/SKILL.md +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/cli/skills/unique-cli-file-management/SKILL.md +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/cli/skills/unique-cli-mcp/SKILL.md +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/cli/skills/unique-cli-scheduled-tasks/SKILL.md +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/cli/skills/unique-cli-search/SKILL.md +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/cli/skills/unique-cli-web-search/SKILL.md +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/cli/state.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/utils/analytics_order_run.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/utils/benchmarking_run.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/utils/chat_history.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/utils/chat_in_space.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/utils/file_io.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/utils/sources.py +0 -0
- {unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/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.24.0.
|
|
3
|
+
Version: 2026.24.0.dev1
|
|
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>
|
|
@@ -27,6 +27,10 @@ from unique_sdk.cli.commands.scheduled_tasks import (
|
|
|
27
27
|
cmd_schedule_update,
|
|
28
28
|
)
|
|
29
29
|
from unique_sdk.cli.commands.search import cmd_search
|
|
30
|
+
from unique_sdk.cli.commands.subagent import cmd_subagent
|
|
31
|
+
from unique_sdk.cli.commands.subagent import (
|
|
32
|
+
is_error_output as _is_subagent_error_output,
|
|
33
|
+
)
|
|
30
34
|
from unique_sdk.cli.commands.web_search import (
|
|
31
35
|
cmd_web_crawl,
|
|
32
36
|
cmd_web_search,
|
|
@@ -82,6 +86,7 @@ Examples:
|
|
|
82
86
|
unique-cli upload ./file.pdf Upload to current folder
|
|
83
87
|
unique-cli download cont_abc123 Download by content ID
|
|
84
88
|
unique-cli elicit ask "Which?" Ask the user a question synchronously
|
|
89
|
+
unique-cli subagent Legal "Review" Invoke a connected space/subagent
|
|
85
90
|
unique-cli web-search search "x" Search the web via the public API
|
|
86
91
|
unique-cli web-search crawl URL Crawl a URL via the public API
|
|
87
92
|
"""
|
|
@@ -465,6 +470,87 @@ def mcp(
|
|
|
465
470
|
)
|
|
466
471
|
|
|
467
472
|
|
|
473
|
+
@main.command()
|
|
474
|
+
@click.argument("tool_name")
|
|
475
|
+
@click.argument("message")
|
|
476
|
+
@click.option(
|
|
477
|
+
"--config",
|
|
478
|
+
"config_path",
|
|
479
|
+
default=None,
|
|
480
|
+
type=click.Path(exists=True),
|
|
481
|
+
help="Path to .unique-subagents.json. Defaults to $UNIQUE_SUBAGENTS_CONFIG or cwd.",
|
|
482
|
+
)
|
|
483
|
+
@click.option(
|
|
484
|
+
"--chat-id",
|
|
485
|
+
"parent_chat_id",
|
|
486
|
+
default=None,
|
|
487
|
+
envvar="UNIQUE_CHAT_ID",
|
|
488
|
+
help="Parent chat ID for message correlation.",
|
|
489
|
+
)
|
|
490
|
+
@click.option(
|
|
491
|
+
"--message-id",
|
|
492
|
+
"parent_message_id",
|
|
493
|
+
default=None,
|
|
494
|
+
envvar="UNIQUE_MESSAGE_ID",
|
|
495
|
+
help="Parent message ID for message correlation.",
|
|
496
|
+
)
|
|
497
|
+
@click.option(
|
|
498
|
+
"--assistant-id",
|
|
499
|
+
"parent_assistant_id",
|
|
500
|
+
default=None,
|
|
501
|
+
envvar="UNIQUE_ASSISTANT_ID",
|
|
502
|
+
help="Parent assistant ID for message correlation.",
|
|
503
|
+
)
|
|
504
|
+
@click.option(
|
|
505
|
+
"--reset-chat",
|
|
506
|
+
is_flag=True,
|
|
507
|
+
help="Ignore any saved reusable chat for this subagent call.",
|
|
508
|
+
)
|
|
509
|
+
@click.option(
|
|
510
|
+
"--json",
|
|
511
|
+
"output_json",
|
|
512
|
+
is_flag=True,
|
|
513
|
+
help="Print the raw response JSON instead of a human-readable response.",
|
|
514
|
+
)
|
|
515
|
+
@click.pass_context
|
|
516
|
+
def subagent(
|
|
517
|
+
ctx: click.Context,
|
|
518
|
+
tool_name: str,
|
|
519
|
+
message: str,
|
|
520
|
+
config_path: str | None,
|
|
521
|
+
parent_chat_id: str | None,
|
|
522
|
+
parent_message_id: str | None,
|
|
523
|
+
parent_assistant_id: str | None,
|
|
524
|
+
reset_chat: bool,
|
|
525
|
+
output_json: bool,
|
|
526
|
+
) -> None:
|
|
527
|
+
"""Invoke a configured connected-space subagent.
|
|
528
|
+
|
|
529
|
+
\b
|
|
530
|
+
TOOL_NAME must match an entry in .unique-subagents.json. The command sends
|
|
531
|
+
MESSAGE to that connected assistant and waits for the assistant response.
|
|
532
|
+
|
|
533
|
+
\b
|
|
534
|
+
Examples:
|
|
535
|
+
unique-cli subagent LegalReview "Review this contract clause"
|
|
536
|
+
unique-cli subagent Finance "Summarize Q4 revenue" --reset-chat
|
|
537
|
+
"""
|
|
538
|
+
output = cmd_subagent(
|
|
539
|
+
LazyState.get(ctx),
|
|
540
|
+
tool_name=tool_name,
|
|
541
|
+
message=message,
|
|
542
|
+
config_path=config_path,
|
|
543
|
+
parent_chat_id=parent_chat_id,
|
|
544
|
+
parent_message_id=parent_message_id,
|
|
545
|
+
parent_assistant_id=parent_assistant_id,
|
|
546
|
+
reset_chat=reset_chat,
|
|
547
|
+
output_json=output_json,
|
|
548
|
+
)
|
|
549
|
+
click.echo(output)
|
|
550
|
+
if _is_subagent_error_output(output):
|
|
551
|
+
ctx.exit(1)
|
|
552
|
+
|
|
553
|
+
|
|
468
554
|
# -- Scheduled Tasks -------------------------------------------------------
|
|
469
555
|
|
|
470
556
|
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
"""Subagent command: invoke configured connected spaces via the Unique platform."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any, Literal, TypedDict, cast
|
|
10
|
+
|
|
11
|
+
from unique_sdk._error import APIError
|
|
12
|
+
from unique_sdk.api_resources._space import Space
|
|
13
|
+
from unique_sdk.cli.state import ShellState
|
|
14
|
+
from unique_sdk.utils.chat_in_space import send_message_and_wait_for_completion
|
|
15
|
+
|
|
16
|
+
CONFIG_FILENAME = ".unique-subagents.json"
|
|
17
|
+
STATE_FILENAME = ".unique-subagent-chats.json"
|
|
18
|
+
ENV_CONFIG_PATH = "UNIQUE_SUBAGENTS_CONFIG"
|
|
19
|
+
SUBAGENT_ERROR_PREFIX = "subagent:"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class SubagentDefinition(TypedDict, total=False):
|
|
23
|
+
name: str
|
|
24
|
+
displayName: str
|
|
25
|
+
configuration: dict[str, Any]
|
|
26
|
+
assistantId: str
|
|
27
|
+
chatId: str | None
|
|
28
|
+
reuseChat: bool
|
|
29
|
+
forcedTools: list[str]
|
|
30
|
+
pollInterval: float
|
|
31
|
+
maxWait: float
|
|
32
|
+
stopCondition: Literal["stoppedStreamingAt", "completedAt"]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def is_error_output(output: str) -> bool:
|
|
36
|
+
"""Return ``True`` when ``output`` is a CLI error message."""
|
|
37
|
+
return output.startswith(SUBAGENT_ERROR_PREFIX)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def resolve_config_path(config_path: str | None = None) -> Path:
|
|
41
|
+
"""Resolve the subagent config file path."""
|
|
42
|
+
if config_path:
|
|
43
|
+
return Path(config_path).expanduser()
|
|
44
|
+
env_path = os.environ.get(ENV_CONFIG_PATH)
|
|
45
|
+
if env_path:
|
|
46
|
+
return Path(env_path).expanduser()
|
|
47
|
+
return Path.cwd() / CONFIG_FILENAME
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _load_config(config_path: Path) -> list[SubagentDefinition]:
|
|
51
|
+
try:
|
|
52
|
+
raw = json.loads(config_path.read_text(encoding="utf-8"))
|
|
53
|
+
except FileNotFoundError as exc:
|
|
54
|
+
raise ValueError(f"config file not found: {config_path}") from exc
|
|
55
|
+
except json.JSONDecodeError as exc:
|
|
56
|
+
raise ValueError(f"config file is not valid JSON: {exc}") from exc
|
|
57
|
+
|
|
58
|
+
if not isinstance(raw, dict):
|
|
59
|
+
raise ValueError("config root must be a JSON object")
|
|
60
|
+
|
|
61
|
+
subagents = raw.get("subagents")
|
|
62
|
+
if not isinstance(subagents, list):
|
|
63
|
+
raise ValueError('config must contain a "subagents" array')
|
|
64
|
+
|
|
65
|
+
validated: list[SubagentDefinition] = []
|
|
66
|
+
for item in subagents:
|
|
67
|
+
if isinstance(item, dict):
|
|
68
|
+
validated.append(cast(SubagentDefinition, cast(object, item)))
|
|
69
|
+
return validated
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _find_subagent(
|
|
73
|
+
subagents: list[SubagentDefinition],
|
|
74
|
+
tool_name: str,
|
|
75
|
+
) -> SubagentDefinition:
|
|
76
|
+
for subagent in subagents:
|
|
77
|
+
if subagent.get("name") == tool_name:
|
|
78
|
+
return subagent
|
|
79
|
+
available = sorted(name for subagent in subagents if (name := subagent.get("name")))
|
|
80
|
+
suffix = f" Available: {', '.join(available)}." if available else ""
|
|
81
|
+
raise ValueError(f"unknown subagent tool {tool_name!r}.{suffix}")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _get_str(
|
|
85
|
+
subagent: SubagentDefinition,
|
|
86
|
+
*keys: str,
|
|
87
|
+
required: bool = False,
|
|
88
|
+
) -> str | None:
|
|
89
|
+
value = _get_value(subagent, *keys)
|
|
90
|
+
if isinstance(value, str) and value.strip():
|
|
91
|
+
return value
|
|
92
|
+
if required:
|
|
93
|
+
key_list = " / ".join(keys)
|
|
94
|
+
raise ValueError(f"subagent {subagent.get('name')!r} is missing {key_list}")
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _get_value(subagent: SubagentDefinition, *keys: str) -> Any:
|
|
99
|
+
subagent_data = cast(dict[str, Any], cast(object, subagent))
|
|
100
|
+
for key in keys:
|
|
101
|
+
if key in subagent_data:
|
|
102
|
+
return subagent_data[key]
|
|
103
|
+
configuration = subagent.get("configuration")
|
|
104
|
+
if isinstance(configuration, dict):
|
|
105
|
+
for key in keys:
|
|
106
|
+
if key in configuration:
|
|
107
|
+
return configuration[key]
|
|
108
|
+
return None
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _get_bool(
|
|
112
|
+
subagent: SubagentDefinition,
|
|
113
|
+
*keys: str,
|
|
114
|
+
default: bool,
|
|
115
|
+
) -> bool:
|
|
116
|
+
value = _get_value(subagent, *keys)
|
|
117
|
+
return value if isinstance(value, bool) else default
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _get_float(
|
|
121
|
+
subagent: SubagentDefinition,
|
|
122
|
+
*keys: str,
|
|
123
|
+
default: float,
|
|
124
|
+
) -> float:
|
|
125
|
+
value = _get_value(subagent, *keys)
|
|
126
|
+
if isinstance(value, int | float) and not isinstance(value, bool):
|
|
127
|
+
return float(value)
|
|
128
|
+
return default
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _get_forced_tools(subagent: SubagentDefinition) -> list[str] | None:
|
|
132
|
+
value = _get_value(subagent, "forcedTools", "forced_tools")
|
|
133
|
+
if isinstance(value, list) and all(isinstance(item, str) for item in value):
|
|
134
|
+
return value or None
|
|
135
|
+
return None
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _get_stop_condition(
|
|
139
|
+
subagent: SubagentDefinition,
|
|
140
|
+
) -> Literal["stoppedStreamingAt", "completedAt"]:
|
|
141
|
+
value = _get_value(subagent, "stopCondition", "stop_condition")
|
|
142
|
+
if value in ("stoppedStreamingAt", "completedAt"):
|
|
143
|
+
return value
|
|
144
|
+
return "completedAt"
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _load_chat_state(state_path: Path) -> dict[str, str]:
|
|
148
|
+
if not state_path.is_file():
|
|
149
|
+
return {}
|
|
150
|
+
try:
|
|
151
|
+
raw = json.loads(state_path.read_text(encoding="utf-8"))
|
|
152
|
+
except (OSError, json.JSONDecodeError):
|
|
153
|
+
return {}
|
|
154
|
+
if not isinstance(raw, dict):
|
|
155
|
+
return {}
|
|
156
|
+
return {
|
|
157
|
+
key: value
|
|
158
|
+
for key, value in raw.items()
|
|
159
|
+
if isinstance(key, str) and isinstance(value, str)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _save_chat_state(state_path: Path, state: dict[str, str]) -> None:
|
|
164
|
+
state_path.write_text(json.dumps(state, indent=2), encoding="utf-8")
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _format_response(
|
|
168
|
+
*,
|
|
169
|
+
tool_name: str,
|
|
170
|
+
display_name: str,
|
|
171
|
+
response: Space.Message,
|
|
172
|
+
output_json: bool,
|
|
173
|
+
) -> str:
|
|
174
|
+
if output_json:
|
|
175
|
+
return json.dumps(response, indent=2, default=str)
|
|
176
|
+
|
|
177
|
+
text = response.get("text")
|
|
178
|
+
if text is None:
|
|
179
|
+
raise ValueError(f"subagent {tool_name!r} returned no text")
|
|
180
|
+
|
|
181
|
+
lines = [f"Subagent: {display_name} ({tool_name})"]
|
|
182
|
+
chat_id = response.get("chatId")
|
|
183
|
+
if chat_id:
|
|
184
|
+
lines.append(f"Chat: {chat_id}")
|
|
185
|
+
lines.append("")
|
|
186
|
+
lines.append(text)
|
|
187
|
+
return "\n".join(lines)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
async def _send_to_subagent(
|
|
191
|
+
*,
|
|
192
|
+
state: ShellState,
|
|
193
|
+
subagent: SubagentDefinition,
|
|
194
|
+
message: str,
|
|
195
|
+
chat_id: str | None,
|
|
196
|
+
parent_chat_id: str | None,
|
|
197
|
+
parent_message_id: str | None,
|
|
198
|
+
parent_assistant_id: str | None,
|
|
199
|
+
) -> Space.Message:
|
|
200
|
+
assistant_id = _get_str(
|
|
201
|
+
subagent,
|
|
202
|
+
"assistantId",
|
|
203
|
+
"assistant_id",
|
|
204
|
+
required=True,
|
|
205
|
+
)
|
|
206
|
+
assert assistant_id is not None
|
|
207
|
+
|
|
208
|
+
correlation: Space.Correlation | None = None
|
|
209
|
+
if parent_chat_id and parent_message_id and parent_assistant_id:
|
|
210
|
+
correlation = {
|
|
211
|
+
"parentMessageId": parent_message_id,
|
|
212
|
+
"parentChatId": parent_chat_id,
|
|
213
|
+
"parentAssistantId": parent_assistant_id,
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return await send_message_and_wait_for_completion(
|
|
217
|
+
user_id=state.config.user_id,
|
|
218
|
+
company_id=state.config.company_id,
|
|
219
|
+
assistant_id=assistant_id,
|
|
220
|
+
text=message,
|
|
221
|
+
tool_choices=_get_forced_tools(subagent),
|
|
222
|
+
chat_id=chat_id,
|
|
223
|
+
poll_interval=_get_float(
|
|
224
|
+
subagent,
|
|
225
|
+
"pollInterval",
|
|
226
|
+
"poll_interval",
|
|
227
|
+
default=1.0,
|
|
228
|
+
),
|
|
229
|
+
max_wait=_get_float(
|
|
230
|
+
subagent,
|
|
231
|
+
"maxWait",
|
|
232
|
+
"max_wait",
|
|
233
|
+
default=120.0,
|
|
234
|
+
),
|
|
235
|
+
stop_condition=_get_stop_condition(subagent),
|
|
236
|
+
correlation=correlation,
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def cmd_subagent(
|
|
241
|
+
state: ShellState,
|
|
242
|
+
tool_name: str,
|
|
243
|
+
message: str,
|
|
244
|
+
*,
|
|
245
|
+
config_path: str | None = None,
|
|
246
|
+
parent_chat_id: str | None = None,
|
|
247
|
+
parent_message_id: str | None = None,
|
|
248
|
+
parent_assistant_id: str | None = None,
|
|
249
|
+
reset_chat: bool = False,
|
|
250
|
+
output_json: bool = False,
|
|
251
|
+
) -> str:
|
|
252
|
+
"""Invoke one configured connected-space subagent."""
|
|
253
|
+
try:
|
|
254
|
+
resolved_config_path = resolve_config_path(config_path)
|
|
255
|
+
subagent = _find_subagent(
|
|
256
|
+
_load_config(resolved_config_path),
|
|
257
|
+
tool_name=tool_name,
|
|
258
|
+
)
|
|
259
|
+
display_name = _get_str(subagent, "displayName") or tool_name
|
|
260
|
+
configured_chat_id = _get_str(subagent, "chatId", "chat_id")
|
|
261
|
+
reuse_chat = _get_bool(subagent, "reuseChat", "reuse_chat", default=True)
|
|
262
|
+
|
|
263
|
+
state_path = resolved_config_path.parent / STATE_FILENAME
|
|
264
|
+
chat_id = configured_chat_id
|
|
265
|
+
chat_state: dict[str, str] = {}
|
|
266
|
+
if chat_id is None and reuse_chat and not reset_chat:
|
|
267
|
+
chat_state = _load_chat_state(state_path)
|
|
268
|
+
chat_id = chat_state.get(tool_name)
|
|
269
|
+
|
|
270
|
+
response = asyncio.run(
|
|
271
|
+
_send_to_subagent(
|
|
272
|
+
state=state,
|
|
273
|
+
subagent=subagent,
|
|
274
|
+
message=message,
|
|
275
|
+
chat_id=chat_id,
|
|
276
|
+
parent_chat_id=parent_chat_id,
|
|
277
|
+
parent_message_id=parent_message_id,
|
|
278
|
+
parent_assistant_id=parent_assistant_id,
|
|
279
|
+
)
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
response_chat_id = response.get("chatId")
|
|
283
|
+
if configured_chat_id is None and reuse_chat and response_chat_id:
|
|
284
|
+
chat_state = chat_state or _load_chat_state(state_path)
|
|
285
|
+
chat_state[tool_name] = response_chat_id
|
|
286
|
+
try:
|
|
287
|
+
_save_chat_state(state_path, chat_state)
|
|
288
|
+
except OSError:
|
|
289
|
+
pass
|
|
290
|
+
|
|
291
|
+
return _format_response(
|
|
292
|
+
tool_name=tool_name,
|
|
293
|
+
display_name=display_name,
|
|
294
|
+
response=response,
|
|
295
|
+
output_json=output_json,
|
|
296
|
+
)
|
|
297
|
+
except (ValueError, OSError, TimeoutError, APIError) as exc:
|
|
298
|
+
return f"{SUBAGENT_ERROR_PREFIX} {exc}"
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: unique-cli-subagent
|
|
3
|
+
description: >-
|
|
4
|
+
Invoke connected Unique spaces/subagents through the unique-cli subagent
|
|
5
|
+
command. Use when the workspace exposes connected-space tools and you need
|
|
6
|
+
to delegate a question or task to one of those configured assistants.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Unique CLI -- Connected Spaces / Subagents
|
|
10
|
+
|
|
11
|
+
Use this skill to call a connected Unique space as a tool. Each connected
|
|
12
|
+
space is configured by the platform in `.unique-subagents.json`; call it by
|
|
13
|
+
the tool name shown in the generated connected-space skill.
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
unique-cli subagent "<tool_name>" "<message>" \
|
|
19
|
+
--chat-id "$UNIQUE_CHAT_ID" \
|
|
20
|
+
--message-id "$UNIQUE_MESSAGE_ID" \
|
|
21
|
+
--assistant-id "$UNIQUE_ASSISTANT_ID"
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Rules
|
|
25
|
+
|
|
26
|
+
1. Use the exact tool name from the connected-space skill or from
|
|
27
|
+
`.unique-subagents.json`.
|
|
28
|
+
2. Send a focused message that contains the subagent-specific task and the
|
|
29
|
+
relevant context.
|
|
30
|
+
3. Treat the returned text as the connected space's answer. Do not invent
|
|
31
|
+
details that are not in the response.
|
|
32
|
+
4. If the connected space has references in its text, preserve them exactly.
|
|
33
|
+
|
|
34
|
+
## Options
|
|
35
|
+
|
|
36
|
+
| Option | Default | Description |
|
|
37
|
+
|--------|---------|-------------|
|
|
38
|
+
| `<tool_name>` | required | Name of the configured connected-space tool. |
|
|
39
|
+
| `<message>` | required | Prompt sent to the connected space. |
|
|
40
|
+
| `--reset-chat` | off | Start from a fresh subagent chat instead of reusing the saved chat. |
|
|
41
|
+
| `--json` | off | Print the raw response JSON. |
|
|
42
|
+
| `--config` | `.unique-subagents.json` | Override the config path. |
|
|
43
|
+
|
|
44
|
+
## Prerequisites
|
|
45
|
+
|
|
46
|
+
The platform sets these environment variables automatically:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
UNIQUE_USER_ID
|
|
50
|
+
UNIQUE_COMPANY_ID
|
|
51
|
+
UNIQUE_CHAT_ID
|
|
52
|
+
UNIQUE_MESSAGE_ID
|
|
53
|
+
UNIQUE_ASSISTANT_ID
|
|
54
|
+
UNIQUE_API_KEY
|
|
55
|
+
UNIQUE_APP_ID
|
|
56
|
+
```
|
|
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.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/api_resources/__init__.py
RENAMED
|
File without changes
|
{unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/api_resources/_acronyms.py
RENAMED
|
File without changes
|
{unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/api_resources/_agentic_table.py
RENAMED
|
File without changes
|
{unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/api_resources/_analytics_order.py
RENAMED
|
File without changes
|
{unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/api_resources/_benchmarking.py
RENAMED
|
File without changes
|
{unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/api_resources/_briefing.py
RENAMED
|
File without changes
|
{unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/api_resources/_chat_completion.py
RENAMED
|
File without changes
|
{unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/api_resources/_content.py
RENAMED
|
File without changes
|
{unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/api_resources/_elicitation.py
RENAMED
|
File without changes
|
{unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/api_resources/_embedding.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/api_resources/_integrated.py
RENAMED
|
File without changes
|
{unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/api_resources/_llm_models.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/api_resources/_message.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/api_resources/_message_log.py
RENAMED
|
File without changes
|
{unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/api_resources/_message_tool.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/api_resources/_scheduled_task.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/api_resources/_search_string.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/api_resources/_web_search.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/cli/commands/elicitation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/cli/commands/navigation.py
RENAMED
|
File without changes
|
{unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/cli/commands/scheduled_tasks.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/cli/commands/web_search.py
RENAMED
|
File without changes
|
{unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/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
|
{unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/unique_sdk/utils/analytics_order_run.py
RENAMED
|
File without changes
|
{unique_sdk-2026.24.0.dev0 → unique_sdk-2026.24.0.dev1}/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
|