langstage-cli 0.6.8__tar.gz → 0.6.10__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.10}/PKG-INFO +1 -7
- {langstage_cli-0.6.8 → langstage_cli-0.6.10}/README.md +0 -6
- {langstage_cli-0.6.8 → langstage_cli-0.6.10}/langstage_cli/cli.py +43 -42
- {langstage_cli-0.6.8 → langstage_cli-0.6.10}/langstage_cli/config.py +6 -4
- {langstage_cli-0.6.8 → langstage_cli-0.6.10/langstage_cli.egg-info}/PKG-INFO +1 -7
- {langstage_cli-0.6.8 → langstage_cli-0.6.10}/langstage_cli.egg-info/SOURCES.txt +1 -0
- {langstage_cli-0.6.8 → langstage_cli-0.6.10}/pyproject.toml +1 -1
- {langstage_cli-0.6.8 → langstage_cli-0.6.10}/tests/test_cli_help.py +3 -1
- {langstage_cli-0.6.8 → langstage_cli-0.6.10}/tests/test_codeconfig.py +12 -9
- langstage_cli-0.6.10/tests/test_config_command.py +40 -0
- {langstage_cli-0.6.8 → langstage_cli-0.6.10}/tests/test_show_config.py +4 -7
- langstage_cli-0.6.10/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.10}/LICENSE +0 -0
- {langstage_cli-0.6.8 → langstage_cli-0.6.10}/deepagent_code/__init__.py +0 -0
- {langstage_cli-0.6.8 → langstage_cli-0.6.10}/langstage_cli/__init__.py +0 -0
- {langstage_cli-0.6.8 → langstage_cli-0.6.10}/langstage_cli/agui_stream.py +0 -0
- {langstage_cli-0.6.8 → langstage_cli-0.6.10}/langstage_cli.egg-info/dependency_links.txt +0 -0
- {langstage_cli-0.6.8 → langstage_cli-0.6.10}/langstage_cli.egg-info/entry_points.txt +0 -0
- {langstage_cli-0.6.8 → langstage_cli-0.6.10}/langstage_cli.egg-info/requires.txt +0 -0
- {langstage_cli-0.6.8 → langstage_cli-0.6.10}/langstage_cli.egg-info/top_level.txt +0 -0
- {langstage_cli-0.6.8 → langstage_cli-0.6.10}/setup.cfg +0 -0
- {langstage_cli-0.6.8 → langstage_cli-0.6.10}/tests/test_agui_stream.py +0 -0
- {langstage_cli-0.6.8 → langstage_cli-0.6.10}/tests/test_checkpointer.py +0 -0
- {langstage_cli-0.6.8 → langstage_cli-0.6.10}/tests/test_cli.py +0 -0
- {langstage_cli-0.6.8 → langstage_cli-0.6.10}/tests/test_config.py +0 -0
- {langstage_cli-0.6.8 → langstage_cli-0.6.10}/tests/test_configurable.py +0 -0
- {langstage_cli-0.6.8 → langstage_cli-0.6.10}/tests/test_help_render.py +0 -0
- {langstage_cli-0.6.8 → langstage_cli-0.6.10}/tests/test_no_interactive_approve.py +0 -0
- {langstage_cli-0.6.8 → langstage_cli-0.6.10}/tests/test_quiet_output.py +0 -0
- {langstage_cli-0.6.8 → langstage_cli-0.6.10}/tests/test_rename_shim.py +0 -0
- {langstage_cli-0.6.8 → langstage_cli-0.6.10}/tests/test_spec_resolution.py +0 -0
- {langstage_cli-0.6.8 → langstage_cli-0.6.10}/tests/test_streaming_marker.py +0 -0
- {langstage_cli-0.6.8 → langstage_cli-0.6.10}/tests/test_turn_exit_and_render.py +0 -0
- {langstage_cli-0.6.8 → langstage_cli-0.6.10}/tests/test_unicode_console.py +0 -0
- {langstage_cli-0.6.8 → langstage_cli-0.6.10}/tests/test_verify_flag.py +0 -0
- {langstage_cli-0.6.8 → langstage_cli-0.6.10}/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.10
|
|
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()
|
|
@@ -1017,14 +1002,17 @@ def cmd_config(args: str, context: Dict[str, Any]) -> Optional[str]:
|
|
|
1017
1002
|
print(f" {DIM}TOML sources:{RESET} {DIM}(none — using defaults){RESET}")
|
|
1018
1003
|
|
|
1019
1004
|
# Full resolved view: each value, where it came from, and the env var
|
|
1020
|
-
# / TOML key that sets it.
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
#
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1005
|
+
# / TOML key that sets it. Prefer the snapshot captured at startup (before
|
|
1006
|
+
# apply_workspace self-published LANGSTAGE_WORKSPACE_ROOT), so workspace_root's
|
|
1007
|
+
# source is reported truthfully and /config agrees with --show-config (gh #64).
|
|
1008
|
+
# Fall back to a fresh resolve only if the snapshot is somehow absent.
|
|
1009
|
+
report = config.get("_resolved_config_report")
|
|
1010
|
+
if report is None:
|
|
1011
|
+
from langstage_cli.config import CodeConfig
|
|
1012
|
+
|
|
1013
|
+
# Omit the inherited server/web keys the terminal CLI never honors (gh #36).
|
|
1014
|
+
report = CodeConfig.resolve().describe(omit_keys=_INERT_KEYS)
|
|
1015
|
+
for line in report.splitlines():
|
|
1028
1016
|
print(f" {line}")
|
|
1029
1017
|
|
|
1030
1018
|
configurable = config.get("configurable", {})
|
|
@@ -1483,9 +1471,11 @@ def run_conversation_loop(
|
|
|
1483
1471
|
@click.option(
|
|
1484
1472
|
"--stream-mode",
|
|
1485
1473
|
type=click.Choice(["auto", "updates", "messages"]),
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1474
|
+
# DEPRECATED (gh #62): a no-op since the AG-UI streaming migration — all three
|
|
1475
|
+
# modes render identically. Kept hidden + accepted so existing `--stream-mode X`
|
|
1476
|
+
# invocations don't hard-error; a one-line notice fires when it is passed.
|
|
1477
|
+
hidden=True,
|
|
1478
|
+
help="(deprecated: no effect since the AG-UI streaming migration).",
|
|
1489
1479
|
)
|
|
1490
1480
|
@click.option(
|
|
1491
1481
|
"--verbose",
|
|
@@ -1566,7 +1556,6 @@ def main(
|
|
|
1566
1556
|
\b
|
|
1567
1557
|
- LANGSTAGE_AGENT_SPEC: Agent location (same formats as above).
|
|
1568
1558
|
- LANGSTAGE_WORKSPACE_ROOT: Working directory for the agent
|
|
1569
|
-
- LANGSTAGE_STREAM_MODE: Stream mode for LangGraph (auto, updates, or messages)
|
|
1570
1559
|
|
|
1571
1560
|
Reads ~/.langstage/config.toml (global) and langstage.toml (project,
|
|
1572
1561
|
walks up from cwd). Precedence: CLI args > env vars > project TOML >
|
|
@@ -1629,6 +1618,14 @@ def main(
|
|
|
1629
1618
|
"verbose": True if verbose else None,
|
|
1630
1619
|
}
|
|
1631
1620
|
|
|
1621
|
+
# --stream-mode is deprecated and inert since the AG-UI streaming migration
|
|
1622
|
+
# (gh #62): accepted so scripts don't break, but say so once when it's passed.
|
|
1623
|
+
if stream_mode is not None:
|
|
1624
|
+
_status(
|
|
1625
|
+
f"{DIM}⏺ Note: --stream-mode is deprecated and has no effect "
|
|
1626
|
+
f"(streaming is uniform since the AG-UI migration).{RESET}"
|
|
1627
|
+
)
|
|
1628
|
+
|
|
1632
1629
|
if show_config:
|
|
1633
1630
|
# See _INERT_KEYS: hide the server/web keys this surface ignores. (gh #36)
|
|
1634
1631
|
print(
|
|
@@ -1669,18 +1666,19 @@ def main(
|
|
|
1669
1666
|
toml_start=Path.cwd(),
|
|
1670
1667
|
overrides=cli_overrides,
|
|
1671
1668
|
)
|
|
1669
|
+
# Snapshot the resolved-config diagnostic NOW, before apply_workspace() below
|
|
1670
|
+
# self-publishes LANGSTAGE_WORKSPACE_ROOT into os.environ (ADR 0005). The
|
|
1671
|
+
# interactive /config used to re-resolve at display time, see the tool's own
|
|
1672
|
+
# published var, and misreport workspace_root's source as [env:...] — diverging
|
|
1673
|
+
# from --show-config (which runs before apply_workspace). Reuse this snapshot so
|
|
1674
|
+
# /config shows the true provenance. (gh #64)
|
|
1675
|
+
resolved_config_report = cfg.describe(omit_keys=_INERT_KEYS)
|
|
1672
1676
|
final_spec = cfg.agent_spec
|
|
1673
1677
|
final_graph_name_default = cfg.graph_name
|
|
1678
|
+
# stream_mode is deprecated and inert (gh #62) — it only ever comes from the
|
|
1679
|
+
# accepted-and-ignored flag now (env/TOML no longer resolve it), so there is
|
|
1680
|
+
# nothing to validate; it changes no rendering.
|
|
1674
1681
|
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
1682
|
use_async = cfg.async_mode
|
|
1685
1683
|
verbose = cfg.verbose
|
|
1686
1684
|
# Whether a workspace root was explicitly configured (vs the default cwd);
|
|
@@ -1747,6 +1745,9 @@ def main(
|
|
|
1747
1745
|
|
|
1748
1746
|
# Expose TOML sources to slash commands via the config dict
|
|
1749
1747
|
config_dict["_toml_sources"] = [str(p) for p in toml_sources]
|
|
1748
|
+
# The resolved-config diagnostic snapshotted before apply_workspace, so /config
|
|
1749
|
+
# reports the true source of workspace_root instead of the self-published env (gh #64).
|
|
1750
|
+
config_dict["_resolved_config_report"] = resolved_config_report
|
|
1750
1751
|
|
|
1751
1752
|
# Extract agent name and description from graph object
|
|
1752
1753
|
agent_name = get_agent_name(graph)
|
|
@@ -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.10
|
|
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.10"
|
|
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
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Interactive `/config` reports the true config source, matching `--show-config` (gh #64).
|
|
2
|
+
|
|
3
|
+
`/config` used to re-resolve `CodeConfig` at display time — but by then the startup path
|
|
4
|
+
had already called `apply_workspace()`, which self-publishes `LANGSTAGE_WORKSPACE_ROOT`
|
|
5
|
+
into `os.environ` (ADR 0005). The re-resolve saw the tool's own published var and reported
|
|
6
|
+
`workspace_root`'s source as `[env:LANGSTAGE_WORKSPACE_ROOT]` even when the user never set
|
|
7
|
+
it — diverging from `--show-config` (which runs before `apply_workspace`). `/config` now
|
|
8
|
+
reuses the report snapshotted at startup, before the self-publish.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from langstage_cli.cli import cmd_config
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_config_uses_startup_snapshot_not_a_reresolve(monkeypatch, capsys):
|
|
15
|
+
# Reproduce the trigger: apply_workspace has self-published the workspace env.
|
|
16
|
+
monkeypatch.setenv("LANGSTAGE_WORKSPACE_ROOT", "/abs/self/published/ws")
|
|
17
|
+
monkeypatch.setenv("DEEPAGENT_WORKSPACE_ROOT", "/abs/self/published/ws")
|
|
18
|
+
|
|
19
|
+
# The snapshot captured at startup (before the self-publish) shows the true source.
|
|
20
|
+
snapshot = (
|
|
21
|
+
"agent_spec = a.py:graph [override]\n"
|
|
22
|
+
"workspace_root = . [default] (env: LANGSTAGE_WORKSPACE_ROOT, toml: workspace.root)"
|
|
23
|
+
)
|
|
24
|
+
ctx = {
|
|
25
|
+
"config": {
|
|
26
|
+
"_resolved_config_report": snapshot,
|
|
27
|
+
"_toml_sources": [],
|
|
28
|
+
"configurable": {},
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
cmd_config("", ctx)
|
|
33
|
+
out = capsys.readouterr().out
|
|
34
|
+
|
|
35
|
+
# /config prints the snapshot's true provenance...
|
|
36
|
+
assert "workspace_root" in out
|
|
37
|
+
assert "[default]" in out
|
|
38
|
+
# ...and does NOT misreport the self-published env var as the source (the #64 bug).
|
|
39
|
+
assert "[env:LANGSTAGE_WORKSPACE_ROOT]" not in out
|
|
40
|
+
assert "/abs/self/published/ws" not in out
|
|
@@ -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
|