langstage-cli 0.6.44__tar.gz → 0.6.46__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.44/langstage_cli.egg-info → langstage_cli-0.6.46}/PKG-INFO +1 -1
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/langstage_cli/cli.py +42 -14
- {langstage_cli-0.6.44 → langstage_cli-0.6.46/langstage_cli.egg-info}/PKG-INFO +1 -1
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/langstage_cli.egg-info/SOURCES.txt +2 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/pyproject.toml +1 -1
- langstage_cli-0.6.46/tests/test_tool_card_args.py +100 -0
- langstage_cli-0.6.46/tests/test_usage_hint_186.py +52 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/LICENSE +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/README.md +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/deepagent_code/__init__.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/langstage_cli/__init__.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/langstage_cli/agui_stream.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/langstage_cli/config.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/langstage_cli/exit_codes.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/langstage_cli/sessions.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/langstage_cli.egg-info/dependency_links.txt +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/langstage_cli.egg-info/entry_points.txt +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/langstage_cli.egg-info/requires.txt +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/langstage_cli.egg-info/top_level.txt +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/setup.cfg +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_advertised_wave4.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_agui_stream.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_approval_menu_abort.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_async_mode.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_checkpointer.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_cli.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_cli_help.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_codeconfig.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_config.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_config_command.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_configurable.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_deferred_pass.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_dumb_term.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_exit_codes.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_generic_interrupt.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_help_render.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_history_persist.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_hitl_approval_preview.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_hitl_resume_warning.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_init.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_interrupt_non_tty.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_legacy_config_home.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_load_error.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_message_boundaries.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_no_color.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_no_interactive_approve.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_no_persist_resume.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_persist_env.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_quiet_output.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_reasoning_render.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_rename_shim.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_render_markdown.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_session_lifecycle.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_sessions.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_show_config.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_show_config_malformed.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_spec_loading_core.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_spec_resolution.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_spinner_reply_survives.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_stdin_interactive.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_stdin_single_shot.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_stream_mode.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_streaming_marker.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_terminal_width.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_toml_relative_paths.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_tool_output_escape.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_turn_error_verbose.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_turn_exit_and_render.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_unicode_console.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_verbose_command.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_verify_flag.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_workspace.py +0 -0
- {langstage_cli-0.6.44 → langstage_cli-0.6.46}/tests/test_wrong_type_graph.py +0 -0
|
@@ -1001,6 +1001,15 @@ def _default_agent_path() -> Optional[Path]:
|
|
|
1001
1001
|
return path if path.exists() else None
|
|
1002
1002
|
|
|
1003
1003
|
|
|
1004
|
+
# A MESSAGE that is really an agent spec: ``my_agent.py:graph`` or ``pkg.module:agent``.
|
|
1005
|
+
_SPEC_LIKE = re.compile(r"^(?:(?:[A-Za-z]:)?[^\s:]+\.py|[A-Za-z_][\w.]*):[A-Za-z_]\w*$")
|
|
1006
|
+
|
|
1007
|
+
|
|
1008
|
+
def _looks_like_agent_spec(message: Optional[str]) -> bool:
|
|
1009
|
+
"""Whether a positional MESSAGE looks like an agent spec meant for ``-a`` (gh #186)."""
|
|
1010
|
+
return bool(message) and bool(_SPEC_LIKE.match(message.strip()))
|
|
1011
|
+
|
|
1012
|
+
|
|
1004
1013
|
def _no_agent_toml_hint(cfg: Any, toml_sources: list) -> List[str]:
|
|
1005
1014
|
"""Lines explaining why a ``langstage.toml`` that was read supplied no spec (gh #148).
|
|
1006
1015
|
|
|
@@ -1140,20 +1149,31 @@ def _absolutize_file_spec(spec: str) -> str:
|
|
|
1140
1149
|
return f"{Path(path_part).expanduser().resolve()}{suffix}"
|
|
1141
1150
|
|
|
1142
1151
|
|
|
1143
|
-
|
|
1144
|
-
|
|
1152
|
+
# Per-value cap on the tool-call card (default output; ``-v`` shows values in full).
|
|
1153
|
+
_TOOL_ARG_VALUE_MAX = 50
|
|
1154
|
+
|
|
1145
1155
|
|
|
1146
|
-
|
|
1147
|
-
|
|
1156
|
+
def get_tool_arg_preview(args: Dict[str, Any], verbose: bool = False) -> str:
|
|
1157
|
+
"""The arguments a tool was called with, for the tool-call card.
|
|
1158
|
+
|
|
1159
|
+
A lone argument shows as its bare value (``read_file └─ docs/a.md``). Several show
|
|
1160
|
+
as ``name=value`` pairs, every one of them: showing only the first misstated the
|
|
1161
|
+
call, ``add(a=17, b=25)`` read as "add 17" (gh #184). Each value is capped at 50
|
|
1162
|
+
characters unless ``verbose``. Values are escaped before they are truncated, so a
|
|
1163
|
+
tool arg can't move the cursor or retitle the terminal, and a cut can't leave half
|
|
1164
|
+
an escape sequence live (gh #180).
|
|
1148
1165
|
"""
|
|
1149
1166
|
if not args:
|
|
1150
1167
|
return ""
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
if len(
|
|
1155
|
-
|
|
1156
|
-
|
|
1168
|
+
limit = None if verbose else _TOOL_ARG_VALUE_MAX
|
|
1169
|
+
if not isinstance(args, dict):
|
|
1170
|
+
return _approval_text(args, limit)
|
|
1171
|
+
if len(args) == 1:
|
|
1172
|
+
(value,) = args.values()
|
|
1173
|
+
return _approval_text(str(value), limit)
|
|
1174
|
+
return ", ".join(
|
|
1175
|
+
f"{_approval_text(str(k), None)}={_approval_text(v, limit)}" for k, v in args.items()
|
|
1176
|
+
)
|
|
1157
1177
|
|
|
1158
1178
|
|
|
1159
1179
|
# Per-value cap for the HITL approval preview. Values are truncated, never dropped: the
|
|
@@ -1495,7 +1515,7 @@ def print_chunk(chunk: Dict[str, Any], verbose: bool = False):
|
|
|
1495
1515
|
for tool_call in chunk["tool_calls"]:
|
|
1496
1516
|
tool_name = _escape_controls(str(tool_call["name"])) # gh #180
|
|
1497
1517
|
args = tool_call.get("args", {})
|
|
1498
|
-
arg_preview = get_tool_arg_preview(args)
|
|
1518
|
+
arg_preview = get_tool_arg_preview(args, verbose)
|
|
1499
1519
|
|
|
1500
1520
|
safe_print(f"\n{GREEN}● {tool_name}{RESET}")
|
|
1501
1521
|
if arg_preview:
|
|
@@ -3457,10 +3477,18 @@ def main(
|
|
|
3457
3477
|
# of only pointing at the env var (gh #148).
|
|
3458
3478
|
for line in _no_agent_toml_hint(cfg, toml_sources):
|
|
3459
3479
|
_status(f"{YELLOW}{line}{RESET}")
|
|
3480
|
+
# The spec went in as the MESSAGE: name the command that works (gh #186).
|
|
3481
|
+
if _looks_like_agent_spec(message):
|
|
3482
|
+
_status(
|
|
3483
|
+
f"{YELLOW}The agent spec goes after -a: langstage-cli -a {message.strip()}{RESET}"
|
|
3484
|
+
)
|
|
3460
3485
|
_status(f"\n{DIM}Usage:{RESET}")
|
|
3461
|
-
_status(" langstage-cli path/to/agent.py:graph")
|
|
3462
|
-
_status(" langstage-cli mypackage.module:agent")
|
|
3463
|
-
_status(
|
|
3486
|
+
_status(" langstage-cli -a path/to/agent.py:graph")
|
|
3487
|
+
_status(" langstage-cli -a mypackage.module:agent")
|
|
3488
|
+
_status(
|
|
3489
|
+
f"\n{DIM}Or set LANGSTAGE_AGENT_SPEC, put the spec under [agent] spec in "
|
|
3490
|
+
f"langstage.toml, or run `langstage-cli init` for a starter agent{RESET}"
|
|
3491
|
+
)
|
|
3464
3492
|
sys.exit(1)
|
|
3465
3493
|
|
|
3466
3494
|
# The BASE directory a relative spec resolves against depends on WHERE it came
|
|
@@ -59,10 +59,12 @@ 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
|
|
65
66
|
tests/test_unicode_console.py
|
|
67
|
+
tests/test_usage_hint_186.py
|
|
66
68
|
tests/test_verbose_command.py
|
|
67
69
|
tests/test_verify_flag.py
|
|
68
70
|
tests/test_workspace.py
|
|
@@ -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.46"
|
|
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
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""gh #186: the "No agent specified" hint shows commands that work (with -a)."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from click.testing import CliRunner
|
|
5
|
+
|
|
6
|
+
from langstage_cli import cli
|
|
7
|
+
from langstage_cli.cli import _looks_like_agent_spec, main
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@pytest.fixture
|
|
11
|
+
def no_agent(tmp_path, monkeypatch):
|
|
12
|
+
monkeypatch.chdir(tmp_path)
|
|
13
|
+
for var in ("LANGSTAGE_AGENT_SPEC", "DEEPAGENT_AGENT_SPEC", "DEEPAGENT_SPEC"):
|
|
14
|
+
monkeypatch.delenv(var, raising=False)
|
|
15
|
+
monkeypatch.setattr(cli, "_default_agent_path", lambda: None)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_usage_hint_uses_dash_a(no_agent):
|
|
19
|
+
r = CliRunner().invoke(main, ["hi"])
|
|
20
|
+
assert r.exit_code == 1, r.output
|
|
21
|
+
assert "No agent specified" in r.output
|
|
22
|
+
assert "langstage-cli -a path/to/agent.py:graph" in r.output
|
|
23
|
+
assert "langstage-cli -a mypackage.module:agent" in r.output
|
|
24
|
+
assert "[agent] spec" in r.output and "langstage-cli init" in r.output
|
|
25
|
+
# Not a spec-looking message: no "goes after -a" line.
|
|
26
|
+
assert "goes after -a" not in r.output
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@pytest.mark.parametrize("spec", ["my_agent.py:graph", "mypackage.module:agent"])
|
|
30
|
+
def test_spec_passed_as_message_names_the_working_command(no_agent, spec):
|
|
31
|
+
r = CliRunner().invoke(main, [spec])
|
|
32
|
+
assert r.exit_code == 1, r.output
|
|
33
|
+
assert f"The agent spec goes after -a: langstage-cli -a {spec}" in r.output
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@pytest.mark.parametrize(
|
|
37
|
+
"message,expected",
|
|
38
|
+
[
|
|
39
|
+
("my_agent.py:graph", True),
|
|
40
|
+
("./agents/a.py:graph", True),
|
|
41
|
+
(r"C:\work\agent.py:graph", True),
|
|
42
|
+
("pkg.module:agent", True),
|
|
43
|
+
("hello", False),
|
|
44
|
+
("what is 2:3", False),
|
|
45
|
+
("ratio 3:1 please", False),
|
|
46
|
+
("http://example.org:80", False),
|
|
47
|
+
("", False),
|
|
48
|
+
(None, False),
|
|
49
|
+
],
|
|
50
|
+
)
|
|
51
|
+
def test_looks_like_agent_spec(message, expected):
|
|
52
|
+
assert _looks_like_agent_spec(message) is expected
|
|
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
|
|
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
|
|
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
|