Skip to content

Commit 146f9a0

Browse files
authored
Merge pull request #898 from major/rlsapi-v1
feat(rlsapi): add method to combine request inputs
2 parents dd458f7 + 8272057 commit 146f9a0

2 files changed

Lines changed: 179 additions & 0 deletions

File tree

src/models/rlsapi/requests.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,3 +174,27 @@ def validate_question(cls, value: str) -> str:
174174
if not stripped:
175175
raise ValueError("Question cannot be empty or whitespace-only")
176176
return stripped
177+
178+
def get_input_source(self) -> str:
179+
"""Combine all non-empty input sources into a single string.
180+
181+
Joins question, stdin, attachment contents, and terminal output with double
182+
newlines, in priority order. Empty sources are omitted.
183+
184+
Priority order:
185+
1. question
186+
2. stdin
187+
3. attachment contents
188+
4. terminal output
189+
190+
Returns:
191+
The combined input string with sources separated by double newlines.
192+
"""
193+
# pylint: disable=no-member # Pydantic fields are dynamic
194+
parts = [
195+
self.question,
196+
self.context.stdin,
197+
self.context.attachments.contents,
198+
self.context.terminal.output,
199+
]
200+
return "\n\n".join(part for part in parts if part)

tests/unit/models/rlsapi/test_requests.py

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,3 +306,158 @@ def test_serialization_roundtrip(
306306
assert restored.question == sample_request.question
307307
assert restored.skip_rag == sample_request.skip_rag
308308
assert restored.context.systeminfo.os == sample_request.context.systeminfo.os
309+
310+
311+
# ---------------------------------------------------------------------------
312+
# get_input_source() tests
313+
# ---------------------------------------------------------------------------
314+
315+
316+
class TestGetInputSource:
317+
"""Test cases for RlsapiV1InferRequest.get_input_source() method."""
318+
319+
@pytest.fixture(name="make_request")
320+
def make_request_fixture(self) -> Any:
321+
"""Factory fixture to build requests with specific context values."""
322+
323+
class _RequestBuilder: # pylint: disable=too-few-public-methods
324+
"""Helper to construct requests with variable context."""
325+
326+
@staticmethod
327+
def build(
328+
question: str = "q",
329+
stdin: str = "",
330+
attachment: str = "",
331+
terminal: str = "",
332+
) -> RlsapiV1InferRequest:
333+
"""Build an RlsapiV1InferRequest with specified context values."""
334+
return RlsapiV1InferRequest(
335+
question=question,
336+
context=RlsapiV1Context(
337+
stdin=stdin,
338+
attachments=RlsapiV1Attachment(contents=attachment),
339+
terminal=RlsapiV1Terminal(output=terminal),
340+
),
341+
)
342+
343+
return _RequestBuilder
344+
345+
# -------------------------------------------------------------------------
346+
# Parameterized tests for input combinations
347+
# -------------------------------------------------------------------------
348+
349+
@pytest.mark.parametrize(
350+
("question", "stdin", "attachment", "terminal", "expected"),
351+
[
352+
# All four sources present
353+
pytest.param(
354+
"question",
355+
"stdin",
356+
"attachment",
357+
"terminal",
358+
"question\n\nstdin\n\nattachment\n\nterminal",
359+
id="all_four_sources",
360+
),
361+
# Three sources
362+
pytest.param(
363+
"question",
364+
"stdin",
365+
"attachment",
366+
"",
367+
"question\n\nstdin\n\nattachment",
368+
id="question_stdin_attachment",
369+
),
370+
pytest.param(
371+
"question",
372+
"",
373+
"attachment",
374+
"terminal",
375+
"question\n\nattachment\n\nterminal",
376+
id="question_attachment_terminal",
377+
),
378+
# Two sources
379+
pytest.param(
380+
"question",
381+
"stdin",
382+
"",
383+
"",
384+
"question\n\nstdin",
385+
id="question_stdin",
386+
),
387+
pytest.param(
388+
"question",
389+
"",
390+
"attachment",
391+
"",
392+
"question\n\nattachment",
393+
id="question_attachment",
394+
),
395+
pytest.param(
396+
"question",
397+
"",
398+
"",
399+
"terminal",
400+
"question\n\nterminal",
401+
id="question_terminal",
402+
),
403+
# Question only
404+
pytest.param(
405+
"question",
406+
"",
407+
"",
408+
"",
409+
"question",
410+
id="question_only",
411+
),
412+
],
413+
)
414+
# pylint: disable=too-many-arguments,too-many-positional-arguments
415+
def test_input_combinations(
416+
self,
417+
make_request: Any,
418+
question: str,
419+
stdin: str,
420+
attachment: str,
421+
terminal: str,
422+
expected: str,
423+
) -> None:
424+
"""Test get_input_source() joins non-empty sources with double newlines."""
425+
request = make_request.build(
426+
question=question,
427+
stdin=stdin,
428+
attachment=attachment,
429+
terminal=terminal,
430+
)
431+
assert request.get_input_source() == expected
432+
433+
# -------------------------------------------------------------------------
434+
# Edge cases
435+
# -------------------------------------------------------------------------
436+
437+
def test_preserves_content_formatting(self, make_request: Any) -> None:
438+
"""Test that content formatting (newlines, special chars) is preserved."""
439+
multiline_attachment = "line1\nline2\nline3"
440+
request = make_request.build(
441+
question="Explain this config",
442+
attachment=multiline_attachment,
443+
)
444+
result = request.get_input_source()
445+
assert "line1\nline2\nline3" in result
446+
447+
def test_priority_order(self, make_request: Any) -> None:
448+
"""Test that sources appear in priority order: question, stdin, attachment, terminal."""
449+
request = make_request.build(
450+
question="Q",
451+
stdin="S",
452+
attachment="A",
453+
terminal="T",
454+
)
455+
result = request.get_input_source()
456+
assert result == "Q\n\nS\n\nA\n\nT"
457+
# Verify order by checking positions
458+
assert (
459+
result.index("Q")
460+
< result.index("S")
461+
< result.index("A")
462+
< result.index("T")
463+
)

0 commit comments

Comments
 (0)