Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
# Changelog

## 0.6.22 - 2026-07-23

### Fixed
- **Tool calls and tool results now render for a non-token-streaming agent (gh #91).** An agent
whose messages are produced without token streaming — a custom node calling `model.invoke()`, a
non-streaming provider, a rule-based node (the "Creating Your Own Agent" shape) — delivers its
turn via the AG-UI snapshot path, and `langstage-core`'s snapshot handler yielded text only: the
`● tool_name` / `↳ result` the CLI renderer already knows how to draw never arrived, and a
tool-call-only turn rendered nothing while `--verify`/the exit code reported success. The root
cause and fix are in `langstage-core`; this release raises the floor to **>= 1.0.24**, which
emits `tool_calls`/`tool_result` on the snapshot wire, so `print_chunk` renders them. (Tool
frames remain intentionally suppressed in the quiet/scriptable single-shot path, where stdout
carries only the reply text — that "tool chatter" contract from 0.6.20 is unchanged.)
- **README: `/status` no longer documents a "sync/async mode" field (gh #90).** 0.6.21 removed that
line from the runtime (#88) and updated the CLI Options table and the `[ui] async_mode` example,
but missed the `/status` entry in the Commands list — so the docs still advertised a field the
command hasn't shown since 0.6.21. Corrected to `agent, thread, verbose, cwd`.

## 0.6.21 - 2026-07-19

### Changed
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ langstage-cli --show-config

In the interactive loop:
- `/help` (`/h`, `/?`) - Show this help message, or `/help <command>` for one command
- `/status` (`/s`) - Show session status (agent, thread, sync/async mode, verbose, cwd)
- `/status` (`/s`) - Show session status (agent, thread, verbose, cwd)
- `/version` (`/v`) - Show version and the current agent
- `/config` (`/cfg`) - Show the resolved configuration, or set a runtime key: `/config [key] [value]`
- `/verbose` - Toggle verbose output, or set it explicitly: `/verbose [on|off]`
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "langstage-cli"
version = "0.6.21"
version = "0.6.22"
description = "The terminal stage for your LangGraph agent — Claude Code-style CLI for any CompiledGraph"
readme = "README.md"
requires-python = ">=3.11"
Expand All @@ -31,7 +31,7 @@ dependencies = [
# is a HARD dep. A bare `pip install langstage-cli` must be able to run a turn.
# >=1.0.6 for the shared preflight primitive core.verify() used by --verify.
# >=1.0.14 for describe(configurable=) — the single complete config diagnostic.
"langstage-core[agui]>=1.0.14",
"langstage-core[agui]>=1.0.24",
"click>=8.0.0",
"python-dotenv",
]
Expand Down
48 changes: 48 additions & 0 deletions tests/test_agui_stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,3 +153,51 @@ async def go():
assert not any(c.get("status") == "interrupt" for c in resumed), resumed
text = "".join(c["chunk"] for c in resumed if "chunk" in c)
assert "resolved:" in text and "accept" in text


def _snapshot_tool_graph():
"""The issue #91 shape: custom nodes returning finished messages, ToolMessage
appended manually (no ToolNode) -> everything via MessagesSnapshotEvent, no
streaming ToolCall events. Before langstage-core 1.0.24 the snapshot path
dropped tool calls/results here; the >=1.0.24 floor delivers the fix."""

def call_tool(state):
return {
"messages": [
AIMessage(
content="Let me check the weather.",
tool_calls=[{"name": "get_weather", "args": {"city": "Paris"}, "id": "call_1"}],
)
]
}

def run_tool(state):
return {"messages": [ToolMessage(content="Sunny, 24C", tool_call_id="call_1")]}

def final(state):
return {"messages": [AIMessage(content="The weather in Paris is sunny, 24C.")]}

g = StateGraph(MessagesState)
for n, f in [("call_tool", call_tool), ("run_tool", run_tool), ("final", final)]:
g.add_node(n, f)
g.add_edge(START, "call_tool")
g.add_edge("call_tool", "run_tool")
g.add_edge("run_tool", "final")
g.add_edge("final", END)
return g.compile()


def test_non_streaming_tool_agent_surfaces_tool_call_and_result():
"""gh #91: a non-token tool agent's tool call + result must reach the CLI stream
(they render via print_chunk's `● name` / `↳ result` branches). Requires the
langstage-core snapshot fix delivered by the >=1.0.24 floor."""
agent = build_session_agent(_snapshot_tool_graph())
chunks = _collect(agent, "weather in paris?")
calls = [c for c in chunks if "tool_calls" in c]
results = [c for c in chunks if "tool_result" in c]
assert calls and calls[0]["tool_calls"][0]["name"] == "get_weather", (
"tool call dropped on the CLI snapshot path (needs langstage-core >= 1.0.24, gh #91)"
)
assert results and results[0]["tool_result"] == "Sunny, 24C", (
"tool result dropped on the CLI snapshot path (needs langstage-core >= 1.0.24, gh #91)"
)
Loading