unique-orchestrator 2026.28.0.dev2__tar.gz → 2026.28.0.dev3__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 (32) hide show
  1. {unique_orchestrator-2026.28.0.dev2 → unique_orchestrator-2026.28.0.dev3}/PKG-INFO +2 -2
  2. {unique_orchestrator-2026.28.0.dev2 → unique_orchestrator-2026.28.0.dev3}/pyproject.toml +2 -2
  3. {unique_orchestrator-2026.28.0.dev2 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/config.py +40 -0
  4. {unique_orchestrator-2026.28.0.dev2 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/tests/test_config_services_validators.py +69 -0
  5. {unique_orchestrator-2026.28.0.dev2 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/unique_ai.py +1 -1
  6. {unique_orchestrator-2026.28.0.dev2 → unique_orchestrator-2026.28.0.dev3}/README.md +0 -0
  7. {unique_orchestrator-2026.28.0.dev2 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/__init__.py +0 -0
  8. {unique_orchestrator-2026.28.0.dev2 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/_builders/__init__.py +0 -0
  9. {unique_orchestrator-2026.28.0.dev2 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/_builders/inject_tool_reminders.py +0 -0
  10. {unique_orchestrator-2026.28.0.dev2 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/_builders/loop_iteration_runner.py +0 -0
  11. {unique_orchestrator-2026.28.0.dev2 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/_builders/open_file_setup.py +0 -0
  12. {unique_orchestrator-2026.28.0.dev2 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/_builders/skill_setup.py +0 -0
  13. {unique_orchestrator-2026.28.0.dev2 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/prompts/generic_reference_prompt.jinja2 +0 -0
  14. {unique_orchestrator-2026.28.0.dev2 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/prompts/system_prompt.jinja2 +0 -0
  15. {unique_orchestrator-2026.28.0.dev2 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/prompts/user_message_prompt.jinja2 +0 -0
  16. {unique_orchestrator-2026.28.0.dev2 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/settings.py +0 -0
  17. {unique_orchestrator-2026.28.0.dev2 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/tests/test_agent_config_rjsf_ui_schema.py +0 -0
  18. {unique_orchestrator-2026.28.0.dev2 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/tests/test_build_loop_iteration_runner.py +0 -0
  19. {unique_orchestrator-2026.28.0.dev2 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/tests/test_qwen_max_loop_iterations.py +0 -0
  20. {unique_orchestrator-2026.28.0.dev2 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/tests/test_register_code_interpreter_postprocessors.py +0 -0
  21. {unique_orchestrator-2026.28.0.dev2 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/tests/test_unique_ai_async_modify.py +0 -0
  22. {unique_orchestrator-2026.28.0.dev2 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/tests/test_unique_ai_execution_timing.py +0 -0
  23. {unique_orchestrator-2026.28.0.dev2 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/tests/test_unique_ai_get_filtered_user_metadata.py +0 -0
  24. {unique_orchestrator-2026.28.0.dev2 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/tests/test_unique_ai_log_tool_calls.py +0 -0
  25. {unique_orchestrator-2026.28.0.dev2 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/tests/test_unique_ai_loop_debug_params.py +0 -0
  26. {unique_orchestrator-2026.28.0.dev2 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/tests/test_unique_ai_persist_tool_calls.py +0 -0
  27. {unique_orchestrator-2026.28.0.dev2 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/tests/test_unique_ai_plan_or_execute_include.py +0 -0
  28. {unique_orchestrator-2026.28.0.dev2 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/tests/test_unique_ai_reference_order.py +0 -0
  29. {unique_orchestrator-2026.28.0.dev2 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/tests/test_unique_ai_render_system_prompt_user_instructions.py +0 -0
  30. {unique_orchestrator-2026.28.0.dev2 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/tests/test_utils_resolve_other_options.py +0 -0
  31. {unique_orchestrator-2026.28.0.dev2 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/unique_ai_builder.py +0 -0
  32. {unique_orchestrator-2026.28.0.dev2 → unique_orchestrator-2026.28.0.dev3}/unique_orchestrator/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: unique-orchestrator
3
- Version: 2026.28.0.dev2
3
+ Version: 2026.28.0.dev3
4
4
  Summary:
5
5
  Author: Andreas Hauri
6
6
  Author-email: Andreas Hauri <andreas.hauri@unique.ai>
@@ -9,7 +9,7 @@ Requires-Dist: pydantic>=2.8.2,<3
9
9
  Requires-Dist: pydantic-settings>=2.10.1,<3
10
10
  Requires-Dist: typing-extensions>=4.9.0,<5
11
11
  Requires-Dist: jinja2>=3.1.0,<4
12
- Requires-Dist: unique-toolkit>=2026.28.0.dev0,<2026.28.0rc0
12
+ Requires-Dist: unique-toolkit>=2026.28.0.dev2,<2026.28.0rc0
13
13
  Requires-Dist: unique-stock-ticker>=2026.28.0.dev0,<2026.28.0rc0
14
14
  Requires-Dist: unique-follow-up-questions>=2026.28.0.dev0,<2026.28.0rc0
15
15
  Requires-Dist: unique-internal-search>=2026.28.0.dev0,<2026.28.0rc0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "unique_orchestrator"
3
- version = "2026.28.0.dev2"
3
+ version = "2026.28.0.dev3"
4
4
  description = ""
5
5
  readme = "README.md"
6
6
  license = { text = "Proprietary" }
@@ -13,7 +13,7 @@ dependencies = [
13
13
  "pydantic-settings>=2.10.1,<3",
14
14
  "typing-extensions>=4.9.0,<5",
15
15
  "jinja2>=3.1.0,<4",
16
- "unique-toolkit>=2026.28.0.dev0,<2026.28.0rc0",
16
+ "unique-toolkit>=2026.28.0.dev2,<2026.28.0rc0",
17
17
  "unique-stock-ticker>=2026.28.0.dev0,<2026.28.0rc0",
18
18
  "unique-follow-up-questions>=2026.28.0.dev0,<2026.28.0rc0",
19
19
  "unique-internal-search>=2026.28.0.dev0,<2026.28.0rc0",
@@ -37,6 +37,12 @@ from unique_toolkit.agentic.tools.a2a import (
37
37
  REFERENCING_INSTRUCTIONS_FOR_USER_PROMPT,
38
38
  )
39
39
  from unique_toolkit.agentic.tools.a2a.evaluation import SubAgentEvaluationServiceConfig
40
+ from unique_toolkit.agentic.tools.experimental.ask_user_tool import (
41
+ AskUserTool,
42
+ )
43
+ from unique_toolkit.agentic.tools.experimental.ask_user_tool import (
44
+ AskUserToolConfig as BaseAskUserToolConfig,
45
+ )
40
46
  from unique_toolkit.agentic.tools.experimental.open_file_tool.config import (
41
47
  OpenFileToolConfig,
42
48
  )
@@ -391,6 +397,13 @@ class UploadedSearchToolConfig(BaseToolConfig):
391
397
  return self
392
398
 
393
399
 
400
+ class AskUserToolConfig(BaseAskUserToolConfig):
401
+ enabled: bool = Field(
402
+ default=False,
403
+ description="Enable the AskUser elicitation tool.",
404
+ )
405
+
406
+
394
407
  class ExperimentalConfig(BaseToolConfig):
395
408
  """Experimental features this part of the configuration might evolve in the future continuously"""
396
409
 
@@ -431,6 +444,12 @@ class ExperimentalConfig(BaseToolConfig):
431
444
 
432
445
  uploaded_search_tool_config: UploadedSearchToolConfig = UploadedSearchToolConfig()
433
446
 
447
+ ask_user_tool_config: AskUserToolConfig = Field(
448
+ title="Ask User Tool (Elicitation)",
449
+ description="Configuration for the AskUser tool",
450
+ default_factory=AskUserToolConfig,
451
+ )
452
+
434
453
  use_responses_api: bool = Field(
435
454
  default=False,
436
455
  description="If set, the main agent will use the Responses API from OpenAI",
@@ -593,6 +612,27 @@ class UniqueAIConfig(BaseToolConfig):
593
612
 
594
613
  return self
595
614
 
615
+ @model_validator(mode="after")
616
+ def inject_ask_user_tool(self) -> "UniqueAIConfig":
617
+ tool_names = [t.name for t in self.space.tools]
618
+ has_tool = AskUserTool.name in tool_names
619
+ config = self.agent.experimental.ask_user_tool_config
620
+
621
+ if config.enabled and not has_tool:
622
+ self.space.tools.append(
623
+ ToolBuildConfig(
624
+ name=AskUserTool.name,
625
+ display_name=AskUserTool.DISPLAY_NAME,
626
+ configuration=config,
627
+ )
628
+ )
629
+ elif not config.enabled and has_tool:
630
+ self.space.tools = [
631
+ t for t in self.space.tools if t.name != AskUserTool.name
632
+ ]
633
+
634
+ return self
635
+
596
636
  @property
597
637
  def effective_max_loop_iterations(self) -> int:
598
638
  """Effective max loop iterations, accounting for model-specific overrides."""
@@ -1,6 +1,9 @@
1
1
  import pytest
2
2
  from unique_follow_up_questions.config import FollowUpQuestionsConfig
3
3
  from unique_stock_ticker.config import StockTickerConfig
4
+ from unique_toolkit.agentic.tools.experimental.ask_user_tool import (
5
+ AskUserTool,
6
+ )
4
7
  from unique_toolkit.agentic.tools.experimental.open_file_tool.config import (
5
8
  OpenFileToolConfig,
6
9
  )
@@ -23,6 +26,7 @@ from unique_toolkit.language_model.schemas import LanguageModelTokenLimits
23
26
  from unique_user_memory.config import UserMemoryConfig
24
27
 
25
28
  from unique_orchestrator.config import (
29
+ AskUserToolConfig,
26
30
  EvaluationConfig,
27
31
  UniqueAIConfig,
28
32
  UniqueAIServices,
@@ -496,3 +500,68 @@ class TestUniqueAIConfigUserMemory:
496
500
  config = UniqueAIConfig(space={"allowUserMemory": True})
497
501
 
498
502
  assert config.space.allow_user_memory is True
503
+
504
+
505
+ class TestUniqueAIConfigInjectAskUserToolValidator:
506
+ """Tests for UniqueAIConfig.inject_ask_user_tool (AskUser)."""
507
+
508
+ def _entries(self, config: UniqueAIConfig) -> list[ToolBuildConfig]:
509
+ return [t for t in config.space.tools if t.name == AskUserTool.name]
510
+
511
+ def test_appends_when_enabled(self) -> None:
512
+ config = UniqueAIConfig(
513
+ space=UniqueAISpaceConfig(
514
+ language_model=_make_model(supports_responses_api=True),
515
+ tools=[],
516
+ ),
517
+ agent={
518
+ "experimental": {
519
+ "ask_user_tool_config": AskUserToolConfig(enabled=True)
520
+ }
521
+ },
522
+ )
523
+
524
+ entries = self._entries(config)
525
+ assert len(entries) == 1
526
+ assert entries[0].name == AskUserTool.name
527
+ assert isinstance(entries[0].configuration, AskUserToolConfig)
528
+ assert entries[0].configuration.enabled is True
529
+
530
+ def test_removes_when_disabled(self) -> None:
531
+ config = UniqueAIConfig(
532
+ space=UniqueAISpaceConfig(
533
+ language_model=_make_model(supports_responses_api=True),
534
+ tools=[
535
+ ToolBuildConfig(
536
+ name=AskUserTool.name,
537
+ configuration=AskUserToolConfig(enabled=False),
538
+ )
539
+ ],
540
+ ),
541
+ agent={
542
+ "experimental": {
543
+ "ask_user_tool_config": AskUserToolConfig(enabled=False)
544
+ }
545
+ },
546
+ )
547
+
548
+ assert self._entries(config) == []
549
+
550
+ def test_idempotent_when_already_present(self) -> None:
551
+ existing = ToolBuildConfig(
552
+ name=AskUserTool.name,
553
+ configuration=AskUserToolConfig(enabled=True),
554
+ )
555
+ config = UniqueAIConfig(
556
+ space=UniqueAISpaceConfig(
557
+ language_model=_make_model(supports_responses_api=True),
558
+ tools=[existing],
559
+ ),
560
+ agent={
561
+ "experimental": {
562
+ "ask_user_tool_config": AskUserToolConfig(enabled=True)
563
+ }
564
+ },
565
+ )
566
+
567
+ assert len(self._entries(config)) == 1
@@ -701,7 +701,7 @@ class UniqueAI:
701
701
  # (see ``unique_skill_tool.SkillTool._log_skill_loaded``), so it is
702
702
  # redundant and noisy to also list it here.
703
703
 
704
- tool_names_not_to_log: set[str] = {"DeepResearch", "Skill"}
704
+ tool_names_not_to_log: set[str] = {"DeepResearch", "Skill", "AskUser"}
705
705
 
706
706
  used_tools: dict[str, int] = {}
707
707
  for tool_call in tool_calls: