unique_orchestrator 2026.30.0.dev6__tar.gz → 2026.30.0.dev7__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 (33) hide show
  1. {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/PKG-INFO +2 -2
  2. {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/pyproject.toml +2 -2
  3. {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/tests/test_unique_ai_async_modify.py +24 -0
  4. {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/tests/test_unique_ai_loop_debug_params.py +28 -0
  5. {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/unique_ai.py +22 -2
  6. {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/README.md +0 -0
  7. {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/__init__.py +0 -0
  8. {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/_builders/__init__.py +0 -0
  9. {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/_builders/history_manager.py +0 -0
  10. {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/_builders/inject_tool_reminders.py +0 -0
  11. {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/_builders/loop_iteration_runner.py +0 -0
  12. {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/_builders/open_file_setup.py +0 -0
  13. {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/_builders/skill_setup.py +0 -0
  14. {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/config.py +0 -0
  15. {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/prompts/generic_reference_prompt.jinja2 +0 -0
  16. {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/prompts/system_prompt.jinja2 +0 -0
  17. {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/prompts/user_message_prompt.jinja2 +0 -0
  18. {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/settings.py +0 -0
  19. {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/tests/test_agent_config_rjsf_ui_schema.py +0 -0
  20. {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/tests/test_build_loop_iteration_runner.py +0 -0
  21. {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/tests/test_config_services_validators.py +0 -0
  22. {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/tests/test_qwen_max_loop_iterations.py +0 -0
  23. {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/tests/test_register_code_interpreter_postprocessors.py +0 -0
  24. {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/tests/test_unique_ai_execution_timing.py +0 -0
  25. {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/tests/test_unique_ai_get_filtered_user_metadata.py +0 -0
  26. {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/tests/test_unique_ai_log_tool_calls.py +0 -0
  27. {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/tests/test_unique_ai_persist_tool_calls.py +0 -0
  28. {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/tests/test_unique_ai_plan_or_execute_include.py +0 -0
  29. {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/tests/test_unique_ai_reference_order.py +0 -0
  30. {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/tests/test_unique_ai_render_system_prompt_user_instructions.py +0 -0
  31. {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/tests/test_utils_resolve_other_options.py +0 -0
  32. {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/unique_ai_builder.py +0 -0
  33. {unique_orchestrator-2026.30.0.dev6 → unique_orchestrator-2026.30.0.dev7}/unique_orchestrator/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: unique-orchestrator
3
- Version: 2026.30.0.dev6
3
+ Version: 2026.30.0.dev7
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.30.0.dev17,<2026.30.0rc0
12
+ Requires-Dist: unique-toolkit>=2026.30.0.dev19,<2026.30.0rc0
13
13
  Requires-Dist: unique-stock-ticker>=2026.30.0.dev0,<2026.30.0rc0
14
14
  Requires-Dist: unique-follow-up-questions>=2026.30.0.dev0,<2026.30.0rc0
15
15
  Requires-Dist: unique-internal-search>=2026.30.0.dev0,<2026.30.0rc0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "unique_orchestrator"
3
- version = "2026.30.0.dev6"
3
+ version = "2026.30.0.dev7"
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.30.0.dev17,<2026.30.0rc0",
16
+ "unique-toolkit>=2026.30.0.dev19,<2026.30.0rc0",
17
17
  "unique-stock-ticker>=2026.30.0.dev0,<2026.30.0rc0",
18
18
  "unique-follow-up-questions>=2026.30.0.dev0,<2026.30.0rc0",
19
19
  "unique-internal-search>=2026.30.0.dev0,<2026.30.0rc0",
@@ -3,6 +3,9 @@
3
3
  from unittest.mock import AsyncMock, MagicMock
4
4
 
5
5
  import pytest
6
+ from unique_toolkit.agentic.tools.openai_builtin.code_interpreter import (
7
+ DisplayCodeInterpreterFilesPostProcessor,
8
+ )
6
9
 
7
10
  from unique_orchestrator.unique_ai import UniqueAI
8
11
 
@@ -85,6 +88,7 @@ def _make_ua(monkeypatch, *, feature_flag_enabled: bool = False):
85
88
  ua._mcp_servers = []
86
89
  ua._execution_times = []
87
90
  ua._current_loop_timing = {}
91
+ ua._generated_files_info = None
88
92
  ua.current_iteration_index = 0
89
93
  ua._skill_choices = []
90
94
 
@@ -126,3 +130,23 @@ async def test_process_plan_calls_modify_async_on_empty_response(monkeypatch):
126
130
 
127
131
  assert result is True
128
132
  ua._chat_service.modify_assistant_message_async.assert_called()
133
+
134
+
135
+ @pytest.mark.ai
136
+ @pytest.mark.asyncio
137
+ async def test_handle_no_tool_calls_stores_returned_artifacts_for_analytics(
138
+ monkeypatch,
139
+ ):
140
+ ua = _make_ua(monkeypatch)
141
+ artifacts = {"count": 1, "filetypes": ["csv"]}
142
+ ua._postprocessor_manager.run_postprocessors.return_value = {
143
+ DisplayCodeInterpreterFilesPostProcessor.__name__: artifacts
144
+ }
145
+ ua._current_loop_timing = {"post_processing": {}, "evaluation": {}}
146
+
147
+ response = MagicMock()
148
+ response.model_copy.return_value = response
149
+
150
+ await ua._handle_no_tool_calls(response)
151
+
152
+ assert ua._generated_files_info == artifacts
@@ -478,6 +478,34 @@ class TestRunLoopDebugParams:
478
478
  analytics_call = ua._debug_info_manager.add_analytics.call_args
479
479
  assert analytics_call.kwargs["total_time_to_answer_ms"] is None
480
480
 
481
+ @pytest.mark.ai
482
+ @pytest.mark.asyncio
483
+ async def test_run__generated_files_info__reset_at_start_prevents_stale_artifacts(
484
+ self, monkeypatch
485
+ ) -> None:
486
+ """
487
+ Purpose: Verify run() resets _generated_files_info at the start, so a rerun
488
+ that exits without reaching _handle_no_tool_calls does not report the previous
489
+ run's artifacts.
490
+ Why this matters: _generated_files_info persists on the UniqueAI instance;
491
+ without a reset it leaks a prior run's artifact count/filetypes into the
492
+ current answer's analytics (Cursor bot, UN-22110).
493
+ Setup summary: Seed a stale value, force an early loop exit via _process_plan
494
+ returning True (a tool took control — _handle_no_tool_calls never runs), and
495
+ assert add_analytics received artifacts=None.
496
+ """
497
+ ua = self._build_run_ua(monkeypatch)
498
+ # Leftover from a hypothetical previous run on the same instance.
499
+ ua._generated_files_info = {"count": 99, "filetypes": ["stale"]}
500
+ # Exit the loop as if a tool took control: _handle_no_tool_calls (which sets
501
+ # _generated_files_info) is skipped, but finalization still calls add_analytics.
502
+ ua._process_plan = AsyncMock(return_value=True) # type: ignore[method-assign]
503
+
504
+ await ua.run()
505
+
506
+ analytics_call = ua._debug_info_manager.add_analytics.call_args
507
+ assert analytics_call.kwargs["artifacts"] is None
508
+
481
509
  @pytest.mark.ai
482
510
  @pytest.mark.asyncio
483
511
  async def test_run__loop_params__contains_one_entry_per_iteration(
@@ -32,6 +32,12 @@ from unique_toolkit.agentic.tools.experimental.open_file_tool import (
32
32
  OpenFileToolRuntime,
33
33
  OpenFileToolRuntimeConfig,
34
34
  )
35
+ from unique_toolkit.agentic.tools.openai_builtin.code_interpreter import (
36
+ DisplayCodeInterpreterFilesPostProcessor,
37
+ )
38
+ from unique_toolkit.agentic.tools.openai_builtin.code_interpreter.postprocessors.generated_files import (
39
+ ArtifactsDebugInfo,
40
+ )
35
41
  from unique_toolkit.agentic.tools.tool_manager import (
36
42
  ResponsesApiToolManager,
37
43
  SafeTaskExecutor,
@@ -197,6 +203,7 @@ class UniqueAI:
197
203
  self._execution_times: list[dict[str, Any]] = []
198
204
  self._current_loop_timing: dict[str, Any] = {}
199
205
  self._loop_debug_params: list[dict[str, Any]] = []
206
+ self._generated_files_info: ArtifactsDebugInfo | None = None
200
207
 
201
208
  async def _on_cancellation(self, _event: CancellationEvent) -> None:
202
209
  """Subscriber called by the cancellation event bus."""
@@ -222,6 +229,10 @@ class UniqueAI:
222
229
 
223
230
  self._execution_times = []
224
231
  self._loop_debug_params = []
232
+ # Reset per-run artifact analytics too: a later run that exits without
233
+ # reaching _handle_no_tool_calls (tool takes control / empty response /
234
+ # cancellation) must not report the previous run's artifacts.
235
+ self._generated_files_info = None
225
236
  run_start = time.perf_counter()
226
237
 
227
238
  await preload_invoked_skills(
@@ -363,6 +374,7 @@ class UniqueAI:
363
374
  answer_length=len(self._last_assistant_text or ""),
364
375
  loop_iteration_count=len(self._execution_times),
365
376
  total_time_to_answer_ms=total_time_to_answer_ms,
377
+ artifacts=self._generated_files_info,
366
378
  )
367
379
 
368
380
  # Get current debug info from chat service and add debug info from run. Do not update if DeepResearch is in the tool names.
@@ -696,11 +708,19 @@ class UniqueAI:
696
708
  loop_response.model_copy(deep=True),
697
709
  )
698
710
 
699
- _, evaluation_results = await asyncio.gather(
711
+ postprocessor_result, evaluation_results = await asyncio.gather(
700
712
  postprocessor_result,
701
713
  evaluation_results,
702
714
  )
703
-
715
+ postprocessor_outputs = postprocessor_result.unpack() or {}
716
+ # run_postprocessors erases each postprocessor's return type to
717
+ # `object | None` (generic channel), so re-narrow the one we own.
718
+ self._generated_files_info = cast(
719
+ "ArtifactsDebugInfo | None",
720
+ postprocessor_outputs.get(
721
+ DisplayCodeInterpreterFilesPostProcessor.__name__
722
+ ),
723
+ )
704
724
  self._current_loop_timing["post_processing"].update(
705
725
  self._postprocessor_manager.get_execution_times()
706
726
  )