drydock-cli 3.0.25__tar.gz → 3.0.26__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 (53) hide show
  1. {drydock_cli-3.0.25/drydock_cli.egg-info → drydock_cli-3.0.26}/PKG-INFO +1 -1
  2. {drydock_cli-3.0.25 → drydock_cli-3.0.26}/drydock/__init__.py +1 -1
  3. {drydock_cli-3.0.25 → drydock_cli-3.0.26}/drydock/providers.py +6 -1
  4. {drydock_cli-3.0.25 → drydock_cli-3.0.26/drydock_cli.egg-info}/PKG-INFO +1 -1
  5. {drydock_cli-3.0.25 → drydock_cli-3.0.26}/drydock_cli.egg-info/SOURCES.txt +1 -0
  6. {drydock_cli-3.0.25 → drydock_cli-3.0.26}/pyproject.toml +1 -1
  7. drydock_cli-3.0.26/tests/test_streaming_newlines.py +70 -0
  8. {drydock_cli-3.0.25 → drydock_cli-3.0.26}/LICENSE +0 -0
  9. {drydock_cli-3.0.25 → drydock_cli-3.0.26}/NOTICE +0 -0
  10. {drydock_cli-3.0.25 → drydock_cli-3.0.26}/README.md +0 -0
  11. {drydock_cli-3.0.25 → drydock_cli-3.0.26}/drydock/__main__.py +0 -0
  12. {drydock_cli-3.0.25 → drydock_cli-3.0.26}/drydock/agent.py +0 -0
  13. {drydock_cli-3.0.25 → drydock_cli-3.0.26}/drydock/bash_safety.py +0 -0
  14. {drydock_cli-3.0.25 → drydock_cli-3.0.26}/drydock/cli.py +0 -0
  15. {drydock_cli-3.0.25 → drydock_cli-3.0.26}/drydock/compaction.py +0 -0
  16. {drydock_cli-3.0.25 → drydock_cli-3.0.26}/drydock/config.py +0 -0
  17. {drydock_cli-3.0.25 → drydock_cli-3.0.26}/drydock/detect.py +0 -0
  18. {drydock_cli-3.0.25 → drydock_cli-3.0.26}/drydock/guards.py +0 -0
  19. {drydock_cli-3.0.25 → drydock_cli-3.0.26}/drydock/loop_detect.py +0 -0
  20. {drydock_cli-3.0.25 → drydock_cli-3.0.26}/drydock/tool_registry.py +0 -0
  21. {drydock_cli-3.0.25 → drydock_cli-3.0.26}/drydock/tools/__init__.py +0 -0
  22. {drydock_cli-3.0.25 → drydock_cli-3.0.26}/drydock/tui/__init__.py +0 -0
  23. {drydock_cli-3.0.25 → drydock_cli-3.0.26}/drydock/tui/app.py +0 -0
  24. {drydock_cli-3.0.25 → drydock_cli-3.0.26}/drydock/tui/approval.py +0 -0
  25. {drydock_cli-3.0.25 → drydock_cli-3.0.26}/drydock/tui/messages.py +0 -0
  26. {drydock_cli-3.0.25 → drydock_cli-3.0.26}/drydock/tui/widgets.py +0 -0
  27. {drydock_cli-3.0.25 → drydock_cli-3.0.26}/drydock/tuning.py +0 -0
  28. {drydock_cli-3.0.25 → drydock_cli-3.0.26}/drydock_cli.egg-info/dependency_links.txt +0 -0
  29. {drydock_cli-3.0.25 → drydock_cli-3.0.26}/drydock_cli.egg-info/entry_points.txt +0 -0
  30. {drydock_cli-3.0.25 → drydock_cli-3.0.26}/drydock_cli.egg-info/requires.txt +0 -0
  31. {drydock_cli-3.0.25 → drydock_cli-3.0.26}/drydock_cli.egg-info/top_level.txt +0 -0
  32. {drydock_cli-3.0.25 → drydock_cli-3.0.26}/setup.cfg +0 -0
  33. {drydock_cli-3.0.25 → drydock_cli-3.0.26}/tests/test_approval.py +0 -0
  34. {drydock_cli-3.0.25 → drydock_cli-3.0.26}/tests/test_back_command.py +0 -0
  35. {drydock_cli-3.0.25 → drydock_cli-3.0.26}/tests/test_bash_safety.py +0 -0
  36. {drydock_cli-3.0.25 → drydock_cli-3.0.26}/tests/test_cli_agents.py +0 -0
  37. {drydock_cli-3.0.25 → drydock_cli-3.0.26}/tests/test_compaction.py +0 -0
  38. {drydock_cli-3.0.25 → drydock_cli-3.0.26}/tests/test_config.py +0 -0
  39. {drydock_cli-3.0.25 → drydock_cli-3.0.26}/tests/test_detect.py +0 -0
  40. {drydock_cli-3.0.25 → drydock_cli-3.0.26}/tests/test_empty_response.py +0 -0
  41. {drydock_cli-3.0.25 → drydock_cli-3.0.26}/tests/test_guards_and_tools.py +0 -0
  42. {drydock_cli-3.0.25 → drydock_cli-3.0.26}/tests/test_hallucinated_tools.py +0 -0
  43. {drydock_cli-3.0.25 → drydock_cli-3.0.26}/tests/test_leaked_tool_call.py +0 -0
  44. {drydock_cli-3.0.25 → drydock_cli-3.0.26}/tests/test_loop_detect.py +0 -0
  45. {drydock_cli-3.0.25 → drydock_cli-3.0.26}/tests/test_plan_autocontinue.py +0 -0
  46. {drydock_cli-3.0.25 → drydock_cli-3.0.26}/tests/test_providers_unreachable.py +0 -0
  47. {drydock_cli-3.0.25 → drydock_cli-3.0.26}/tests/test_stop.py +0 -0
  48. {drydock_cli-3.0.25 → drydock_cli-3.0.26}/tests/test_subagent.py +0 -0
  49. {drydock_cli-3.0.25 → drydock_cli-3.0.26}/tests/test_todo.py +0 -0
  50. {drydock_cli-3.0.25 → drydock_cli-3.0.26}/tests/test_tool_arg_parsing.py +0 -0
  51. {drydock_cli-3.0.25 → drydock_cli-3.0.26}/tests/test_tools_undo.py +0 -0
  52. {drydock_cli-3.0.25 → drydock_cli-3.0.26}/tests/test_tui.py +0 -0
  53. {drydock_cli-3.0.25 → drydock_cli-3.0.26}/tests/test_tuning.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: drydock-cli
