langstage-cli 0.6.44__tar.gz → 0.6.45__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 (72) hide show
  1. {langstage_cli-0.6.44/langstage_cli.egg-info → langstage_cli-0.6.45}/PKG-INFO +1 -1
  2. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/langstage_cli/cli.py +22 -11
  3. {langstage_cli-0.6.44 → langstage_cli-0.6.45/langstage_cli.egg-info}/PKG-INFO +1 -1
  4. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/langstage_cli.egg-info/SOURCES.txt +1 -0
  5. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/pyproject.toml +1 -1
  6. langstage_cli-0.6.45/tests/test_tool_card_args.py +100 -0
  7. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/LICENSE +0 -0
  8. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/README.md +0 -0
  9. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/deepagent_code/__init__.py +0 -0
  10. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/langstage_cli/__init__.py +0 -0
  11. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/langstage_cli/agui_stream.py +0 -0
  12. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/langstage_cli/config.py +0 -0
  13. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/langstage_cli/exit_codes.py +0 -0
  14. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/langstage_cli/sessions.py +0 -0
  15. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/langstage_cli.egg-info/dependency_links.txt +0 -0
  16. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/langstage_cli.egg-info/entry_points.txt +0 -0
  17. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/langstage_cli.egg-info/requires.txt +0 -0
  18. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/langstage_cli.egg-info/top_level.txt +0 -0
  19. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/setup.cfg +0 -0
  20. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_advertised_wave4.py +0 -0
  21. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_agui_stream.py +0 -0
  22. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_approval_menu_abort.py +0 -0
  23. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_async_mode.py +0 -0
  24. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_checkpointer.py +0 -0
  25. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_cli.py +0 -0
  26. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_cli_help.py +0 -0
  27. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_codeconfig.py +0 -0
  28. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_config.py +0 -0
  29. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_config_command.py +0 -0
  30. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_configurable.py +0 -0
  31. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_deferred_pass.py +0 -0
  32. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_dumb_term.py +0 -0
  33. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_exit_codes.py +0 -0
  34. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_generic_interrupt.py +0 -0
  35. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_help_render.py +0 -0
  36. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_history_persist.py +0 -0
  37. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_hitl_approval_preview.py +0 -0
  38. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_hitl_resume_warning.py +0 -0
  39. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_init.py +0 -0
  40. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_interrupt_non_tty.py +0 -0
  41. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_legacy_config_home.py +0 -0
  42. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_load_error.py +0 -0
  43. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_message_boundaries.py +0 -0
  44. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_no_color.py +0 -0
  45. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_no_interactive_approve.py +0 -0
  46. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_no_persist_resume.py +0 -0
  47. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_persist_env.py +0 -0
  48. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_quiet_output.py +0 -0
  49. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_reasoning_render.py +0 -0
  50. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_rename_shim.py +0 -0
  51. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_render_markdown.py +0 -0
  52. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_session_lifecycle.py +0 -0
  53. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_sessions.py +0 -0
  54. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_show_config.py +0 -0
  55. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_show_config_malformed.py +0 -0
  56. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_spec_loading_core.py +0 -0
  57. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_spec_resolution.py +0 -0
  58. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_spinner_reply_survives.py +0 -0
  59. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_stdin_interactive.py +0 -0
  60. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_stdin_single_shot.py +0 -0
  61. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_stream_mode.py +0 -0
  62. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_streaming_marker.py +0 -0
  63. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_terminal_width.py +0 -0
  64. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_toml_relative_paths.py +0 -0
  65. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_tool_output_escape.py +0 -0
  66. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_turn_error_verbose.py +0 -0
  67. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_turn_exit_and_render.py +0 -0
  68. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_unicode_console.py +0 -0
  69. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_verbose_command.py +0 -0
  70. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_verify_flag.py +0 -0
  71. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_workspace.py +0 -0
  72. {langstage_cli-0.6.44 → langstage_cli-0.6.45}/tests/test_wrong_type_graph.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: langstage-cli
3
- Version: 0.6.44
3
+ Version: 0.6.45
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
@@ -1140,20 +1140,31 @@ def _absolutize_file_spec(spec: str) -> str:
1140
1140
  return f"{Path(path_part).expanduser().resolve()}{suffix}"
