unique_toolkit 0.7.7__py3-none-any.whl → 1.23.0__py3-none-any.whl
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.
Potentially problematic release.
This version of unique_toolkit might be problematic. Click here for more details.
- unique_toolkit/__init__.py +28 -1
- unique_toolkit/_common/api_calling/human_verification_manager.py +343 -0
- unique_toolkit/_common/base_model_type_attribute.py +303 -0
- unique_toolkit/_common/chunk_relevancy_sorter/config.py +49 -0
- unique_toolkit/_common/chunk_relevancy_sorter/exception.py +5 -0
- unique_toolkit/_common/chunk_relevancy_sorter/schemas.py +46 -0
- unique_toolkit/_common/chunk_relevancy_sorter/service.py +374 -0
- unique_toolkit/_common/chunk_relevancy_sorter/tests/test_service.py +275 -0
- unique_toolkit/_common/default_language_model.py +12 -0
- unique_toolkit/_common/docx_generator/__init__.py +7 -0
- unique_toolkit/_common/docx_generator/config.py +12 -0
- unique_toolkit/_common/docx_generator/schemas.py +80 -0
- unique_toolkit/_common/docx_generator/service.py +252 -0
- unique_toolkit/_common/docx_generator/template/Doc Template.docx +0 -0
- unique_toolkit/_common/endpoint_builder.py +305 -0
- unique_toolkit/_common/endpoint_requestor.py +430 -0
- unique_toolkit/_common/exception.py +24 -0
- unique_toolkit/_common/feature_flags/schema.py +9 -0
- unique_toolkit/_common/pydantic/rjsf_tags.py +936 -0
- unique_toolkit/_common/pydantic_helpers.py +154 -0
- unique_toolkit/_common/referencing.py +53 -0
- unique_toolkit/_common/string_utilities.py +140 -0
- unique_toolkit/_common/tests/test_referencing.py +521 -0
- unique_toolkit/_common/tests/test_string_utilities.py +506 -0
- unique_toolkit/_common/token/image_token_counting.py +67 -0
- unique_toolkit/_common/token/token_counting.py +204 -0
- unique_toolkit/_common/utils/__init__.py +1 -0
- unique_toolkit/_common/utils/files.py +43 -0
- unique_toolkit/_common/utils/structured_output/__init__.py +1 -0
- unique_toolkit/_common/utils/structured_output/schema.py +5 -0
- unique_toolkit/_common/utils/write_configuration.py +51 -0
- unique_toolkit/_common/validators.py +101 -4
- unique_toolkit/agentic/__init__.py +1 -0
- unique_toolkit/agentic/debug_info_manager/debug_info_manager.py +28 -0
- unique_toolkit/agentic/debug_info_manager/test/test_debug_info_manager.py +278 -0
- unique_toolkit/agentic/evaluation/config.py +36 -0
- unique_toolkit/{evaluators → agentic/evaluation}/context_relevancy/prompts.py +25 -0
- unique_toolkit/agentic/evaluation/context_relevancy/schema.py +80 -0
- unique_toolkit/agentic/evaluation/context_relevancy/service.py +273 -0
- unique_toolkit/agentic/evaluation/evaluation_manager.py +218 -0
- unique_toolkit/agentic/evaluation/hallucination/constants.py +61 -0
- unique_toolkit/agentic/evaluation/hallucination/hallucination_evaluation.py +111 -0
- unique_toolkit/{evaluators → agentic/evaluation}/hallucination/prompts.py +1 -1
- unique_toolkit/{evaluators → agentic/evaluation}/hallucination/service.py +16 -15
- unique_toolkit/{evaluators → agentic/evaluation}/hallucination/utils.py +30 -20
- unique_toolkit/{evaluators → agentic/evaluation}/output_parser.py +20 -2
- unique_toolkit/{evaluators → agentic/evaluation}/schemas.py +27 -7
- unique_toolkit/agentic/evaluation/tests/test_context_relevancy_service.py +253 -0
- unique_toolkit/agentic/evaluation/tests/test_output_parser.py +87 -0
- unique_toolkit/agentic/history_manager/history_construction_with_contents.py +297 -0
- unique_toolkit/agentic/history_manager/history_manager.py +242 -0
- unique_toolkit/agentic/history_manager/loop_token_reducer.py +484 -0
- unique_toolkit/agentic/history_manager/utils.py +96 -0
- unique_toolkit/agentic/postprocessor/postprocessor_manager.py +212 -0
- unique_toolkit/agentic/reference_manager/reference_manager.py +103 -0
- unique_toolkit/agentic/responses_api/__init__.py +19 -0
- unique_toolkit/agentic/responses_api/postprocessors/code_display.py +63 -0
- unique_toolkit/agentic/responses_api/postprocessors/generated_files.py +145 -0
- unique_toolkit/agentic/responses_api/stream_handler.py +15 -0
- unique_toolkit/agentic/short_term_memory_manager/persistent_short_term_memory_manager.py +141 -0
- unique_toolkit/agentic/thinking_manager/thinking_manager.py +103 -0
- unique_toolkit/agentic/tools/__init__.py +1 -0
- unique_toolkit/agentic/tools/a2a/__init__.py +36 -0
- unique_toolkit/agentic/tools/a2a/config.py +17 -0
- unique_toolkit/agentic/tools/a2a/evaluation/__init__.py +15 -0
- unique_toolkit/agentic/tools/a2a/evaluation/_utils.py +66 -0
- unique_toolkit/agentic/tools/a2a/evaluation/config.py +55 -0
- unique_toolkit/agentic/tools/a2a/evaluation/evaluator.py +260 -0
- unique_toolkit/agentic/tools/a2a/evaluation/summarization_user_message.j2 +9 -0
- unique_toolkit/agentic/tools/a2a/manager.py +55 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/__init__.py +21 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/_display_utils.py +185 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/_ref_utils.py +73 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/config.py +45 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/display.py +180 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/references.py +101 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/test/test_display_utils.py +1335 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/test/test_ref_utils.py +603 -0
- unique_toolkit/agentic/tools/a2a/prompts.py +46 -0
- unique_toolkit/agentic/tools/a2a/response_watcher/__init__.py +6 -0
- unique_toolkit/agentic/tools/a2a/response_watcher/service.py +91 -0
- unique_toolkit/agentic/tools/a2a/tool/__init__.py +4 -0
- unique_toolkit/agentic/tools/a2a/tool/_memory.py +26 -0
- unique_toolkit/agentic/tools/a2a/tool/_schema.py +9 -0
- unique_toolkit/agentic/tools/a2a/tool/config.py +73 -0
- unique_toolkit/agentic/tools/a2a/tool/service.py +306 -0
- unique_toolkit/agentic/tools/agent_chunks_hanlder.py +65 -0
- unique_toolkit/agentic/tools/config.py +167 -0
- unique_toolkit/agentic/tools/factory.py +44 -0
- unique_toolkit/agentic/tools/mcp/__init__.py +4 -0
- unique_toolkit/agentic/tools/mcp/manager.py +71 -0
- unique_toolkit/agentic/tools/mcp/models.py +28 -0
- unique_toolkit/agentic/tools/mcp/tool_wrapper.py +234 -0
- unique_toolkit/agentic/tools/openai_builtin/__init__.py +11 -0
- unique_toolkit/agentic/tools/openai_builtin/base.py +30 -0
- unique_toolkit/agentic/tools/openai_builtin/code_interpreter/__init__.py +8 -0
- unique_toolkit/agentic/tools/openai_builtin/code_interpreter/config.py +57 -0
- unique_toolkit/agentic/tools/openai_builtin/code_interpreter/service.py +230 -0
- unique_toolkit/agentic/tools/openai_builtin/manager.py +62 -0
- unique_toolkit/agentic/tools/schemas.py +141 -0
- unique_toolkit/agentic/tools/test/test_mcp_manager.py +536 -0
- unique_toolkit/agentic/tools/test/test_tool_progress_reporter.py +445 -0
- unique_toolkit/agentic/tools/tool.py +183 -0
- unique_toolkit/agentic/tools/tool_manager.py +523 -0
- unique_toolkit/agentic/tools/tool_progress_reporter.py +285 -0
- unique_toolkit/agentic/tools/utils/__init__.py +19 -0
- unique_toolkit/agentic/tools/utils/execution/__init__.py +1 -0
- unique_toolkit/agentic/tools/utils/execution/execution.py +286 -0
- unique_toolkit/agentic/tools/utils/source_handling/__init__.py +0 -0
- unique_toolkit/agentic/tools/utils/source_handling/schema.py +21 -0
- unique_toolkit/agentic/tools/utils/source_handling/source_formatting.py +207 -0
- unique_toolkit/agentic/tools/utils/source_handling/tests/test_source_formatting.py +216 -0
- unique_toolkit/app/__init__.py +6 -0
- unique_toolkit/app/dev_util.py +180 -0
- unique_toolkit/app/init_sdk.py +32 -1
- unique_toolkit/app/schemas.py +198 -31
- unique_toolkit/app/unique_settings.py +367 -0
- unique_toolkit/chat/__init__.py +8 -1
- unique_toolkit/chat/deprecated/service.py +232 -0
- unique_toolkit/chat/functions.py +642 -77
- unique_toolkit/chat/rendering.py +34 -0
- unique_toolkit/chat/responses_api.py +461 -0
- unique_toolkit/chat/schemas.py +133 -2
- unique_toolkit/chat/service.py +115 -767
- unique_toolkit/content/functions.py +153 -4
- unique_toolkit/content/schemas.py +122 -15
- unique_toolkit/content/service.py +278 -44
- unique_toolkit/content/smart_rules.py +301 -0
- unique_toolkit/content/utils.py +8 -3
- unique_toolkit/embedding/service.py +102 -11
- unique_toolkit/framework_utilities/__init__.py +1 -0
- unique_toolkit/framework_utilities/langchain/client.py +71 -0
- unique_toolkit/framework_utilities/langchain/history.py +19 -0
- unique_toolkit/framework_utilities/openai/__init__.py +6 -0
- unique_toolkit/framework_utilities/openai/client.py +83 -0
- unique_toolkit/framework_utilities/openai/message_builder.py +229 -0
- unique_toolkit/framework_utilities/utils.py +23 -0
- unique_toolkit/language_model/__init__.py +3 -0
- unique_toolkit/language_model/builder.py +27 -11
- unique_toolkit/language_model/default_language_model.py +3 -0
- unique_toolkit/language_model/functions.py +327 -43
- unique_toolkit/language_model/infos.py +992 -50
- unique_toolkit/language_model/reference.py +242 -0
- unique_toolkit/language_model/schemas.py +475 -48
- unique_toolkit/language_model/service.py +228 -27
- unique_toolkit/protocols/support.py +145 -0
- unique_toolkit/services/__init__.py +7 -0
- unique_toolkit/services/chat_service.py +1630 -0
- unique_toolkit/services/knowledge_base.py +861 -0
- unique_toolkit/short_term_memory/service.py +178 -41
- unique_toolkit/smart_rules/__init__.py +0 -0
- unique_toolkit/smart_rules/compile.py +56 -0
- unique_toolkit/test_utilities/events.py +197 -0
- {unique_toolkit-0.7.7.dist-info → unique_toolkit-1.23.0.dist-info}/METADATA +606 -7
- unique_toolkit-1.23.0.dist-info/RECORD +182 -0
- unique_toolkit/evaluators/__init__.py +0 -1
- unique_toolkit/evaluators/config.py +0 -35
- unique_toolkit/evaluators/constants.py +0 -1
- unique_toolkit/evaluators/context_relevancy/constants.py +0 -32
- unique_toolkit/evaluators/context_relevancy/service.py +0 -53
- unique_toolkit/evaluators/context_relevancy/utils.py +0 -142
- unique_toolkit/evaluators/hallucination/constants.py +0 -41
- unique_toolkit-0.7.7.dist-info/RECORD +0 -64
- /unique_toolkit/{evaluators → agentic/evaluation}/exception.py +0 -0
- {unique_toolkit-0.7.7.dist-info → unique_toolkit-1.23.0.dist-info}/LICENSE +0 -0
- {unique_toolkit-0.7.7.dist-info → unique_toolkit-1.23.0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,1335 @@
|
|
|
1
|
+
"""Unit tests for display module, focusing on HTML formatting and regex removal logic."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from unique_toolkit.agentic.tools.a2a.postprocessing._display_utils import (
|
|
8
|
+
_add_line_break,
|
|
9
|
+
_get_display_removal_re,
|
|
10
|
+
_get_display_template,
|
|
11
|
+
_join_text_blocks,
|
|
12
|
+
_wrap_hidden_div,
|
|
13
|
+
_wrap_strong,
|
|
14
|
+
_wrap_text,
|
|
15
|
+
_wrap_with_block_border,
|
|
16
|
+
_wrap_with_details_tag,
|
|
17
|
+
_wrap_with_quote_border,
|
|
18
|
+
get_sub_agent_answer_display,
|
|
19
|
+
remove_sub_agent_answer_from_text,
|
|
20
|
+
)
|
|
21
|
+
from unique_toolkit.agentic.tools.a2a.postprocessing.config import (
|
|
22
|
+
SubAgentDisplayConfig,
|
|
23
|
+
SubAgentResponseDisplayMode,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
# Test _wrap_with_html_block
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@pytest.mark.ai
|
|
30
|
+
def test_wrap_with_html_block__wraps_text__with_start_and_end_tags() -> None:
|
|
31
|
+
"""
|
|
32
|
+
Purpose: Verify text is wrapped with opening and closing tags with proper newlines.
|
|
33
|
+
Why this matters: Foundation for all HTML wrapping operations.
|
|
34
|
+
Setup summary: Provide text and tags, assert formatted output.
|
|
35
|
+
"""
|
|
36
|
+
# Arrange
|
|
37
|
+
text = "Hello World"
|
|
38
|
+
start_tag = "<div>"
|
|
39
|
+
end_tag = "</div>"
|
|
40
|
+
|
|
41
|
+
# Act
|
|
42
|
+
result = _wrap_text(text, start_tag, end_tag)
|
|
43
|
+
|
|
44
|
+
# Assert
|
|
45
|
+
assert result == "<div>\nHello World\n</div>"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@pytest.mark.ai
|
|
49
|
+
def test_wrap_with_html_block__strips_whitespace__from_text_and_tags() -> None:
|
|
50
|
+
"""
|
|
51
|
+
Purpose: Ensure whitespace is trimmed from text and tags before wrapping.
|
|
52
|
+
Why this matters: Prevents inconsistent HTML formatting.
|
|
53
|
+
Setup summary: Provide text with whitespace, assert trimmed output.
|
|
54
|
+
"""
|
|
55
|
+
# Arrange
|
|
56
|
+
text = " Hello World "
|
|
57
|
+
start_tag = " <div> "
|
|
58
|
+
end_tag = " </div> "
|
|
59
|
+
|
|
60
|
+
# Act
|
|
61
|
+
result = _wrap_text(text, start_tag, end_tag)
|
|
62
|
+
|
|
63
|
+
# Assert
|
|
64
|
+
assert result == "<div>\nHello World\n</div>"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@pytest.mark.ai
|
|
68
|
+
def test_wrap_with_html_block__handles_empty_tags__no_newlines() -> None:
|
|
69
|
+
"""
|
|
70
|
+
Purpose: Verify empty tags don't add newlines to output.
|
|
71
|
+
Why this matters: Allows flexible HTML composition without unwanted whitespace.
|
|
72
|
+
Setup summary: Provide empty tags, assert text without extra newlines.
|
|
73
|
+
"""
|
|
74
|
+
# Arrange
|
|
75
|
+
text = "Hello World"
|
|
76
|
+
start_tag = ""
|
|
77
|
+
end_tag = ""
|
|
78
|
+
|
|
79
|
+
# Act
|
|
80
|
+
result = _wrap_text(text, start_tag, end_tag)
|
|
81
|
+
|
|
82
|
+
# Assert
|
|
83
|
+
assert result == "Hello World"
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@pytest.mark.ai
|
|
87
|
+
def test_wrap_with_html_block__handles_mixed_empty_tags__partial_newlines() -> None:
|
|
88
|
+
"""
|
|
89
|
+
Purpose: Verify behavior with one empty tag and one non-empty tag.
|
|
90
|
+
Why this matters: Ensures consistent formatting in edge cases.
|
|
91
|
+
Setup summary: Provide start tag only, assert newline only after start.
|
|
92
|
+
"""
|
|
93
|
+
# Arrange
|
|
94
|
+
text = "Hello World"
|
|
95
|
+
start_tag = "<div>"
|
|
96
|
+
end_tag = ""
|
|
97
|
+
|
|
98
|
+
# Act
|
|
99
|
+
result = _wrap_text(text, start_tag, end_tag)
|
|
100
|
+
|
|
101
|
+
# Assert
|
|
102
|
+
assert result == "<div>\nHello World"
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# Test _join_html_blocks
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@pytest.mark.ai
|
|
109
|
+
def test_join_html_blocks__joins_multiple_blocks__with_newlines() -> None:
|
|
110
|
+
"""
|
|
111
|
+
Purpose: Verify multiple HTML blocks are joined with newline separators.
|
|
112
|
+
Why this matters: Creates properly formatted multi-line HTML output.
|
|
113
|
+
Setup summary: Provide multiple blocks, assert newline-joined output.
|
|
114
|
+
"""
|
|
115
|
+
# Arrange
|
|
116
|
+
block1 = "<div>Block 1</div>"
|
|
117
|
+
block2 = "<div>Block 2</div>"
|
|
118
|
+
block3 = "<div>Block 3</div>"
|
|
119
|
+
|
|
120
|
+
# Act
|
|
121
|
+
result = _join_text_blocks(block1, block2, block3)
|
|
122
|
+
|
|
123
|
+
# Assert
|
|
124
|
+
assert result == "<div>Block 1</div>\n<div>Block 2</div>\n<div>Block 3</div>"
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@pytest.mark.ai
|
|
128
|
+
def test_join_html_blocks__strips_whitespace__from_each_block() -> None:
|
|
129
|
+
"""
|
|
130
|
+
Purpose: Ensure whitespace is trimmed from each block before joining.
|
|
131
|
+
Why this matters: Prevents unwanted whitespace in combined HTML.
|
|
132
|
+
Setup summary: Provide blocks with whitespace, assert trimmed joined output.
|
|
133
|
+
"""
|
|
134
|
+
# Arrange
|
|
135
|
+
block1 = " <div>Block 1</div> "
|
|
136
|
+
block2 = " <div>Block 2</div> "
|
|
137
|
+
|
|
138
|
+
# Act
|
|
139
|
+
result = _join_text_blocks(block1, block2)
|
|
140
|
+
|
|
141
|
+
# Assert
|
|
142
|
+
assert result == "<div>Block 1</div>\n<div>Block 2</div>"
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@pytest.mark.ai
|
|
146
|
+
def test_join_html_blocks__handles_single_block__no_extra_newlines() -> None:
|
|
147
|
+
"""
|
|
148
|
+
Purpose: Verify single block is returned without modification.
|
|
149
|
+
Why this matters: Edge case handling for variable block counts.
|
|
150
|
+
Setup summary: Provide single block, assert unchanged output.
|
|
151
|
+
"""
|
|
152
|
+
# Arrange
|
|
153
|
+
block = "<div>Single Block</div>"
|
|
154
|
+
|
|
155
|
+
# Act
|
|
156
|
+
result = _join_text_blocks(block)
|
|
157
|
+
|
|
158
|
+
# Assert
|
|
159
|
+
assert result == "<div>Single Block</div>"
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
# Test _wrap_with_details_tag
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@pytest.mark.ai
|
|
166
|
+
def test_wrap_with_details_tag__wraps_open__without_summary() -> None:
|
|
167
|
+
"""
|
|
168
|
+
Purpose: Verify open details tag wrapping without summary element.
|
|
169
|
+
Why this matters: Creates collapsible HTML sections in open state.
|
|
170
|
+
Setup summary: Provide text and open mode, assert details open tag.
|
|
171
|
+
"""
|
|
172
|
+
# Arrange
|
|
173
|
+
text = "Content here"
|
|
174
|
+
|
|
175
|
+
# Act
|
|
176
|
+
result = _wrap_with_details_tag(text, mode="open", summary_name=None)
|
|
177
|
+
|
|
178
|
+
# Assert
|
|
179
|
+
assert result == "<details open>\nContent here\n</details>"
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
@pytest.mark.ai
|
|
183
|
+
def test_wrap_with_details_tag__wraps_closed__without_summary() -> None:
|
|
184
|
+
"""
|
|
185
|
+
Purpose: Verify closed details tag wrapping without summary element.
|
|
186
|
+
Why this matters: Creates collapsible HTML sections in closed state.
|
|
187
|
+
Setup summary: Provide text and closed mode, assert details tag.
|
|
188
|
+
"""
|
|
189
|
+
# Arrange
|
|
190
|
+
text = "Content here"
|
|
191
|
+
|
|
192
|
+
# Act
|
|
193
|
+
result = _wrap_with_details_tag(text, mode="closed", summary_name=None)
|
|
194
|
+
|
|
195
|
+
# Assert
|
|
196
|
+
assert result == "<details>\nContent here\n</details>"
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
@pytest.mark.ai
|
|
200
|
+
def test_wrap_with_details_tag__includes_summary__when_provided() -> None:
|
|
201
|
+
"""
|
|
202
|
+
Purpose: Verify summary element is added when summary_name provided.
|
|
203
|
+
Why this matters: Creates labeled collapsible sections.
|
|
204
|
+
Setup summary: Provide summary_name, assert summary tag before content.
|
|
205
|
+
"""
|
|
206
|
+
# Arrange
|
|
207
|
+
text = "Content here"
|
|
208
|
+
summary_name = "Click to expand"
|
|
209
|
+
|
|
210
|
+
# Act
|
|
211
|
+
result = _wrap_with_details_tag(text, mode="closed", summary_name=summary_name)
|
|
212
|
+
|
|
213
|
+
# Assert
|
|
214
|
+
expected = (
|
|
215
|
+
"<details>\n<summary>\nClick to expand\n</summary>\nContent here\n</details>"
|
|
216
|
+
)
|
|
217
|
+
assert result == expected
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
# Test border and style wrappers
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
@pytest.mark.ai
|
|
224
|
+
def test_wrap_with_block_border__adds_styled_div__with_border() -> None:
|
|
225
|
+
"""
|
|
226
|
+
Purpose: Verify block border wrapper adds div with border styling.
|
|
227
|
+
Why this matters: Visual separation of content blocks.
|
|
228
|
+
Setup summary: Provide text, assert div with border style.
|
|
229
|
+
"""
|
|
230
|
+
# Arrange
|
|
231
|
+
text = "Bordered content"
|
|
232
|
+
|
|
233
|
+
# Act
|
|
234
|
+
result = _wrap_with_block_border(text)
|
|
235
|
+
|
|
236
|
+
# Assert
|
|
237
|
+
assert result.startswith("<div style='overflow-y: auto; border: 1px solid #ccc;")
|
|
238
|
+
assert "Bordered content" in result
|
|
239
|
+
assert result.endswith("\n</div>")
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
@pytest.mark.ai
|
|
243
|
+
def test_wrap_with_quote_border__adds_styled_div__with_left_border() -> None:
|
|
244
|
+
"""
|
|
245
|
+
Purpose: Verify quote border wrapper adds div with left border styling.
|
|
246
|
+
Why this matters: Visual indication of quoted content.
|
|
247
|
+
Setup summary: Provide text, assert div with left border style.
|
|
248
|
+
"""
|
|
249
|
+
# Arrange
|
|
250
|
+
text = "Quoted content"
|
|
251
|
+
|
|
252
|
+
# Act
|
|
253
|
+
result = _wrap_with_quote_border(text)
|
|
254
|
+
|
|
255
|
+
# Assert
|
|
256
|
+
assert result.startswith(
|
|
257
|
+
"<div style='margin-left: 20px; border-left: 2px solid #ccc;"
|
|
258
|
+
)
|
|
259
|
+
assert "Quoted content" in result
|
|
260
|
+
assert result.endswith("\n</div>")
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
@pytest.mark.ai
|
|
264
|
+
def test_wrap_strong__wraps_text__with_strong_tags() -> None:
|
|
265
|
+
"""
|
|
266
|
+
Purpose: Verify text is wrapped with strong tags for bold formatting.
|
|
267
|
+
Why this matters: Text emphasis in HTML output.
|
|
268
|
+
Setup summary: Provide text, assert strong tag wrapping.
|
|
269
|
+
"""
|
|
270
|
+
# Arrange
|
|
271
|
+
text = "Bold text"
|
|
272
|
+
|
|
273
|
+
# Act
|
|
274
|
+
result = _wrap_strong(text)
|
|
275
|
+
|
|
276
|
+
# Assert
|
|
277
|
+
assert result == "<strong>\nBold text\n</strong>"
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
@pytest.mark.ai
|
|
281
|
+
def test_wrap_hidden_div__wraps_text__with_display_none() -> None:
|
|
282
|
+
"""
|
|
283
|
+
Purpose: Verify text is wrapped in hidden div with display:none style.
|
|
284
|
+
Why this matters: Hides content from visual display while keeping it in DOM.
|
|
285
|
+
Setup summary: Provide text, assert div with display:none.
|
|
286
|
+
"""
|
|
287
|
+
# Arrange
|
|
288
|
+
text = "Hidden content"
|
|
289
|
+
|
|
290
|
+
# Act
|
|
291
|
+
result = _wrap_hidden_div(text)
|
|
292
|
+
|
|
293
|
+
# Assert
|
|
294
|
+
assert result == '<div style="display: none;">\nHidden content\n</div>'
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
# Test _add_line_break
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
@pytest.mark.ai
|
|
301
|
+
def test_add_line_break__adds_both__by_default() -> None:
|
|
302
|
+
"""
|
|
303
|
+
Purpose: Verify line breaks are added before and after text by default.
|
|
304
|
+
Why this matters: Default spacing behavior for content.
|
|
305
|
+
Setup summary: Provide text with defaults, assert br tags both sides.
|
|
306
|
+
"""
|
|
307
|
+
# Arrange
|
|
308
|
+
text = "Text content"
|
|
309
|
+
|
|
310
|
+
# Act
|
|
311
|
+
result = _add_line_break(text)
|
|
312
|
+
|
|
313
|
+
# Assert
|
|
314
|
+
assert result == "<br>\nText content\n<br>"
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
@pytest.mark.ai
|
|
318
|
+
def test_add_line_break__adds_only_before__when_after_false() -> None:
|
|
319
|
+
"""
|
|
320
|
+
Purpose: Verify line break only before text when after=False.
|
|
321
|
+
Why this matters: Flexible spacing control.
|
|
322
|
+
Setup summary: Set after=False, assert br only before.
|
|
323
|
+
"""
|
|
324
|
+
# Arrange
|
|
325
|
+
text = "Text content"
|
|
326
|
+
|
|
327
|
+
# Act
|
|
328
|
+
result = _add_line_break(text, before=True, after=False)
|
|
329
|
+
|
|
330
|
+
# Assert
|
|
331
|
+
assert result == "<br>\nText content"
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
@pytest.mark.ai
|
|
335
|
+
def test_add_line_break__adds_only_after__when_before_false() -> None:
|
|
336
|
+
"""
|
|
337
|
+
Purpose: Verify line break only after text when before=False.
|
|
338
|
+
Why this matters: Flexible spacing control.
|
|
339
|
+
Setup summary: Set before=False, assert br only after.
|
|
340
|
+
"""
|
|
341
|
+
# Arrange
|
|
342
|
+
text = "Text content"
|
|
343
|
+
|
|
344
|
+
# Act
|
|
345
|
+
result = _add_line_break(text, before=False, after=True)
|
|
346
|
+
|
|
347
|
+
# Assert
|
|
348
|
+
assert result == "Text content\n<br>"
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
@pytest.mark.ai
|
|
352
|
+
def test_add_line_break__adds_none__when_both_false() -> None:
|
|
353
|
+
"""
|
|
354
|
+
Purpose: Verify no line breaks added when both flags false.
|
|
355
|
+
Why this matters: Complete control over spacing.
|
|
356
|
+
Setup summary: Set both flags false, assert no br tags.
|
|
357
|
+
"""
|
|
358
|
+
# Arrange
|
|
359
|
+
text = "Text content"
|
|
360
|
+
|
|
361
|
+
# Act
|
|
362
|
+
result = _add_line_break(text, before=False, after=False)
|
|
363
|
+
|
|
364
|
+
# Assert
|
|
365
|
+
assert result == "Text content"
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
# Test _get_display_template
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
@pytest.mark.ai
|
|
372
|
+
def test_get_display_template__returns_empty__when_hidden_mode() -> None:
|
|
373
|
+
"""
|
|
374
|
+
Purpose: Verify empty string returned for HIDDEN display mode.
|
|
375
|
+
Why this matters: Content should not be displayed when hidden.
|
|
376
|
+
Setup summary: Set mode to HIDDEN, assert empty string.
|
|
377
|
+
"""
|
|
378
|
+
# Arrange
|
|
379
|
+
mode = SubAgentResponseDisplayMode.HIDDEN
|
|
380
|
+
|
|
381
|
+
# Act
|
|
382
|
+
result = _get_display_template(
|
|
383
|
+
mode=mode,
|
|
384
|
+
add_quote_border=False,
|
|
385
|
+
add_block_border=False,
|
|
386
|
+
display_title_template="Answer from <strong>{}</strong>",
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
# Assert
|
|
390
|
+
assert result == ""
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
@pytest.mark.ai
|
|
394
|
+
def test_get_display_template__includes_placeholders__for_all_modes() -> None:
|
|
395
|
+
"""
|
|
396
|
+
Purpose: Verify all required placeholders present in non-hidden modes.
|
|
397
|
+
Why this matters: Template must support variable substitution.
|
|
398
|
+
Setup summary: Test each display mode, assert placeholders exist.
|
|
399
|
+
"""
|
|
400
|
+
# Arrange
|
|
401
|
+
modes = [
|
|
402
|
+
SubAgentResponseDisplayMode.PLAIN,
|
|
403
|
+
SubAgentResponseDisplayMode.DETAILS_OPEN,
|
|
404
|
+
SubAgentResponseDisplayMode.DETAILS_CLOSED,
|
|
405
|
+
]
|
|
406
|
+
|
|
407
|
+
for mode in modes:
|
|
408
|
+
# Act
|
|
409
|
+
result = _get_display_template(
|
|
410
|
+
mode=mode,
|
|
411
|
+
add_quote_border=False,
|
|
412
|
+
add_block_border=False,
|
|
413
|
+
display_title_template="Answer from <strong>{}</strong>",
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
# Assert
|
|
417
|
+
assert "{assistant_id}" in result, f"assistant_id missing in {mode}"
|
|
418
|
+
assert "{answer}" in result, f"answer missing in {mode}"
|
|
419
|
+
assert "{display_name}" in result, f"display_name missing in {mode}"
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
@pytest.mark.ai
|
|
423
|
+
def test_get_display_template__wraps_assistant_id__as_hidden_div() -> None:
|
|
424
|
+
"""
|
|
425
|
+
Purpose: Verify assistant_id is always wrapped in hidden div.
|
|
426
|
+
Why this matters: Assistant ID should not be visible to users.
|
|
427
|
+
Setup summary: Check template contains hidden div with assistant_id.
|
|
428
|
+
"""
|
|
429
|
+
# Arrange
|
|
430
|
+
mode = SubAgentResponseDisplayMode.PLAIN
|
|
431
|
+
|
|
432
|
+
# Act
|
|
433
|
+
result = _get_display_template(
|
|
434
|
+
mode=mode,
|
|
435
|
+
add_quote_border=False,
|
|
436
|
+
add_block_border=False,
|
|
437
|
+
display_title_template="Answer from <strong>{}</strong>",
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
# Assert
|
|
441
|
+
assert '<div style="display: none;">' in result
|
|
442
|
+
assert "{assistant_id}" in result
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
@pytest.mark.ai
|
|
446
|
+
def test_get_display_template__wraps_display_name__as_strong() -> None:
|
|
447
|
+
"""
|
|
448
|
+
Purpose: Verify display_name is wrapped in strong tags for emphasis.
|
|
449
|
+
Why this matters: Display name should be bold for visibility.
|
|
450
|
+
Setup summary: Check template contains strong tags with display_name.
|
|
451
|
+
"""
|
|
452
|
+
# Arrange
|
|
453
|
+
mode = SubAgentResponseDisplayMode.PLAIN
|
|
454
|
+
|
|
455
|
+
# Act
|
|
456
|
+
result = _get_display_template(
|
|
457
|
+
mode=mode,
|
|
458
|
+
add_quote_border=False,
|
|
459
|
+
add_block_border=False,
|
|
460
|
+
display_title_template="Answer from <strong>{}</strong>",
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
# Assert
|
|
464
|
+
assert "<strong>" in result
|
|
465
|
+
assert "{display_name}" in result
|
|
466
|
+
assert "</strong>" in result
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
@pytest.mark.ai
|
|
470
|
+
def test_get_display_template__adds_details_open__when_details_open_mode() -> None:
|
|
471
|
+
"""
|
|
472
|
+
Purpose: Verify details open tags present in DETAILS_OPEN mode.
|
|
473
|
+
Why this matters: Creates expandable section in open state.
|
|
474
|
+
Setup summary: Set DETAILS_OPEN mode, assert details open tags.
|
|
475
|
+
"""
|
|
476
|
+
# Arrange
|
|
477
|
+
mode = SubAgentResponseDisplayMode.DETAILS_OPEN
|
|
478
|
+
|
|
479
|
+
# Act
|
|
480
|
+
result = _get_display_template(
|
|
481
|
+
mode=mode,
|
|
482
|
+
add_quote_border=False,
|
|
483
|
+
add_block_border=False,
|
|
484
|
+
display_title_template="Answer from <strong>{}</strong>",
|
|
485
|
+
)
|
|
486
|
+
|
|
487
|
+
# Assert
|
|
488
|
+
assert "<details open>" in result
|
|
489
|
+
assert "</details>" in result
|
|
490
|
+
assert "<summary>" in result
|
|
491
|
+
assert "</summary>" in result
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
@pytest.mark.ai
|
|
495
|
+
def test_get_display_template__adds_details_closed__when_details_closed_mode() -> None:
|
|
496
|
+
"""
|
|
497
|
+
Purpose: Verify details tags present without open in DETAILS_CLOSED mode.
|
|
498
|
+
Why this matters: Creates expandable section in closed state.
|
|
499
|
+
Setup summary: Set DETAILS_CLOSED mode, assert details tags without open.
|
|
500
|
+
"""
|
|
501
|
+
# Arrange
|
|
502
|
+
mode = SubAgentResponseDisplayMode.DETAILS_CLOSED
|
|
503
|
+
|
|
504
|
+
# Act
|
|
505
|
+
result = _get_display_template(
|
|
506
|
+
mode=mode,
|
|
507
|
+
add_quote_border=False,
|
|
508
|
+
add_block_border=False,
|
|
509
|
+
display_title_template="Answer from <strong>{}</strong>",
|
|
510
|
+
)
|
|
511
|
+
|
|
512
|
+
# Assert
|
|
513
|
+
assert "<details>" in result
|
|
514
|
+
assert "<details open>" not in result
|
|
515
|
+
assert "</details>" in result
|
|
516
|
+
assert "<summary>" in result
|
|
517
|
+
assert "</summary>" in result
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
@pytest.mark.ai
|
|
521
|
+
def test_get_display_template__adds_line_break_after_name__in_plain_mode() -> None:
|
|
522
|
+
"""
|
|
523
|
+
Purpose: Verify line break added after display name in PLAIN mode.
|
|
524
|
+
Why this matters: Separates display name from content visually.
|
|
525
|
+
Setup summary: Set PLAIN mode, assert br tag after display_name.
|
|
526
|
+
"""
|
|
527
|
+
# Arrange
|
|
528
|
+
mode = SubAgentResponseDisplayMode.PLAIN
|
|
529
|
+
|
|
530
|
+
# Act
|
|
531
|
+
result = _get_display_template(
|
|
532
|
+
mode=mode,
|
|
533
|
+
add_quote_border=False,
|
|
534
|
+
add_block_border=False,
|
|
535
|
+
display_title_template="Answer from <strong>{}</strong>",
|
|
536
|
+
)
|
|
537
|
+
|
|
538
|
+
# Assert
|
|
539
|
+
# The display_name should be wrapped with line break (before=False, after=True)
|
|
540
|
+
assert "<br>" in result
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
@pytest.mark.ai
|
|
544
|
+
def test_get_display_template__adds_quote_border__when_flag_true() -> None:
|
|
545
|
+
"""
|
|
546
|
+
Purpose: Verify quote border styling added when add_quote_border=True.
|
|
547
|
+
Why this matters: Visual indication of quoted content.
|
|
548
|
+
Setup summary: Set add_quote_border=True, assert quote border style.
|
|
549
|
+
"""
|
|
550
|
+
# Arrange
|
|
551
|
+
mode = SubAgentResponseDisplayMode.PLAIN
|
|
552
|
+
|
|
553
|
+
# Act
|
|
554
|
+
result = _get_display_template(
|
|
555
|
+
mode=mode,
|
|
556
|
+
add_quote_border=True,
|
|
557
|
+
add_block_border=False,
|
|
558
|
+
display_title_template="Answer from <strong>{}</strong>",
|
|
559
|
+
)
|
|
560
|
+
|
|
561
|
+
# Assert
|
|
562
|
+
assert "margin-left: 20px" in result
|
|
563
|
+
assert "border-left: 2px solid #ccc" in result
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
@pytest.mark.ai
|
|
567
|
+
def test_get_display_template__adds_block_border__when_flag_true() -> None:
|
|
568
|
+
"""
|
|
569
|
+
Purpose: Verify block border styling added when add_block_border=True.
|
|
570
|
+
Why this matters: Visual separation of content blocks.
|
|
571
|
+
Setup summary: Set add_block_border=True, assert block border style.
|
|
572
|
+
"""
|
|
573
|
+
# Arrange
|
|
574
|
+
mode = SubAgentResponseDisplayMode.PLAIN
|
|
575
|
+
|
|
576
|
+
# Act
|
|
577
|
+
result = _get_display_template(
|
|
578
|
+
mode=mode,
|
|
579
|
+
add_quote_border=False,
|
|
580
|
+
add_block_border=True,
|
|
581
|
+
display_title_template="Answer from <strong>{}</strong>",
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
# Assert
|
|
585
|
+
assert "overflow-y: auto" in result
|
|
586
|
+
assert "border: 1px solid #ccc" in result
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
@pytest.mark.ai
|
|
590
|
+
def test_get_display_template__adds_both_borders__when_both_flags_true() -> None:
|
|
591
|
+
"""
|
|
592
|
+
Purpose: Verify both border styles added when both flags true.
|
|
593
|
+
Why this matters: Support for combining visual styles.
|
|
594
|
+
Setup summary: Set both border flags true, assert both styles present.
|
|
595
|
+
"""
|
|
596
|
+
# Arrange
|
|
597
|
+
mode = SubAgentResponseDisplayMode.PLAIN
|
|
598
|
+
|
|
599
|
+
# Act
|
|
600
|
+
result = _get_display_template(
|
|
601
|
+
mode=mode,
|
|
602
|
+
add_quote_border=True,
|
|
603
|
+
add_block_border=True,
|
|
604
|
+
display_title_template="Answer from <strong>{}</strong>",
|
|
605
|
+
)
|
|
606
|
+
|
|
607
|
+
# Assert
|
|
608
|
+
# Quote border should be inside block border
|
|
609
|
+
assert "overflow-y: auto" in result
|
|
610
|
+
assert "border: 1px solid #ccc" in result
|
|
611
|
+
assert "margin-left: 20px" in result
|
|
612
|
+
assert "border-left: 2px solid #ccc" in result
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
# Test _get_display_removal_re (regex pattern generation)
|
|
616
|
+
|
|
617
|
+
|
|
618
|
+
@pytest.mark.ai
|
|
619
|
+
def test_get_display_removal_re__returns_pattern__for_plain_mode() -> None:
|
|
620
|
+
"""
|
|
621
|
+
Purpose: Verify regex pattern is created for PLAIN display mode.
|
|
622
|
+
Why this matters: Enables removal of displayed content from text.
|
|
623
|
+
Setup summary: Generate pattern for PLAIN mode, assert Pattern type.
|
|
624
|
+
"""
|
|
625
|
+
# Arrange
|
|
626
|
+
assistant_id = "test-assistant-123"
|
|
627
|
+
mode = SubAgentResponseDisplayMode.PLAIN
|
|
628
|
+
|
|
629
|
+
# Act
|
|
630
|
+
result = _get_display_removal_re(
|
|
631
|
+
assistant_id=assistant_id,
|
|
632
|
+
mode=mode,
|
|
633
|
+
add_quote_border=False,
|
|
634
|
+
add_block_border=False,
|
|
635
|
+
display_title_template="Answer from <strong>{}</strong>",
|
|
636
|
+
)
|
|
637
|
+
|
|
638
|
+
# Assert
|
|
639
|
+
assert isinstance(result, re.Pattern)
|
|
640
|
+
assert result.flags & re.DOTALL # Should have DOTALL flag
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+
@pytest.mark.ai
|
|
644
|
+
def test_get_display_removal_re__returns_pattern__for_details_modes() -> None:
|
|
645
|
+
"""
|
|
646
|
+
Purpose: Verify regex patterns created for both DETAILS modes.
|
|
647
|
+
Why this matters: Ensures removal works for collapsible sections.
|
|
648
|
+
Setup summary: Generate patterns for DETAILS modes, assert Pattern types.
|
|
649
|
+
"""
|
|
650
|
+
# Arrange
|
|
651
|
+
assistant_id = "test-assistant-123"
|
|
652
|
+
modes = [
|
|
653
|
+
SubAgentResponseDisplayMode.DETAILS_OPEN,
|
|
654
|
+
SubAgentResponseDisplayMode.DETAILS_CLOSED,
|
|
655
|
+
]
|
|
656
|
+
|
|
657
|
+
for mode in modes:
|
|
658
|
+
# Act
|
|
659
|
+
result = _get_display_removal_re(
|
|
660
|
+
assistant_id=assistant_id,
|
|
661
|
+
mode=mode,
|
|
662
|
+
add_quote_border=False,
|
|
663
|
+
add_block_border=False,
|
|
664
|
+
display_title_template="Answer from <strong>{}</strong>",
|
|
665
|
+
)
|
|
666
|
+
|
|
667
|
+
# Assert
|
|
668
|
+
assert isinstance(result, re.Pattern), f"Pattern not created for {mode}"
|
|
669
|
+
assert result.flags & re.DOTALL, f"DOTALL flag missing for {mode}"
|
|
670
|
+
|
|
671
|
+
|
|
672
|
+
@pytest.mark.ai
|
|
673
|
+
def test_get_display_removal_re__includes_assistant_id__in_pattern() -> None:
|
|
674
|
+
"""
|
|
675
|
+
Purpose: Verify assistant_id is embedded in regex pattern.
|
|
676
|
+
Why this matters: Ensures only specific assistant's content is removed.
|
|
677
|
+
Setup summary: Generate pattern with assistant_id, assert ID in pattern.
|
|
678
|
+
"""
|
|
679
|
+
# Arrange
|
|
680
|
+
assistant_id = "unique-assistant-xyz"
|
|
681
|
+
mode = SubAgentResponseDisplayMode.PLAIN
|
|
682
|
+
|
|
683
|
+
# Act
|
|
684
|
+
result = _get_display_removal_re(
|
|
685
|
+
assistant_id=assistant_id,
|
|
686
|
+
mode=mode,
|
|
687
|
+
add_quote_border=False,
|
|
688
|
+
add_block_border=False,
|
|
689
|
+
display_title_template="Answer from <strong>{}</strong>",
|
|
690
|
+
)
|
|
691
|
+
|
|
692
|
+
# Assert
|
|
693
|
+
assert re.escape(assistant_id) in result.pattern
|
|
694
|
+
|
|
695
|
+
|
|
696
|
+
@pytest.mark.ai
|
|
697
|
+
def test_get_display_removal_re__has_capture_groups__for_answer_and_name() -> None:
|
|
698
|
+
"""
|
|
699
|
+
Purpose: Verify regex pattern includes capture groups for dynamic content.
|
|
700
|
+
Why this matters: Allows flexible matching of variable content.
|
|
701
|
+
Setup summary: Check pattern contains regex capture groups.
|
|
702
|
+
"""
|
|
703
|
+
# Arrange
|
|
704
|
+
assistant_id = "test-assistant"
|
|
705
|
+
mode = SubAgentResponseDisplayMode.PLAIN
|
|
706
|
+
|
|
707
|
+
# Act
|
|
708
|
+
result = _get_display_removal_re(
|
|
709
|
+
assistant_id=assistant_id,
|
|
710
|
+
mode=mode,
|
|
711
|
+
add_quote_border=False,
|
|
712
|
+
add_block_border=False,
|
|
713
|
+
display_title_template="Answer from <strong>{}</strong>",
|
|
714
|
+
)
|
|
715
|
+
|
|
716
|
+
# Assert
|
|
717
|
+
# Pattern should contain (.*?) for capturing groups
|
|
718
|
+
assert "(.*?)" in result.pattern
|
|
719
|
+
|
|
720
|
+
|
|
721
|
+
# Test _build_sub_agent_answer_display
|
|
722
|
+
|
|
723
|
+
|
|
724
|
+
@pytest.mark.ai
|
|
725
|
+
def test_build_sub_agent_answer_display__creates_html__for_plain_mode() -> None:
|
|
726
|
+
"""
|
|
727
|
+
Purpose: Verify HTML output is generated for PLAIN display mode.
|
|
728
|
+
Why this matters: Core functionality for displaying agent responses.
|
|
729
|
+
Setup summary: Build display with PLAIN mode, assert HTML structure.
|
|
730
|
+
"""
|
|
731
|
+
# Arrange
|
|
732
|
+
display_name = "Test Agent"
|
|
733
|
+
answer = "This is the answer"
|
|
734
|
+
assistant_id = "agent-123"
|
|
735
|
+
config = SubAgentDisplayConfig(
|
|
736
|
+
mode=SubAgentResponseDisplayMode.PLAIN,
|
|
737
|
+
add_quote_border=False,
|
|
738
|
+
add_block_border=False,
|
|
739
|
+
display_title_template="Answer from <strong>{}</strong>",
|
|
740
|
+
)
|
|
741
|
+
|
|
742
|
+
# Act
|
|
743
|
+
result = get_sub_agent_answer_display(
|
|
744
|
+
display_name=display_name,
|
|
745
|
+
display_config=config,
|
|
746
|
+
answer=answer,
|
|
747
|
+
assistant_id=assistant_id,
|
|
748
|
+
)
|
|
749
|
+
|
|
750
|
+
# Assert
|
|
751
|
+
assert "Test Agent" in result
|
|
752
|
+
assert "This is the answer" in result
|
|
753
|
+
assert "agent-123" in result
|
|
754
|
+
assert '<div style="display: none;">' in result
|
|
755
|
+
assert "<strong>" in result
|
|
756
|
+
|
|
757
|
+
|
|
758
|
+
@pytest.mark.ai
|
|
759
|
+
def test_build_sub_agent_answer_display__creates_details__for_details_open() -> None:
|
|
760
|
+
"""
|
|
761
|
+
Purpose: Verify details HTML with open attribute for DETAILS_OPEN mode.
|
|
762
|
+
Why this matters: Creates expandable sections in open state.
|
|
763
|
+
Setup summary: Build display with DETAILS_OPEN, assert details open tags.
|
|
764
|
+
"""
|
|
765
|
+
# Arrange
|
|
766
|
+
display_name = "Test Agent"
|
|
767
|
+
answer = "This is the answer"
|
|
768
|
+
assistant_id = "agent-123"
|
|
769
|
+
config = SubAgentDisplayConfig(
|
|
770
|
+
mode=SubAgentResponseDisplayMode.DETAILS_OPEN,
|
|
771
|
+
add_quote_border=False,
|
|
772
|
+
add_block_border=False,
|
|
773
|
+
display_title_template="Answer from <strong>{}</strong>",
|
|
774
|
+
)
|
|
775
|
+
|
|
776
|
+
# Act
|
|
777
|
+
result = get_sub_agent_answer_display(
|
|
778
|
+
display_name=display_name,
|
|
779
|
+
display_config=config,
|
|
780
|
+
answer=answer,
|
|
781
|
+
assistant_id=assistant_id,
|
|
782
|
+
)
|
|
783
|
+
|
|
784
|
+
# Assert
|
|
785
|
+
assert "<details open>" in result
|
|
786
|
+
assert "</details>" in result
|
|
787
|
+
assert "<summary>" in result
|
|
788
|
+
assert "Test Agent" in result
|
|
789
|
+
assert "This is the answer" in result
|
|
790
|
+
|
|
791
|
+
|
|
792
|
+
@pytest.mark.ai
|
|
793
|
+
def test_build_sub_agent_answer_display__creates_details__for_details_closed() -> None:
|
|
794
|
+
"""
|
|
795
|
+
Purpose: Verify details HTML without open for DETAILS_CLOSED mode.
|
|
796
|
+
Why this matters: Creates expandable sections in closed state.
|
|
797
|
+
Setup summary: Build display with DETAILS_CLOSED, assert details tags.
|
|
798
|
+
"""
|
|
799
|
+
# Arrange
|
|
800
|
+
display_name = "Test Agent"
|
|
801
|
+
answer = "This is the answer"
|
|
802
|
+
assistant_id = "agent-123"
|
|
803
|
+
config = SubAgentDisplayConfig(
|
|
804
|
+
mode=SubAgentResponseDisplayMode.DETAILS_CLOSED,
|
|
805
|
+
add_quote_border=False,
|
|
806
|
+
add_block_border=False,
|
|
807
|
+
display_title_template="Answer from <strong>{}</strong>",
|
|
808
|
+
)
|
|
809
|
+
|
|
810
|
+
# Act
|
|
811
|
+
result = get_sub_agent_answer_display(
|
|
812
|
+
display_name=display_name,
|
|
813
|
+
display_config=config,
|
|
814
|
+
answer=answer,
|
|
815
|
+
assistant_id=assistant_id,
|
|
816
|
+
)
|
|
817
|
+
|
|
818
|
+
# Assert
|
|
819
|
+
assert "<details>" in result
|
|
820
|
+
assert "<details open>" not in result
|
|
821
|
+
assert "</details>" in result
|
|
822
|
+
assert "<summary>" in result
|
|
823
|
+
assert "Test Agent" in result
|
|
824
|
+
|
|
825
|
+
|
|
826
|
+
@pytest.mark.ai
|
|
827
|
+
def test_build_sub_agent_answer_display__returns_empty__for_hidden_mode() -> None:
|
|
828
|
+
"""
|
|
829
|
+
Purpose: Verify empty string returned for HIDDEN display mode.
|
|
830
|
+
Why this matters: Hidden content should not generate any HTML.
|
|
831
|
+
Setup summary: Build display with HIDDEN mode, assert empty string.
|
|
832
|
+
"""
|
|
833
|
+
# Arrange
|
|
834
|
+
display_name = "Test Agent"
|
|
835
|
+
answer = "This is the answer"
|
|
836
|
+
assistant_id = "agent-123"
|
|
837
|
+
config = SubAgentDisplayConfig(
|
|
838
|
+
mode=SubAgentResponseDisplayMode.HIDDEN,
|
|
839
|
+
add_quote_border=False,
|
|
840
|
+
add_block_border=False,
|
|
841
|
+
display_title_template="Answer from <strong>{}</strong>",
|
|
842
|
+
)
|
|
843
|
+
|
|
844
|
+
# Act
|
|
845
|
+
result = get_sub_agent_answer_display(
|
|
846
|
+
display_name=display_name,
|
|
847
|
+
display_config=config,
|
|
848
|
+
answer=answer,
|
|
849
|
+
assistant_id=assistant_id,
|
|
850
|
+
)
|
|
851
|
+
|
|
852
|
+
# Assert
|
|
853
|
+
assert result == ""
|
|
854
|
+
|
|
855
|
+
|
|
856
|
+
# Test _remove_sub_agent_answer_from_text (regex removal logic)
|
|
857
|
+
|
|
858
|
+
|
|
859
|
+
@pytest.mark.ai
|
|
860
|
+
def test_remove_sub_agent_answer__removes_plain_display__from_text() -> None:
|
|
861
|
+
"""
|
|
862
|
+
Purpose: Verify PLAIN mode display content is removed from text via regex.
|
|
863
|
+
Why this matters: Core removal functionality for cleaning history.
|
|
864
|
+
Setup summary: Build display, embed in text, remove via regex, assert removal.
|
|
865
|
+
"""
|
|
866
|
+
# Arrange
|
|
867
|
+
assistant_id = "agent-123"
|
|
868
|
+
display_name = "Test Agent"
|
|
869
|
+
answer = "This is the answer"
|
|
870
|
+
config = SubAgentDisplayConfig(
|
|
871
|
+
mode=SubAgentResponseDisplayMode.PLAIN,
|
|
872
|
+
add_quote_border=False,
|
|
873
|
+
add_block_border=False,
|
|
874
|
+
display_title_template="Answer from <strong>{}</strong>",
|
|
875
|
+
)
|
|
876
|
+
|
|
877
|
+
# Build the display
|
|
878
|
+
display = get_sub_agent_answer_display(
|
|
879
|
+
display_name=display_name,
|
|
880
|
+
display_config=config,
|
|
881
|
+
answer=answer,
|
|
882
|
+
assistant_id=assistant_id,
|
|
883
|
+
)
|
|
884
|
+
|
|
885
|
+
text_with_display = f"Before content\n{display}\nAfter content"
|
|
886
|
+
|
|
887
|
+
# Act
|
|
888
|
+
result = remove_sub_agent_answer_from_text(
|
|
889
|
+
display_config=config,
|
|
890
|
+
text=text_with_display,
|
|
891
|
+
assistant_id=assistant_id,
|
|
892
|
+
)
|
|
893
|
+
|
|
894
|
+
# Assert
|
|
895
|
+
assert "This is the answer" not in result
|
|
896
|
+
assert "Test Agent" not in result
|
|
897
|
+
assert "Before content" in result
|
|
898
|
+
assert "After content" in result
|
|
899
|
+
|
|
900
|
+
|
|
901
|
+
@pytest.mark.ai
|
|
902
|
+
def test_remove_sub_agent_answer__removes_details_open__from_text() -> None:
|
|
903
|
+
"""
|
|
904
|
+
Purpose: Verify DETAILS_OPEN mode display is removed via regex.
|
|
905
|
+
Why this matters: Ensures removal works for collapsible open sections.
|
|
906
|
+
Setup summary: Build details open display, embed and remove, assert removal.
|
|
907
|
+
"""
|
|
908
|
+
# Arrange
|
|
909
|
+
assistant_id = "agent-456"
|
|
910
|
+
display_name = "Research Agent"
|
|
911
|
+
answer = "Research findings here"
|
|
912
|
+
config = SubAgentDisplayConfig(
|
|
913
|
+
mode=SubAgentResponseDisplayMode.DETAILS_OPEN,
|
|
914
|
+
add_quote_border=False,
|
|
915
|
+
add_block_border=False,
|
|
916
|
+
display_title_template="Answer from <strong>{}</strong>",
|
|
917
|
+
)
|
|
918
|
+
|
|
919
|
+
# Build the display
|
|
920
|
+
display = get_sub_agent_answer_display(
|
|
921
|
+
display_name=display_name,
|
|
922
|
+
display_config=config,
|
|
923
|
+
answer=answer,
|
|
924
|
+
assistant_id=assistant_id,
|
|
925
|
+
)
|
|
926
|
+
|
|
927
|
+
text_with_display = f"Start\n{display}\nEnd"
|
|
928
|
+
|
|
929
|
+
# Act
|
|
930
|
+
result = remove_sub_agent_answer_from_text(
|
|
931
|
+
display_config=config,
|
|
932
|
+
text=text_with_display,
|
|
933
|
+
assistant_id=assistant_id,
|
|
934
|
+
)
|
|
935
|
+
|
|
936
|
+
# Assert
|
|
937
|
+
assert "Research findings here" not in result
|
|
938
|
+
assert "Research Agent" not in result
|
|
939
|
+
assert "<details open>" not in result
|
|
940
|
+
assert "Start" in result
|
|
941
|
+
assert "End" in result
|
|
942
|
+
|
|
943
|
+
|
|
944
|
+
@pytest.mark.ai
|
|
945
|
+
def test_remove_sub_agent_answer__removes_details_closed__from_text() -> None:
|
|
946
|
+
"""
|
|
947
|
+
Purpose: Verify DETAILS_CLOSED mode display is removed via regex.
|
|
948
|
+
Why this matters: Ensures removal works for collapsible closed sections.
|
|
949
|
+
Setup summary: Build details closed display, embed and remove, assert removal.
|
|
950
|
+
"""
|
|
951
|
+
# Arrange
|
|
952
|
+
assistant_id = "agent-789"
|
|
953
|
+
display_name = "Analysis Agent"
|
|
954
|
+
answer = "Analysis results"
|
|
955
|
+
config = SubAgentDisplayConfig(
|
|
956
|
+
mode=SubAgentResponseDisplayMode.DETAILS_CLOSED,
|
|
957
|
+
add_quote_border=False,
|
|
958
|
+
add_block_border=False,
|
|
959
|
+
display_title_template="Answer from <strong>{}</strong>",
|
|
960
|
+
)
|
|
961
|
+
|
|
962
|
+
# Build the display
|
|
963
|
+
display = get_sub_agent_answer_display(
|
|
964
|
+
display_name=display_name,
|
|
965
|
+
display_config=config,
|
|
966
|
+
answer=answer,
|
|
967
|
+
assistant_id=assistant_id,
|
|
968
|
+
)
|
|
969
|
+
|
|
970
|
+
text_with_display = f"Beginning\n{display}\nEnding"
|
|
971
|
+
|
|
972
|
+
# Act
|
|
973
|
+
result = remove_sub_agent_answer_from_text(
|
|
974
|
+
display_config=config,
|
|
975
|
+
text=text_with_display,
|
|
976
|
+
assistant_id=assistant_id,
|
|
977
|
+
)
|
|
978
|
+
|
|
979
|
+
# Assert
|
|
980
|
+
assert "Analysis results" not in result
|
|
981
|
+
assert "Analysis Agent" not in result
|
|
982
|
+
assert "<details>" not in result
|
|
983
|
+
assert "Beginning" in result
|
|
984
|
+
assert "Ending" in result
|
|
985
|
+
|
|
986
|
+
|
|
987
|
+
@pytest.mark.ai
|
|
988
|
+
def test_remove_sub_agent_answer__removes_with_quote_border__from_text() -> None:
|
|
989
|
+
"""
|
|
990
|
+
Purpose: Verify removal works when quote border styling is present.
|
|
991
|
+
Why this matters: Regex must handle additional div wrapper.
|
|
992
|
+
Setup summary: Build display with quote border, remove, assert successful removal.
|
|
993
|
+
"""
|
|
994
|
+
# Arrange
|
|
995
|
+
assistant_id = "agent-quote"
|
|
996
|
+
display_name = "Quote Agent"
|
|
997
|
+
answer = "Quoted answer"
|
|
998
|
+
config = SubAgentDisplayConfig(
|
|
999
|
+
mode=SubAgentResponseDisplayMode.PLAIN,
|
|
1000
|
+
add_quote_border=True,
|
|
1001
|
+
add_block_border=False,
|
|
1002
|
+
display_title_template="Answer from <strong>{}</strong>",
|
|
1003
|
+
)
|
|
1004
|
+
|
|
1005
|
+
# Build with quote border
|
|
1006
|
+
display = get_sub_agent_answer_display(
|
|
1007
|
+
display_name=display_name,
|
|
1008
|
+
display_config=config,
|
|
1009
|
+
answer=answer,
|
|
1010
|
+
assistant_id=assistant_id,
|
|
1011
|
+
)
|
|
1012
|
+
|
|
1013
|
+
text_with_display = f"Before\n{display}\nAfter"
|
|
1014
|
+
|
|
1015
|
+
# Act
|
|
1016
|
+
result = remove_sub_agent_answer_from_text(
|
|
1017
|
+
display_config=config,
|
|
1018
|
+
text=text_with_display,
|
|
1019
|
+
assistant_id=assistant_id,
|
|
1020
|
+
)
|
|
1021
|
+
|
|
1022
|
+
# Assert
|
|
1023
|
+
assert "Quoted answer" not in result
|
|
1024
|
+
assert "Quote Agent" not in result
|
|
1025
|
+
assert "margin-left: 20px" not in result
|
|
1026
|
+
assert "Before" in result
|
|
1027
|
+
assert "After" in result
|
|
1028
|
+
|
|
1029
|
+
|
|
1030
|
+
@pytest.mark.ai
|
|
1031
|
+
def test_remove_sub_agent_answer__removes_with_block_border__from_text() -> None:
|
|
1032
|
+
"""
|
|
1033
|
+
Purpose: Verify removal works when block border styling is present.
|
|
1034
|
+
Why this matters: Regex must handle block border div wrapper.
|
|
1035
|
+
Setup summary: Build display with block border, remove, assert successful removal.
|
|
1036
|
+
"""
|
|
1037
|
+
# Arrange
|
|
1038
|
+
assistant_id = "agent-block"
|
|
1039
|
+
display_name = "Block Agent"
|
|
1040
|
+
answer = "Block answer"
|
|
1041
|
+
config = SubAgentDisplayConfig(
|
|
1042
|
+
mode=SubAgentResponseDisplayMode.PLAIN,
|
|
1043
|
+
add_quote_border=False,
|
|
1044
|
+
add_block_border=True,
|
|
1045
|
+
display_title_template="Answer from <strong>{}</strong>",
|
|
1046
|
+
)
|
|
1047
|
+
|
|
1048
|
+
# Build with block border
|
|
1049
|
+
display = get_sub_agent_answer_display(
|
|
1050
|
+
display_name=display_name,
|
|
1051
|
+
display_config=config,
|
|
1052
|
+
answer=answer,
|
|
1053
|
+
assistant_id=assistant_id,
|
|
1054
|
+
)
|
|
1055
|
+
|
|
1056
|
+
text_with_display = f"Start\n{display}\nFinish"
|
|
1057
|
+
|
|
1058
|
+
# Act
|
|
1059
|
+
result = remove_sub_agent_answer_from_text(
|
|
1060
|
+
display_config=config,
|
|
1061
|
+
text=text_with_display,
|
|
1062
|
+
assistant_id=assistant_id,
|
|
1063
|
+
)
|
|
1064
|
+
|
|
1065
|
+
# Assert
|
|
1066
|
+
assert "Block answer" not in result
|
|
1067
|
+
assert "Block Agent" not in result
|
|
1068
|
+
assert "overflow-y: auto" not in result
|
|
1069
|
+
assert "Start" in result
|
|
1070
|
+
assert "Finish" in result
|
|
1071
|
+
|
|
1072
|
+
|
|
1073
|
+
@pytest.mark.ai
|
|
1074
|
+
def test_remove_sub_agent_answer__removes_with_both_borders__from_text() -> None:
|
|
1075
|
+
"""
|
|
1076
|
+
Purpose: Verify removal works with both quote and block borders.
|
|
1077
|
+
Why this matters: Regex must handle nested div wrappers.
|
|
1078
|
+
Setup summary: Build display with both borders, remove, assert successful removal.
|
|
1079
|
+
"""
|
|
1080
|
+
# Arrange
|
|
1081
|
+
assistant_id = "agent-both"
|
|
1082
|
+
display_name = "Both Borders Agent"
|
|
1083
|
+
answer = "Answer with borders"
|
|
1084
|
+
config = SubAgentDisplayConfig(
|
|
1085
|
+
mode=SubAgentResponseDisplayMode.DETAILS_OPEN,
|
|
1086
|
+
add_quote_border=True,
|
|
1087
|
+
add_block_border=True,
|
|
1088
|
+
display_title_template="Answer from <strong>{}</strong>",
|
|
1089
|
+
)
|
|
1090
|
+
|
|
1091
|
+
# Build with both borders
|
|
1092
|
+
display = get_sub_agent_answer_display(
|
|
1093
|
+
display_name=display_name,
|
|
1094
|
+
display_config=config,
|
|
1095
|
+
answer=answer,
|
|
1096
|
+
assistant_id=assistant_id,
|
|
1097
|
+
)
|
|
1098
|
+
|
|
1099
|
+
text_with_display = f"Prefix\n{display}\nSuffix"
|
|
1100
|
+
|
|
1101
|
+
# Act
|
|
1102
|
+
result = remove_sub_agent_answer_from_text(
|
|
1103
|
+
display_config=config,
|
|
1104
|
+
text=text_with_display,
|
|
1105
|
+
assistant_id=assistant_id,
|
|
1106
|
+
)
|
|
1107
|
+
|
|
1108
|
+
# Assert
|
|
1109
|
+
assert "Answer with borders" not in result
|
|
1110
|
+
assert "Both Borders Agent" not in result
|
|
1111
|
+
assert "<details open>" not in result
|
|
1112
|
+
assert "Prefix" in result
|
|
1113
|
+
assert "Suffix" in result
|
|
1114
|
+
|
|
1115
|
+
|
|
1116
|
+
@pytest.mark.ai
|
|
1117
|
+
def test_remove_sub_agent_answer__preserves_other_content__with_multiple_displays() -> (
|
|
1118
|
+
None
|
|
1119
|
+
):
|
|
1120
|
+
"""
|
|
1121
|
+
Purpose: Verify removal only affects specified assistant_id.
|
|
1122
|
+
Why this matters: Must not remove content from different assistants.
|
|
1123
|
+
Setup summary: Embed multiple assistants, remove one, assert selective removal.
|
|
1124
|
+
"""
|
|
1125
|
+
# Arrange
|
|
1126
|
+
assistant_id_1 = "agent-1"
|
|
1127
|
+
assistant_id_2 = "agent-2"
|
|
1128
|
+
config = SubAgentDisplayConfig(
|
|
1129
|
+
mode=SubAgentResponseDisplayMode.PLAIN,
|
|
1130
|
+
add_quote_border=False,
|
|
1131
|
+
add_block_border=False,
|
|
1132
|
+
display_title_template="Answer from <strong>{}</strong>",
|
|
1133
|
+
)
|
|
1134
|
+
|
|
1135
|
+
# Build displays for two different assistants
|
|
1136
|
+
display_1 = get_sub_agent_answer_display(
|
|
1137
|
+
display_name="Agent 1",
|
|
1138
|
+
display_config=config,
|
|
1139
|
+
answer="Answer from agent 1",
|
|
1140
|
+
assistant_id=assistant_id_1,
|
|
1141
|
+
)
|
|
1142
|
+
|
|
1143
|
+
display_2 = get_sub_agent_answer_display(
|
|
1144
|
+
display_name="Agent 2",
|
|
1145
|
+
display_config=config,
|
|
1146
|
+
answer="Answer from agent 2",
|
|
1147
|
+
assistant_id=assistant_id_2,
|
|
1148
|
+
)
|
|
1149
|
+
|
|
1150
|
+
text_with_displays = f"Start\n{display_1}\nMiddle\n{display_2}\nEnd"
|
|
1151
|
+
|
|
1152
|
+
# Act - Remove only agent-1's display
|
|
1153
|
+
result = remove_sub_agent_answer_from_text(
|
|
1154
|
+
display_config=config,
|
|
1155
|
+
text=text_with_displays,
|
|
1156
|
+
assistant_id=assistant_id_1,
|
|
1157
|
+
)
|
|
1158
|
+
|
|
1159
|
+
# Assert
|
|
1160
|
+
assert "Answer from agent 1" not in result # Removed
|
|
1161
|
+
assert "Agent 1" not in result # Removed
|
|
1162
|
+
assert "Answer from agent 2" in result # Preserved
|
|
1163
|
+
assert "Agent 2" in result # Preserved
|
|
1164
|
+
assert "Start" in result
|
|
1165
|
+
assert "Middle" in result
|
|
1166
|
+
assert "End" in result
|
|
1167
|
+
|
|
1168
|
+
|
|
1169
|
+
@pytest.mark.ai
|
|
1170
|
+
def test_remove_sub_agent_answer__handles_multiline_answer__with_dotall_flag() -> None:
|
|
1171
|
+
"""
|
|
1172
|
+
Purpose: Verify removal works for multiline answers using DOTALL regex flag.
|
|
1173
|
+
Why this matters: Answers can span multiple lines with newlines.
|
|
1174
|
+
Setup summary: Build display with multiline answer, remove, assert removal.
|
|
1175
|
+
"""
|
|
1176
|
+
# Arrange
|
|
1177
|
+
assistant_id = "agent-multiline"
|
|
1178
|
+
display_name = "Multiline Agent"
|
|
1179
|
+
answer = "Line 1\nLine 2\nLine 3\nWith many\nnewlines"
|
|
1180
|
+
config = SubAgentDisplayConfig(
|
|
1181
|
+
mode=SubAgentResponseDisplayMode.PLAIN,
|
|
1182
|
+
add_quote_border=False,
|
|
1183
|
+
add_block_border=False,
|
|
1184
|
+
display_title_template="Answer from <strong>{}</strong>",
|
|
1185
|
+
)
|
|
1186
|
+
|
|
1187
|
+
# Build the display
|
|
1188
|
+
display = get_sub_agent_answer_display(
|
|
1189
|
+
display_name=display_name,
|
|
1190
|
+
display_config=config,
|
|
1191
|
+
answer=answer,
|
|
1192
|
+
assistant_id=assistant_id,
|
|
1193
|
+
)
|
|
1194
|
+
|
|
1195
|
+
text_with_display = f"Before\n{display}\nAfter"
|
|
1196
|
+
|
|
1197
|
+
# Act
|
|
1198
|
+
result = remove_sub_agent_answer_from_text(
|
|
1199
|
+
display_config=config,
|
|
1200
|
+
text=text_with_display,
|
|
1201
|
+
assistant_id=assistant_id,
|
|
1202
|
+
)
|
|
1203
|
+
|
|
1204
|
+
# Assert
|
|
1205
|
+
assert "Line 1" not in result
|
|
1206
|
+
assert "Line 2" not in result
|
|
1207
|
+
assert "Line 3" not in result
|
|
1208
|
+
assert "With many" not in result
|
|
1209
|
+
assert "Multiline Agent" not in result
|
|
1210
|
+
assert "Before" in result
|
|
1211
|
+
assert "After" in result
|
|
1212
|
+
|
|
1213
|
+
|
|
1214
|
+
@pytest.mark.ai
|
|
1215
|
+
def test_remove_sub_agent_answer__handles_special_regex_chars__in_answer() -> None:
|
|
1216
|
+
"""
|
|
1217
|
+
Purpose: Verify removal works when answer contains regex special characters.
|
|
1218
|
+
Why this matters: Template uses (.*?) which should match any content safely.
|
|
1219
|
+
Setup summary: Build display with regex chars in answer, remove, assert removal.
|
|
1220
|
+
"""
|
|
1221
|
+
# Arrange
|
|
1222
|
+
assistant_id = "agent-special"
|
|
1223
|
+
display_name = "Special Chars"
|
|
1224
|
+
answer = "Answer with $pecial ch@rs: .* + ? [ ] { } ( ) | \\"
|
|
1225
|
+
config = SubAgentDisplayConfig(
|
|
1226
|
+
mode=SubAgentResponseDisplayMode.PLAIN,
|
|
1227
|
+
add_quote_border=False,
|
|
1228
|
+
add_block_border=False,
|
|
1229
|
+
display_title_template="Answer from <strong>{}</strong>",
|
|
1230
|
+
)
|
|
1231
|
+
|
|
1232
|
+
# Build the display
|
|
1233
|
+
display = get_sub_agent_answer_display(
|
|
1234
|
+
display_name=display_name,
|
|
1235
|
+
display_config=config,
|
|
1236
|
+
answer=answer,
|
|
1237
|
+
assistant_id=assistant_id,
|
|
1238
|
+
)
|
|
1239
|
+
|
|
1240
|
+
text_with_display = f"Start\n{display}\nEnd"
|
|
1241
|
+
|
|
1242
|
+
# Act
|
|
1243
|
+
result = remove_sub_agent_answer_from_text(
|
|
1244
|
+
display_config=config,
|
|
1245
|
+
text=text_with_display,
|
|
1246
|
+
assistant_id=assistant_id,
|
|
1247
|
+
)
|
|
1248
|
+
|
|
1249
|
+
# Assert
|
|
1250
|
+
assert "$pecial ch@rs" not in result
|
|
1251
|
+
assert "Special Chars" not in result
|
|
1252
|
+
assert "Start" in result
|
|
1253
|
+
assert "End" in result
|
|
1254
|
+
|
|
1255
|
+
|
|
1256
|
+
@pytest.mark.ai
|
|
1257
|
+
def test_remove_sub_agent_answer__handles_empty_answer__successfully() -> None:
|
|
1258
|
+
"""
|
|
1259
|
+
Purpose: Verify removal works when answer is empty string.
|
|
1260
|
+
Why this matters: Edge case handling for empty content.
|
|
1261
|
+
Setup summary: Build display with empty answer, remove, assert removal.
|
|
1262
|
+
"""
|
|
1263
|
+
# Arrange
|
|
1264
|
+
assistant_id = "agent-empty"
|
|
1265
|
+
display_name = "Empty Answer Agent"
|
|
1266
|
+
answer = ""
|
|
1267
|
+
config = SubAgentDisplayConfig(
|
|
1268
|
+
mode=SubAgentResponseDisplayMode.PLAIN,
|
|
1269
|
+
add_quote_border=False,
|
|
1270
|
+
add_block_border=False,
|
|
1271
|
+
display_title_template="Answer from <strong>{}</strong>",
|
|
1272
|
+
)
|
|
1273
|
+
|
|
1274
|
+
# Build the display
|
|
1275
|
+
display = get_sub_agent_answer_display(
|
|
1276
|
+
display_name=display_name,
|
|
1277
|
+
display_config=config,
|
|
1278
|
+
answer=answer,
|
|
1279
|
+
assistant_id=assistant_id,
|
|
1280
|
+
)
|
|
1281
|
+
|
|
1282
|
+
text_with_display = f"Beginning\n{display}\nEnding"
|
|
1283
|
+
|
|
1284
|
+
# Act
|
|
1285
|
+
result = remove_sub_agent_answer_from_text(
|
|
1286
|
+
display_config=config,
|
|
1287
|
+
text=text_with_display,
|
|
1288
|
+
assistant_id=assistant_id,
|
|
1289
|
+
)
|
|
1290
|
+
|
|
1291
|
+
# Assert
|
|
1292
|
+
assert "Empty Answer Agent" not in result
|
|
1293
|
+
assert "Beginning" in result
|
|
1294
|
+
assert "Ending" in result
|
|
1295
|
+
|
|
1296
|
+
|
|
1297
|
+
@pytest.mark.ai
|
|
1298
|
+
def test_remove_sub_agent_answer__no_op_when_assistant_not_found() -> None:
|
|
1299
|
+
"""
|
|
1300
|
+
Purpose: Verify text unchanged when assistant_id has no matching display.
|
|
1301
|
+
Why this matters: Should not modify text when target not present.
|
|
1302
|
+
Setup summary: Build display for one assistant, try removing different one.
|
|
1303
|
+
"""
|
|
1304
|
+
# Arrange
|
|
1305
|
+
assistant_id_present = "agent-present"
|
|
1306
|
+
assistant_id_absent = "agent-absent"
|
|
1307
|
+
config = SubAgentDisplayConfig(
|
|
1308
|
+
mode=SubAgentResponseDisplayMode.PLAIN,
|
|
1309
|
+
add_quote_border=False,
|
|
1310
|
+
add_block_border=False,
|
|
1311
|
+
display_title_template="Answer from <strong>{}</strong>",
|
|
1312
|
+
)
|
|
1313
|
+
|
|
1314
|
+
# Build display for present assistant
|
|
1315
|
+
display = get_sub_agent_answer_display(
|
|
1316
|
+
display_name="Present Agent",
|
|
1317
|
+
display_config=config,
|
|
1318
|
+
answer="Present answer",
|
|
1319
|
+
assistant_id=assistant_id_present,
|
|
1320
|
+
)
|
|
1321
|
+
|
|
1322
|
+
text_with_display = f"Start\n{display}\nEnd"
|
|
1323
|
+
original_text = text_with_display
|
|
1324
|
+
|
|
1325
|
+
# Act - Try to remove absent assistant
|
|
1326
|
+
result = remove_sub_agent_answer_from_text(
|
|
1327
|
+
display_config=config,
|
|
1328
|
+
text=text_with_display,
|
|
1329
|
+
assistant_id=assistant_id_absent,
|
|
1330
|
+
)
|
|
1331
|
+
|
|
1332
|
+
# Assert
|
|
1333
|
+
assert result == original_text
|
|
1334
|
+
assert "Present answer" in result
|
|
1335
|
+
assert "Present Agent" in result
|