3
- Version: 3.0.25
3
+ Version: 3.0.26
4
4
  Summary: Drydock — a local, provider-agnostic terminal coding agent for local LLMs
5
5
  Author: Frank Bobe III
6
6
  License-Expression: Apache-2.0
@@ -7,4 +7,4 @@ try:
7
7
  __version__ = _version("drydock-cli")
8
8
  except PackageNotFoundError:
9
9
  # Running from source before install (e.g. PYTHONPATH dev runs).
10
- __version__ = "3.0.25"
10
+ __version__ = "3.0.26"
@@ -253,7 +253,12 @@ def stream(
253
253
  if delta.content:
254
254
  # Strip any leaked thinking-token markers (best-effort per chunk).
255
255
  chunk_text = strip_thinking_tokens(delta.content)
256
- if chunk_text.strip():
256
+ # Keep ANY non-empty chunk — including whitespace-only ones. A
257
+ # `.strip()` guard here used to DROP newline-only chunks (the blank
258
+ # line a model streams between markdown blocks arrives as its own
259
+ # chunk), mashing "para### Header" together. `if chunk_text:` still
260
+ # skips chunks that stripping emptied (e.g. a lone thinking marker).
261
+ if chunk_text:
257
262
  text += chunk_text
258
263
  yield TextChunk(chunk_text)
259
264
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: drydock-cli
3
- Version: 3.0.25
3
+ Version: 3.0.26
4
4
  Summary: Drydock — a local, provider-agnostic terminal coding agent for local LLMs
5
5
  Author: Frank Bobe III
6
6
  License-Expression: Apache-2.0
@@ -42,6 +42,7 @@ tests/test_loop_detect.py
42
42
  tests/test_plan_autocontinue.py
43
43
  tests/test_providers_unreachable.py
44
44
  tests/test_stop.py
45
+ tests/test_streaming_newlines.py
45
46
  tests/test_subagent.py
46
47
  tests/test_todo.py
47
48
  tests/test_tool_arg_parsing.py
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
7
7
  # PyPI distribution name is drydock-cli (the established install name, continued
8
8
  # from the retired v2 fork); the import package + CLI command stay `drydock`.
9
9
  name = "drydock-cli"
10
- version = "3.0.25"
10
+ version = "3.0.26"
11
11
  description = "Drydock — a local, provider-agnostic terminal coding agent for local LLMs"
12
12
  readme = "README.md"
13
13
  requires-python = ">=3.12"
@@ -0,0 +1,70 @@
1
+ """Streaming must preserve whitespace-only (newline) chunks.
2
+
3
+ A `.strip()` guard in the streaming loop used to DROP a chunk that was only
4
+ "\n\n" — the blank line a model streams between markdown blocks — so a long
5
+ response rendered as "paragraph### Header" mashed together (operator-reported).
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import drydock.providers as P
10
+ from drydock.providers import AssistantTurn, TextChunk, stream
11
+
12
+
13
+ class _Delta:
14
+ def __init__(self, c):
15
+ self.content = c
16
+ self.tool_calls = None
17
+
18
+
19
+ class _Choice:
20
+ def __init__(self, c):
21
+ self.delta = _Delta(c)
22
+ self.finish_reason = None
23
+
24
+
25
+ class _Chunk:
26
+ def __init__(self, c):
27
+ self.choices = [_Choice(c)]
28
+ self.usage = None
29
+
30
+
31
+ def test_streaming_preserves_newline_only_chunks(monkeypatch):
32
+ chunks = [
33
+ _Chunk("Intro paragraph."),
34
+ _Chunk("\n\n"), # ← blank line between blocks (was dropped)
35
+ _Chunk("### Header"),
36
+ _Chunk("\n\n"),
37
+ _Chunk("body text"),
38
+ ]
39
+ monkeypatch.setattr(P, "_safe_create", lambda *a, **k: iter(chunks))
40
+
41
+ full = ""
42
+ streamed = []
43
+ for ev in stream(
44
+ model="gemma4", system="s",
45
+ messages=[{"role": "user", "content": "review"}],
46
+ tool_schemas=[], # no tools → text-only → streaming path even for gemma
47
+ config={"model": "gemma4", "provider": "vllm"},
48
+ ):
49
+ if isinstance(ev, TextChunk):
50
+ streamed.append(ev.text)
51
+ elif isinstance(ev, AssistantTurn):
52
+ full = ev.text
53
+
54
+ assert "\n\n" in "".join(streamed) # newline chunks survived streaming
55
+ assert "Intro paragraph.\n\n### Header" in full # blocks not mashed
56
+ assert "### Header\n\nbody text" in full
57
+
58
+
59
+ def test_streaming_still_drops_chunk_emptied_by_stripping(monkeypatch):
60
+ # A chunk that is ONLY a thinking marker strips to "" and is skipped (so we
61
+ # don't emit empty text), but real whitespace is kept.
62
+ chunks = [_Chunk("hello"), _Chunk("<|channel>x<channel|>"), _Chunk("\n"), _Chunk("world")]
63
+ monkeypatch.setattr(P, "_safe_create", lambda *a, **k: iter(chunks))
64
+ full = ""
65
+ for ev in stream(model="gemma4", system="s",
66
+ messages=[{"role": "user", "content": "hi"}],
67
+ tool_schemas=[], config={"model": "gemma4", "provider": "vllm"}):
68
+ if isinstance(ev, AssistantTurn):
69
+ full = ev.text
70
+ assert full == "hello\nworld" # marker-only chunk gone, newline kept
File without changes
File without changes
File without changes
File without changes