langstage-cli 0.6.8__tar.gz → 0.6.9__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.
- {langstage_cli-0.6.8/langstage_cli.egg-info → langstage_cli-0.6.9}/PKG-INFO +1 -7
- {langstage_cli-0.6.8 → langstage_cli-0.6.9}/README.md +0 -6
- {langstage_cli-0.6.8 → langstage_cli-0.6.9}/langstage_cli/cli.py +22 -34
- {langstage_cli-0.6.8 → langstage_cli-0.6.9}/langstage_cli/config.py +6 -4
- {langstage_cli-0.6.8 → langstage_cli-0.6.9/langstage_cli.egg-info}/PKG-INFO +1 -7
- {langstage_cli-0.6.8 → langstage_cli-0.6.9}/pyproject.toml +1 -1
- {langstage_cli-0.6.8 → langstage_cli-0.6.9}/tests/test_cli_help.py +3 -1
- {langstage_cli-0.6.8 → langstage_cli-0.6.9}/tests/test_codeconfig.py +12 -9
- {langstage_cli-0.6.8 → langstage_cli-0.6.9}/tests/test_show_config.py +4 -7
- langstage_cli-0.6.9/tests/test_stream_mode.py +71 -0
- langstage_cli-0.6.8/tests/test_stream_mode.py +0 -76
- {langstage_cli-0.6.8 → langstage_cli-0.6.9}/LICENSE +0 -0
- {langstage_cli-0.6.8 → langstage_cli-0.6.9}/deepagent_code/__init__.py +0 -0
- {langstage_cli-0.6.8 → langstage_cli-0.6.9}/langstage_cli/__init__.py +0 -0
- {langstage_cli-0.6.8 → langstage_cli-0.6.9}/langstage_cli/agui_stream.py +0 -0
- {langstage_cli-0.6.8 → langstage_cli-0.6.9}/langstage_cli.egg-info/SOURCES.txt +0 -0
- {langstage_cli-0.6.8 → langstage_cli-0.6.9}/langstage_cli.egg-info/dependency_links.txt +0 -0
- {langstage_cli-0.6.8 → langstage_cli-0.6.9}/langstage_cli.egg-info/entry_points.txt +0 -0
- {langstage_cli-0.6.8 → langstage_cli-0.6.9}/langstage_cli.egg-info/requires.txt +0 -0
- {langstage_cli-0.6.8 → langstage_cli-0.6.9}/langstage_cli.egg-info/top_level.txt +0 -0
- {langstage_cli-0.6.8 → langstage_cli-0.6.9}/setup.cfg +0 -0
- {langstage_cli-0.6.8 → langstage_cli-0.6.9}/tests/test_agui_stream.py +0 -0
- {langstage_cli-0.6.8 → langstage_cli-0.6.9}/tests/test_checkpointer.py +0 -0
- {langstage_cli-0.6.8 → langstage_cli-0.6.9}/tests/test_cli.py +0 -0
- {langstage_cli-0.6.8 → langstage_cli-0.6.9}/tests/test_config.py +0 -0
- {langstage_cli-0.6.8 → langstage_cli-0.6.9}/tests/test_configurable.py +0 -0
- {langstage_cli-0.6.8 → langstage_cli-0.6.9}/tests/test_help_render.py +0 -0
- {langstage_cli-0.6.8 → langstage_cli-0.6.9}/tests/test_no_interactive_approve.py +0 -0
- {langstage_cli-0.6.8 → langstage_cli-0.6.9}/tests/test_quiet_output.py +0 -0
- {langstage_cli-0.6.8 → langstage_cli-0.6.9}/tests/test_rename_shim.py +0 -0
- {langstage_cli-0.6.8 → langstage_cli-0.6.9}/tests/test_spec_resolution.py +0 -0
- {langstage_cli-0.6.8 → langstage_cli-0.6.9}/tests/test_streaming_marker.py +0 -0
- {langstage_cli-0.6.8 → langstage_cli-0.6.9}/tests/test_turn_exit_and_render.py +0 -0
- {langstage_cli-0.6.8 → langstage_cli-0.6.9}/tests/test_unicode_console.py +0 -0
- {langstage_cli-0.6.8 → langstage_cli-0.6.9}/tests/test_verify_flag.py +0 -0
- {langstage_cli-0.6.8 → langstage_cli-0.6.9}/tests/test_workspace.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: langstage-cli
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.9
|
|
4
4
|
Summary: The terminal stage for your LangGraph agent — Claude Code-style CLI for any CompiledGraph
|
|
5
5
|
Author-email: Kedar Dabhadkar <kdabhadk@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -148,9 +148,6 @@ langstage-cli
|
|
|
148
148
|
|
|
149
149
|
# Working directory
|
|
150
150
|
export LANGSTAGE_WORKSPACE_ROOT="/path/to/workspace"
|
|
151
|
-
|
|
152
|
-
# Stream mode (updates or messages)
|
|
153
|
-
export LANGSTAGE_STREAM_MODE="updates"
|
|
154
151
|
```
|
|
155
152
|
|
|
156
153
|
## Configuration Files
|
|
@@ -179,7 +176,6 @@ root = "."
|
|
|
179
176
|
[ui]
|
|
180
177
|
verbose = true
|
|
181
178
|
async_mode = false
|
|
182
|
-
stream_mode = "auto" # auto | updates | messages
|
|
183
179
|
|
|
184
180
|
[configurable]
|
|
185
181
|
# seeds LangGraph RunnableConfig.configurable
|
|
@@ -200,8 +196,6 @@ Options:
|
|
|
200
196
|
-f, --file PATH Read message from a file (any extension)
|
|
201
197
|
--interactive/--no-interactive Handle interrupts (default: interactive)
|
|
202
198
|
--async-mode/--sync-mode Async streaming (default: sync)
|
|
203
|
-
--stream-mode [auto|updates|messages]
|
|
204
|
-
Stream mode (default: auto)
|
|
205
199
|
-v, --verbose Verbose output
|
|
206
200
|
--demo Run with the built-in keyless demo agent
|
|
207
201
|
--show-config Print the resolved configuration and exit
|
|
@@ -111,9 +111,6 @@ langstage-cli
|
|
|
111
111
|
|
|
112
112
|
# Working directory
|
|
113
113
|
export LANGSTAGE_WORKSPACE_ROOT="/path/to/workspace"
|
|
114
|
-
|
|
115
|
-
# Stream mode (updates or messages)
|
|
116
|
-
export LANGSTAGE_STREAM_MODE="updates"
|
|
117
114
|
```
|
|
118
115
|
|
|
119
116
|
## Configuration Files
|
|
@@ -142,7 +139,6 @@ root = "."
|
|
|
142
139
|
[ui]
|
|
143
140
|
verbose = true
|
|
144
141
|
async_mode = false
|
|
145
|
-
stream_mode = "auto" # auto | updates | messages
|
|
146
142
|
|
|
147
143
|
[configurable]
|
|
148
144
|
# seeds LangGraph RunnableConfig.configurable
|
|
@@ -163,8 +159,6 @@ Options:
|
|
|
163
159
|
-f, --file PATH Read message from a file (any extension)
|
|
164
160
|
--interactive/--no-interactive Handle interrupts (default: interactive)
|
|
165
161
|
--async-mode/--sync-mode Async streaming (default: sync)
|
|
166
|
-
--stream-mode [auto|updates|messages]
|
|
167
|
-
Stream mode (default: auto)
|
|
168
162
|
-v, --verbose Verbose output
|
|
169
163
|
--demo Run with the built-in keyless demo agent
|
|
170
164
|
--show-config Print the resolved configuration and exit
|
|
@@ -76,11 +76,12 @@ def _status(msg: str) -> None:
|
|
|
76
76
|
print(msg, file=sys.stderr if _QUIET else sys.stdout)
|
|
77
77
|
|
|
78
78
|
|
|
79
|
-
#
|
|
80
|
-
#
|
|
81
|
-
#
|
|
82
|
-
#
|
|
83
|
-
|
|
79
|
+
# Keys the terminal CLI never honors, so `--show-config` / `/config` omit them and
|
|
80
|
+
# the diagnostic only advertises knobs that actually do something. The inherited
|
|
81
|
+
# HostConfig server keys — it starts no server (host/port/debug are inert) and the
|
|
82
|
+
# header box uses the loaded graph's name, not `title` (gh #36) — plus `stream_mode`,
|
|
83
|
+
# which has had no effect since the AG-UI streaming migration (gh #62).
|
|
84
|
+
_INERT_KEYS = ["host", "port", "debug", "title", "stream_mode"]
|
|
84
85
|
|
|
85
86
|
# Spinner frames for thinking animation
|
|
86
87
|
SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
|
|
@@ -865,20 +866,6 @@ def handle_interrupt_input(num_actions: int = 1) -> List[Dict[str, Any]]:
|
|
|
865
866
|
sys.exit(0)
|
|
866
867
|
|
|
867
868
|
|
|
868
|
-
def _resolve_stream_mode(stream_mode: str):
|
|
869
|
-
"""Map the user-facing stream mode to what LangGraph actually streams.
|
|
870
|
-
|
|
871
|
-
'auto' = dual-channel ``["updates", "messages"]``: the parser renders LLM
|
|
872
|
-
tokens live when the agent streams them, and falls back to the finished
|
|
873
|
-
message content otherwise — so a node that returns a complete (non-token-
|
|
874
|
-
streamed) ``AIMessage`` still renders instead of an empty turn. (Bare
|
|
875
|
-
'messages' only carries LLM token streams, which is exactly why it was a
|
|
876
|
-
silent-blank trap — gh #-dogfood.) 'updates'/'messages' pass through as the
|
|
877
|
-
explicit single-mode power-user choices.
|
|
878
|
-
"""
|
|
879
|
-
return ["updates", "messages"] if stream_mode == "auto" else stream_mode
|
|
880
|
-
|
|
881
|
-
|
|
882
869
|
def print_help():
|
|
883
870
|
"""Print formatted help information."""
|
|
884
871
|
print(f"\n{BOLD}{BRIGHT_CYAN}Commands{RESET}")
|
|
@@ -980,14 +967,12 @@ def cmd_status(args: str, context: Dict[str, Any]) -> Optional[str]:
|
|
|
980
967
|
agent_name = context.get("agent_name", "Unknown")
|
|
981
968
|
verbose = context.get("verbose", False)
|
|
982
969
|
use_async = context.get("use_async", False)
|
|
983
|
-
stream_mode = context.get("stream_mode", "updates")
|
|
984
970
|
|
|
985
971
|
print(f"\n{BOLD}{BRIGHT_CYAN}Session Status{RESET}")
|
|
986
972
|
print(f"{DIM}{'─' * 30}{RESET}")
|
|
987
973
|
print(f" {DIM}Agent:{RESET} {agent_name}")
|
|
988
974
|
print(f" {DIM}Thread ID:{RESET} {thread_id[:8]}...")
|
|
989
975
|
print(f" {DIM}Mode:{RESET} {'async' if use_async else 'sync'}")
|
|
990
|
-
print(f" {DIM}Stream:{RESET} {stream_mode}")
|
|
991
976
|
print(f" {DIM}Verbose:{RESET} {'on' if verbose else 'off'}")
|
|
992
977
|
print(f" {DIM}CWD:{RESET} {os.getcwd()}")
|
|
993
978
|
print()
|
|
@@ -1483,9 +1468,11 @@ def run_conversation_loop(
|
|
|
1483
1468
|
@click.option(
|
|
1484
1469
|
"--stream-mode",
|
|
1485
1470
|
type=click.Choice(["auto", "updates", "messages"]),
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1471
|
+
# DEPRECATED (gh #62): a no-op since the AG-UI streaming migration — all three
|
|
1472
|
+
# modes render identically. Kept hidden + accepted so existing `--stream-mode X`
|
|
1473
|
+
# invocations don't hard-error; a one-line notice fires when it is passed.
|
|
1474
|
+
hidden=True,
|
|
1475
|
+
help="(deprecated: no effect since the AG-UI streaming migration).",
|
|
1489
1476
|
)
|
|
1490
1477
|
@click.option(
|
|
1491
1478
|
"--verbose",
|
|
@@ -1566,7 +1553,6 @@ def main(
|
|
|
1566
1553
|
\b
|
|
1567
1554
|
- LANGSTAGE_AGENT_SPEC: Agent location (same formats as above).
|
|
1568
1555
|
- LANGSTAGE_WORKSPACE_ROOT: Working directory for the agent
|
|
1569
|
-
- LANGSTAGE_STREAM_MODE: Stream mode for LangGraph (auto, updates, or messages)
|
|
1570
1556
|
|
|
1571
1557
|
Reads ~/.langstage/config.toml (global) and langstage.toml (project,
|
|
1572
1558
|
walks up from cwd). Precedence: CLI args > env vars > project TOML >
|
|
@@ -1629,6 +1615,14 @@ def main(
|
|
|
1629
1615
|
"verbose": True if verbose else None,
|
|
1630
1616
|
}
|
|
1631
1617
|
|
|
1618
|
+
# --stream-mode is deprecated and inert since the AG-UI streaming migration
|
|
1619
|
+
# (gh #62): accepted so scripts don't break, but say so once when it's passed.
|
|
1620
|
+
if stream_mode is not None:
|
|
1621
|
+
_status(
|
|
1622
|
+
f"{DIM}⏺ Note: --stream-mode is deprecated and has no effect "
|
|
1623
|
+
f"(streaming is uniform since the AG-UI migration).{RESET}"
|
|
1624
|
+
)
|
|
1625
|
+
|
|
1632
1626
|
if show_config:
|
|
1633
1627
|
# See _INERT_KEYS: hide the server/web keys this surface ignores. (gh #36)
|
|
1634
1628
|
print(
|
|
@@ -1671,16 +1665,10 @@ def main(
|
|
|
1671
1665
|
)
|
|
1672
1666
|
final_spec = cfg.agent_spec
|
|
1673
1667
|
final_graph_name_default = cfg.graph_name
|
|
1668
|
+
# stream_mode is deprecated and inert (gh #62) — it only ever comes from the
|
|
1669
|
+
# accepted-and-ignored flag now (env/TOML no longer resolve it), so there is
|
|
1670
|
+
# nothing to validate; it changes no rendering.
|
|
1674
1671
|
final_stream_mode = cfg.stream_mode
|
|
1675
|
-
# The CLI's render path only supports 'updates' / 'messages'. Reject anything
|
|
1676
|
-
# else (e.g. LANGSTAGE_STREAM_MODE=values, which bypasses the flag's Choice)
|
|
1677
|
-
# with a clean error instead of a fatal crash deep in the stream worker.
|
|
1678
|
-
if final_stream_mode not in ("auto", "updates", "messages"):
|
|
1679
|
-
_status(
|
|
1680
|
-
f"{RED}⏺ Error: unsupported stream mode {final_stream_mode!r} "
|
|
1681
|
-
f"(use 'auto', 'updates', or 'messages')"
|
|
1682
|
-
)
|
|
1683
|
-
sys.exit(2)
|
|
1684
1672
|
use_async = cfg.async_mode
|
|
1685
1673
|
verbose = cfg.verbose
|
|
1686
1674
|
# Whether a workspace root was explicitly configured (vs the default cwd);
|
|
@@ -84,16 +84,18 @@ class CodeConfig(HostConfig):
|
|
|
84
84
|
resolver); the even-older ``DEEPAGENT_SPEC`` alias is reconciled below.
|
|
85
85
|
"""
|
|
86
86
|
|
|
87
|
+
# stream_mode is retained as an inert field (default only) so existing code and
|
|
88
|
+
# `--stream-mode` overrides don't break, but it is DEPRECATED: it has no effect
|
|
89
|
+
# since the AG-UI streaming migration (all three modes render identically). It is
|
|
90
|
+
# no longer resolved from LANGSTAGE_STREAM_MODE / [ui] stream_mode, and is omitted
|
|
91
|
+
# from `--show-config`. (gh #62)
|
|
87
92
|
stream_mode: str = "auto"
|
|
88
93
|
graph_name: str = "graph"
|
|
89
94
|
verbose: bool = False
|
|
90
95
|
async_mode: bool = False
|
|
91
96
|
|
|
92
|
-
_ENV: ClassVar[dict] = {
|
|
93
|
-
"stream_mode": ("LANGSTAGE_STREAM_MODE", str),
|
|
94
|
-
}
|
|
97
|
+
_ENV: ClassVar[dict] = {}
|
|
95
98
|
_TOML: ClassVar[dict] = {
|
|
96
|
-
"stream_mode": "ui.stream_mode",
|
|
97
99
|
"graph_name": "agent.graph_name",
|
|
98
100
|
"verbose": "ui.verbose",
|
|
99
101
|
"async_mode": "ui.async_mode",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: langstage-cli
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.9
|
|
4
4
|
Summary: The terminal stage for your LangGraph agent — Claude Code-style CLI for any CompiledGraph
|
|
5
5
|
Author-email: Kedar Dabhadkar <kdabhadk@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -148,9 +148,6 @@ langstage-cli
|
|
|
148
148
|
|
|
149
149
|
# Working directory
|
|
150
150
|
export LANGSTAGE_WORKSPACE_ROOT="/path/to/workspace"
|
|
151
|
-
|
|
152
|
-
# Stream mode (updates or messages)
|
|
153
|
-
export LANGSTAGE_STREAM_MODE="updates"
|
|
154
151
|
```
|
|
155
152
|
|
|
156
153
|
## Configuration Files
|
|
@@ -179,7 +176,6 @@ root = "."
|
|
|
179
176
|
[ui]
|
|
180
177
|
verbose = true
|
|
181
178
|
async_mode = false
|
|
182
|
-
stream_mode = "auto" # auto | updates | messages
|
|
183
179
|
|
|
184
180
|
[configurable]
|
|
185
181
|
# seeds LangGraph RunnableConfig.configurable
|
|
@@ -200,8 +196,6 @@ Options:
|
|
|
200
196
|
-f, --file PATH Read message from a file (any extension)
|
|
201
197
|
--interactive/--no-interactive Handle interrupts (default: interactive)
|
|
202
198
|
--async-mode/--sync-mode Async streaming (default: sync)
|
|
203
|
-
--stream-mode [auto|updates|messages]
|
|
204
|
-
Stream mode (default: auto)
|
|
205
199
|
-v, --verbose Verbose output
|
|
206
200
|
--demo Run with the built-in keyless demo agent
|
|
207
201
|
--show-config Print the resolved configuration and exit
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "langstage-cli"
|
|
7
|
-
version = "0.6.
|
|
7
|
+
version = "0.6.9"
|
|
8
8
|
description = "The terminal stage for your LangGraph agent — Claude Code-style CLI for any CompiledGraph"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -21,7 +21,9 @@ def test_help_leads_with_canonical_names():
|
|
|
21
21
|
# canonical LANGSTAGE_* / langstage.toml are present...
|
|
22
22
|
assert "LANGSTAGE_AGENT_SPEC" in out
|
|
23
23
|
assert "LANGSTAGE_WORKSPACE_ROOT" in out
|
|
24
|
-
assert "LANGSTAGE_STREAM_MODE" in out
|
|
25
24
|
assert "langstage.toml" in out
|
|
25
|
+
# ...and the deprecated, no-op stream-mode knob is no longer advertised (gh #62)
|
|
26
|
+
assert "LANGSTAGE_STREAM_MODE" not in out
|
|
27
|
+
assert "--stream-mode" not in out
|
|
26
28
|
# ...and the legacy names are no longer presented as the only config vocab
|
|
27
29
|
assert "DEEPAGENT_AGENT_SPEC" not in out
|
|
@@ -28,24 +28,26 @@ def test_defaults(isolated, tmp_path):
|
|
|
28
28
|
assert cfg.agent_spec is None
|
|
29
29
|
|
|
30
30
|
|
|
31
|
-
def
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
def test_stream_mode_env_and_toml_are_deprecated_and_ignored(isolated, tmp_path):
|
|
32
|
+
# gh #62: stream_mode has no effect since the AG-UI migration, so it is no longer
|
|
33
|
+
# resolved from LANGSTAGE_STREAM_MODE / [ui] stream_mode — both are now ignored and
|
|
34
|
+
# the field stays at its default.
|
|
35
|
+
_toml(tmp_path, '[ui]\nstream_mode = "messages"\n')
|
|
36
|
+
cfg = CodeConfig.resolve(env={"LANGSTAGE_STREAM_MODE": "updates"}, toml_start=tmp_path)
|
|
37
|
+
assert cfg.stream_mode == "auto" # default, neither env nor TOML applied
|
|
38
|
+
assert cfg.sources["stream_mode"] == "default"
|
|
35
39
|
|
|
36
40
|
|
|
37
41
|
def test_toml_keys(isolated, tmp_path):
|
|
38
42
|
_toml(
|
|
39
43
|
tmp_path,
|
|
40
|
-
'[agent]\nspec = "a.py:g"\ngraph_name = "myg"\n'
|
|
41
|
-
'[ui]\nverbose = true\nasync_mode = true\nstream_mode = "values"\n',
|
|
44
|
+
'[agent]\nspec = "a.py:g"\ngraph_name = "myg"\n[ui]\nverbose = true\nasync_mode = true\n',
|
|
42
45
|
)
|
|
43
46
|
cfg = CodeConfig.resolve(env={}, toml_start=tmp_path)
|
|
44
47
|
assert cfg.agent_spec == "a.py:g"
|
|
45
48
|
assert cfg.graph_name == "myg"
|
|
46
49
|
assert cfg.verbose is True
|
|
47
50
|
assert cfg.async_mode is True
|
|
48
|
-
assert cfg.stream_mode == "values"
|
|
49
51
|
|
|
50
52
|
|
|
51
53
|
def test_deepagent_spec_alias_is_deprecated(isolated, tmp_path):
|
|
@@ -75,5 +77,6 @@ def test_overrides_win(isolated, tmp_path):
|
|
|
75
77
|
def test_describe_lists_var_names(isolated, tmp_path):
|
|
76
78
|
text = CodeConfig.resolve(env={}, toml_start=tmp_path).describe()
|
|
77
79
|
assert "DEEPAGENT_AGENT_SPEC" in text
|
|
78
|
-
|
|
79
|
-
|
|
80
|
+
# A field with a live TOML key is listed with its key (stream_mode's env/TOML
|
|
81
|
+
# were removed as a deprecated no-op — gh #62 — so it's no longer var-backed).
|
|
82
|
+
assert "graph_name" in text
|
|
@@ -14,13 +14,10 @@ from langstage_cli.cli import main
|
|
|
14
14
|
|
|
15
15
|
def test_show_config_reflects_cli_flags():
|
|
16
16
|
with CliRunner().isolated_filesystem(): # no stray toml
|
|
17
|
-
r = CliRunner().invoke(
|
|
18
|
-
main, ["-a", "fromcli.py:graph", "-v", "--stream-mode", "messages", "--show-config"]
|
|
19
|
-
)
|
|
17
|
+
r = CliRunner().invoke(main, ["-a", "fromcli.py:graph", "-v", "--show-config"])
|
|
20
18
|
assert r.exit_code == 0, r.output
|
|
21
19
|
# CLI-set values appear with the [override] source, not [default].
|
|
22
20
|
assert re.search(r"agent_spec\s*=\s*fromcli\.py:graph\s*\[override\]", r.output), r.output
|
|
23
|
-
assert re.search(r"stream_mode\s*=\s*messages\s*\[override\]", r.output), r.output
|
|
24
21
|
assert re.search(r"verbose\s*=\s*True\s*\[override\]", r.output), r.output
|
|
25
22
|
|
|
26
23
|
|
|
@@ -49,15 +46,15 @@ def test_show_config_without_flags_still_reports_env():
|
|
|
49
46
|
|
|
50
47
|
def test_show_config_omits_server_only_keys():
|
|
51
48
|
"""The terminal CLI starts no server and titles the header from the graph
|
|
52
|
-
name, so host/port/debug/title are inert and must not be advertised
|
|
49
|
+
name, so host/port/debug/title are inert and must not be advertised (gh #36).
|
|
50
|
+
stream_mode is likewise omitted — it's a deprecated no-op (gh #62)."""
|
|
53
51
|
with CliRunner().isolated_filesystem():
|
|
54
52
|
r = CliRunner().invoke(main, ["--show-config"])
|
|
55
53
|
assert r.exit_code == 0, r.output
|
|
56
|
-
for key in ("host", "port", "debug", "title"):
|
|
54
|
+
for key in ("host", "port", "debug", "title", "stream_mode"):
|
|
57
55
|
assert not re.search(rf"^\s*{key}\s*=", r.output, re.MULTILINE), f"{key} should be omitted"
|
|
58
56
|
# ...but keys the CLI actually honors are still shown.
|
|
59
57
|
assert re.search(r"^\s*agent_spec\s*=", r.output, re.MULTILINE), r.output
|
|
60
|
-
assert re.search(r"^\s*stream_mode\s*=", r.output, re.MULTILINE), r.output
|
|
61
58
|
|
|
62
59
|
|
|
63
60
|
def test_show_config_title_env_not_advertised_as_effective():
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""--stream-mode is deprecated and inert (gh #62).
|
|
2
|
+
|
|
3
|
+
`--stream-mode {auto,updates,messages}` (and `LANGSTAGE_STREAM_MODE` / `[ui]
|
|
4
|
+
stream_mode`) was advertised as three streaming behaviors, but since the AG-UI
|
|
5
|
+
streaming migration all three render identically — the setting has no effect. The
|
|
6
|
+
dead knob is now: hidden from `--help`, omitted from `--show-config`, no longer
|
|
7
|
+
resolved from env / TOML, and accepted-and-ignored on the CLI (so existing
|
|
8
|
+
`--stream-mode X` invocations don't hard-error) with a one-line deprecation notice.
|
|
9
|
+
|
|
10
|
+
The unrelated "a finished AIMessage still renders" behavior (the reason the old
|
|
11
|
+
default was 'auto') is intrinsic to the AG-UI path now and is kept as a regression.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import textwrap
|
|
15
|
+
|
|
16
|
+
from click.testing import CliRunner
|
|
17
|
+
|
|
18
|
+
from langstage_cli.cli import main
|
|
19
|
+
|
|
20
|
+
# A graph whose node returns a *finished* AIMessage (no token streaming).
|
|
21
|
+
FINISHED_AIMESSAGE_AGENT = textwrap.dedent(
|
|
22
|
+
"""
|
|
23
|
+
from langchain_core.messages import AIMessage
|
|
24
|
+
from langgraph.graph import START, END, StateGraph, MessagesState
|
|
25
|
+
|
|
26
|
+
def _respond(state):
|
|
27
|
+
return {"messages": [AIMessage(content="FINISHED_MARKER_42")]}
|
|
28
|
+
|
|
29
|
+
builder = StateGraph(MessagesState)
|
|
30
|
+
builder.add_node("respond", _respond)
|
|
31
|
+
builder.add_edge(START, "respond")
|
|
32
|
+
builder.add_edge("respond", END)
|
|
33
|
+
graph = builder.compile()
|
|
34
|
+
"""
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_finished_aimessage_still_renders(tmp_path, monkeypatch):
|
|
39
|
+
"""Regression: a finished (non-token-streamed) AIMessage's content must render."""
|
|
40
|
+
(tmp_path / "fin_agent.py").write_text(FINISHED_AIMESSAGE_AGENT)
|
|
41
|
+
monkeypatch.chdir(tmp_path)
|
|
42
|
+
r = CliRunner().invoke(main, ["-a", "fin_agent.py:graph", "--no-interactive", "ping"])
|
|
43
|
+
assert r.exit_code == 0, r.output
|
|
44
|
+
assert "FINISHED_MARKER_42" in r.output
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_stream_mode_is_omitted_from_show_config():
|
|
48
|
+
# gh #62: the deprecated no-op knob is no longer advertised in --show-config.
|
|
49
|
+
r = CliRunner().invoke(main, ["--show-config"])
|
|
50
|
+
assert r.exit_code == 0, r.output
|
|
51
|
+
assert "stream_mode" not in r.output
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_stream_mode_flag_is_accepted_and_ignored_with_a_notice():
|
|
55
|
+
# An existing `--stream-mode X` invocation must not hard-error; it is accepted,
|
|
56
|
+
# ignored, and prints a one-line deprecation notice.
|
|
57
|
+
r = CliRunner().invoke(main, ["--demo", "--no-interactive", "--stream-mode", "messages", "hi"])
|
|
58
|
+
assert r.exit_code == 0, r.output
|
|
59
|
+
assert "deprecated" in r.output.lower()
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_stream_mode_env_is_ignored_not_rejected():
|
|
63
|
+
# LANGSTAGE_STREAM_MODE used to be validated (and an unsupported value exited 2).
|
|
64
|
+
# It is now simply ignored — a run proceeds normally regardless of its value.
|
|
65
|
+
r = CliRunner().invoke(
|
|
66
|
+
main,
|
|
67
|
+
["--demo", "--no-interactive", "hi"],
|
|
68
|
+
env={"LANGSTAGE_STREAM_MODE": "values"},
|
|
69
|
+
)
|
|
70
|
+
assert r.exit_code == 0, r.output
|
|
71
|
+
assert "unsupported stream mode" not in r.output.lower()
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
"""--stream-mode validation + the 'auto' default (gh #-dogfood).
|
|
2
|
-
|
|
3
|
-
`values` was advertised but unsupported by the CLI's render path; passing it
|
|
4
|
-
crashed with a fatal interpreter-shutdown error (a ValueError surfaced while the
|
|
5
|
-
spinner daemon thread held stdout). Now: the flag is a Choice {auto,updates,
|
|
6
|
-
messages}, and the resolved value (incl. LANGSTAGE_STREAM_MODE, which bypasses
|
|
7
|
-
the flag) is validated up front with a clean error.
|
|
8
|
-
|
|
9
|
-
Separately, single 'messages' mode only carries LLM *token* streams, so a node
|
|
10
|
-
that returns a finished (non-token-streamed) AIMessage rendered an empty turn.
|
|
11
|
-
The default is now 'auto' (dual updates+messages) so that agent shape — the one
|
|
12
|
-
the README's own "Creating Your Own Agent" example produces — still renders.
|
|
13
|
-
"""
|
|
14
|
-
|
|
15
|
-
import textwrap
|
|
16
|
-
|
|
17
|
-
from click.testing import CliRunner
|
|
18
|
-
|
|
19
|
-
from langstage_cli.cli import main
|
|
20
|
-
|
|
21
|
-
# A graph whose node returns a *finished* AIMessage (no token streaming) — the
|
|
22
|
-
# shape that was a silent blank turn under bare 'messages'.
|
|
23
|
-
FINISHED_AIMESSAGE_AGENT = textwrap.dedent(
|
|
24
|
-
"""
|
|
25
|
-
from langchain_core.messages import AIMessage
|
|
26
|
-
from langgraph.graph import START, END, StateGraph, MessagesState
|
|
27
|
-
|
|
28
|
-
def _respond(state):
|
|
29
|
-
return {"messages": [AIMessage(content="FINISHED_MARKER_42")]}
|
|
30
|
-
|
|
31
|
-
builder = StateGraph(MessagesState)
|
|
32
|
-
builder.add_node("respond", _respond)
|
|
33
|
-
builder.add_edge(START, "respond")
|
|
34
|
-
builder.add_edge("respond", END)
|
|
35
|
-
graph = builder.compile()
|
|
36
|
-
"""
|
|
37
|
-
)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def test_default_auto_renders_finished_aimessage(tmp_path, monkeypatch):
|
|
41
|
-
"""Regression: the default mode must render a finished AIMessage's content."""
|
|
42
|
-
(tmp_path / "fin_agent.py").write_text(FINISHED_AIMESSAGE_AGENT)
|
|
43
|
-
monkeypatch.chdir(tmp_path)
|
|
44
|
-
r = CliRunner().invoke(main, ["-a", "fin_agent.py:graph", "--no-interactive", "ping"])
|
|
45
|
-
assert r.exit_code == 0, r.output
|
|
46
|
-
assert "FINISHED_MARKER_42" in r.output
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def test_default_stream_mode_is_auto():
|
|
50
|
-
r = CliRunner().invoke(main, ["--show-config"])
|
|
51
|
-
assert r.exit_code == 0, r.output
|
|
52
|
-
assert "auto" in r.output
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def test_stream_mode_values_flag_rejected_cleanly():
|
|
56
|
-
r = CliRunner().invoke(main, ["--demo", "--no-interactive", "--stream-mode", "values", "hi"])
|
|
57
|
-
assert r.exit_code != 0
|
|
58
|
-
assert "values" in r.output
|
|
59
|
-
assert "Fatal" not in r.output # no interpreter-shutdown crash
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
def test_stream_mode_messages_flag_accepted():
|
|
63
|
-
# A valid mode must still be accepted by the Choice (parses, doesn't error on the flag).
|
|
64
|
-
r = CliRunner().invoke(main, ["--stream-mode", "messages", "--show-config"])
|
|
65
|
-
assert r.exit_code == 0, r.output
|
|
66
|
-
assert "messages" in r.output
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
def test_stream_mode_values_via_env_rejected():
|
|
70
|
-
r = CliRunner().invoke(
|
|
71
|
-
main,
|
|
72
|
-
["--demo", "--no-interactive", "hi"],
|
|
73
|
-
env={"LANGSTAGE_STREAM_MODE": "values"},
|
|
74
|
-
)
|
|
75
|
-
assert r.exit_code == 2, r.output
|
|
76
|
-
assert "unsupported stream mode" in r.output.lower()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|