1141
1141
 
1142
1142
 
1143
- def get_tool_arg_preview(args: Dict[str, Any]) -> str:
1144
- """Get a preview of the first argument value (nanocode style).
1143
+ # Per-value cap on the tool-call card (default output; ``-v`` shows values in full).
1144
+ _TOOL_ARG_VALUE_MAX = 50
1145
1145
 
1146
- The value is escaped before it is truncated, so a tool arg can't move the cursor
1147
- or retitle the terminal, and a cut can't leave half an escape sequence live (gh #180).
1146
+
1147
+ def get_tool_arg_preview(args: Dict[str, Any], verbose: bool = False) -> str:
1148
+ """The arguments a tool was called with, for the tool-call card.
1149
+
1150
+ A lone argument shows as its bare value (``read_file └─ docs/a.md``). Several show
1151
+ as ``name=value`` pairs, every one of them: showing only the first misstated the
1152
+ call, ``add(a=17, b=25)`` read as "add 17" (gh #184). Each value is capped at 50
1153
+ characters unless ``verbose``. Values are escaped before they are truncated, so a
1154
+ tool arg can't move the cursor or retitle the terminal, and a cut can't leave half
1155
+ an escape sequence live (gh #180).
1148
1156
  """
1149
1157
  if not args:
1150
1158
  return ""
1151
- # Get first value
1152
- first_val = _escape_controls(str(list(args.values())[0]))
1153
- # Truncate if needed
1154
- if len(first_val) > 50:
1155
- return first_val[:50] + "..."
1156
- return first_val
1159
+ limit = None if verbose else _TOOL_ARG_VALUE_MAX
1160
+ if not isinstance(args, dict):
1161
+ return _approval_text(args, limit)
1162
+ if len(args) == 1:
1163
+ (value,) = args.values()
1164
+ return _approval_text(str(value), limit)
1165
+ return ", ".join(
1166
+ f"{_approval_text(str(k), None)}={_approval_text(v, limit)}" for k, v in args.items()
1167
+ )
1157
1168
 
1158
1169
 
1159
1170
  # Per-value cap for the HITL approval preview. Values are truncated, never dropped: the
@@ -1495,7 +1506,7 @@ def print_chunk(chunk: Dict[str, Any], verbose: bool = False):
1495
1506
  for tool_call in chunk["tool_calls"]:
1496
1507
  tool_name = _escape_controls(str(tool_call["name"])) # gh #180
1497
1508
  args = tool_call.get("args", {})
1498
- arg_preview = get_tool_arg_preview(args)
1509
+ arg_preview = get_tool_arg_preview(args, verbose)
1499
1510
 
1500
1511
  safe_print(f"\n{GREEN}● {tool_name}{RESET}")
1501
1512
  if arg_preview:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: langstage-cli
3
- Version: 0.6.44
3
+ Version: 0.6.45
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
@@ -59,6 +59,7 @@ tests/test_stream_mode.py
59
59
  tests/test_streaming_marker.py
60
60
  tests/test_terminal_width.py
61
61
  tests/test_toml_relative_paths.py
62
+ tests/test_tool_card_args.py
62
63
  tests/test_tool_output_escape.py
63
64
  tests/test_turn_error_verbose.py
64
65
  tests/test_turn_exit_and_render.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "langstage-cli"
7
- version = "0.6.44"
7
+ version = "0.6.45"
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"
@@ -0,0 +1,100 @@
1
+ """The tool-call card shows every argument, by name (gh #184).
2
+
3
+ It used to show only the first argument's value, so ``add(a=17, b=25)`` rendered as
4
+ ``add └─ 17`` and ``transfer_funds(to, amount, currency)`` as ``└─ alice``, in default
5
+ and ``-v`` output alike. Under ``--no-interactive`` the card is the only record of
6
+ what ran.
7
+ """
8
+
9
+ import io
10
+ import re
11
+ from contextlib import redirect_stdout
12
+
13
+ import pytest
14
+
15
+ pytest.importorskip("ag_ui_langgraph")
16
+
17
+ from langchain_core.messages import AIMessage, ToolMessage # noqa: E402
18
+ from langgraph.graph import END, START, StateGraph # noqa: E402
19
+ from langgraph.graph.message import MessagesState # noqa: E402
20
+
21
+ from langstage_cli import cli # noqa: E402
22
+ from langstage_cli.agui_stream import build_session_agent # noqa: E402
23
+ from langstage_cli.cli import get_tool_arg_preview, run_single_turn_agui # noqa: E402
24
+
25
+ _SGR = re.compile(r"\x1b\[[0-9;]*m")
26
+ TRANSFER = {"to": "alice", "amount": 1000000, "currency": "USD"}
27
+
28
+
29
+ def test_every_argument_is_named():
30
+ assert get_tool_arg_preview({"a": 17, "b": 25}) == "a=17, b=25"
31
+ assert get_tool_arg_preview(TRANSFER) == "to=alice, amount=1000000, currency=USD"
32
+
33
+
34
+ def test_a_single_argument_stays_a_bare_value():
35
+ assert get_tool_arg_preview({"path": "docs/a.md"}) == "docs/a.md"
36
+ assert get_tool_arg_preview({}) == ""
37
+
38
+
39
+ def test_values_are_capped_but_never_dropped():
40
+ long = "x" * 200
41
+ preview = get_tool_arg_preview({"path": "a.md", "content": long, "mode": "w"})
42
+ assert preview == f"path=a.md, content={'x' * 50}..., mode=w"
43
+ # Structured values read as JSON, not Python reprs.
44
+ assert (
45
+ get_tool_arg_preview({"ids": [1, 2], "opts": {"k": "v"}}) == 'ids=[1, 2], opts={"k": "v"}'
46
+ )
47
+
48
+
49
+ def test_verbose_shows_values_in_full():
50
+ long = "y" * 200
51
+ assert (
52
+ get_tool_arg_preview({"path": "a.md", "content": long}, verbose=True)
53
+ == f"path=a.md, content={long}"
54
+ )
55
+ assert get_tool_arg_preview({"q": long}, verbose=True) == long
56
+
57
+
58
+ def test_names_and_values_are_escaped():
59
+ preview = get_tool_arg_preview({"p\x1b[2K": "a\x1b]0;t\x07", "b": "c\nd"})
60
+ assert "\x1b" not in preview and "\x07" not in preview and "\n" not in preview
61
+ assert preview == r"p\x1b[2K=a\x1b]0;t\x07, b=c\nd"
62
+
63
+
64
+ def _transfer_graph():
65
+ def call(state):
66
+ tc = {"id": "t1", "name": "transfer_funds", "args": TRANSFER}
67
+ return {"messages": [AIMessage(content="", tool_calls=[tc])]}
68
+
69
+ def tool(state):
70
+ return {"messages": [ToolMessage(content="ok", tool_call_id="t1")]}
71
+
72
+ def reply(state):
73
+ return {"messages": [AIMessage(content="Transferred.")]}
74
+
75
+ g = StateGraph(MessagesState)
76
+ for n, f in [("call", call), ("tool", tool), ("reply", reply)]:
77
+ g.add_node(n, f)
78
+ g.add_edge(START, "call")
79
+ g.add_edge("call", "tool")
80
+ g.add_edge("tool", "reply")
81
+ g.add_edge("reply", END)
82
+ return g.compile()
83
+
84
+
85
+ @pytest.mark.parametrize("verbose", [False, True])
86
+ async def test_card_in_a_real_turn(monkeypatch, verbose):
87
+ """The issue's keyless repro, through the AG-UI stream the CLI renders."""
88
+ monkeypatch.setattr(cli, "Spinner", lambda *a, **k: None)
89
+ buf = io.StringIO()
90
+ with redirect_stdout(buf):
91
+ await run_single_turn_agui(
92
+ build_session_agent(_transfer_graph()),
93
+ "pay alice",
94
+ "t-184",
95
+ interactive=False,
96
+ verbose=verbose,
97
+ )
98
+ plain = _SGR.sub("", buf.getvalue())
99
+ assert "● transfer_funds" in plain, plain
100
+ assert "└─ to=alice, amount=1000000, currency=USD" in plain, plain
File without changes
File without changes
File without changes