diff --git a/CHANGELOG.md b/CHANGELOG.md index 254b26b..e3b5b93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/README.md b/README.md index abbbb9a..0dc65a3 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ langstage-cli --show-config In the interactive loop: - `/help` (`/h`, `/?`) - Show this help message, or `/help ` 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]` diff --git a/pyproject.toml b/pyproject.toml index 2f1efd0..d076f51 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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" @@ -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", ] diff --git a/tests/test_agui_stream.py b/tests/test_agui_stream.py index a9914a2..022feb6 100644 --- a/tests/test_agui_stream.py +++ b/tests/test_agui_stream.py @@ -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)" + )