Skip to content
Draft
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
29 changes: 15 additions & 14 deletions src/together/lib/cli/utils/_preparse_tokens.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,17 +97,18 @@ def _long_option_names_in_tokens(tokens: list[str]) -> list[str]:
return names


def _legacy_command_before_first_option(tokens: list[str]) -> tuple[str, bool]:
def _unknown_command_fallback(tokens: list[str]) -> tuple[str, bool]:
"""Fallback when cyclopts cannot resolve a command chain (unknown invocations)."""
parts: list[str] = []
for token in tokens:
if token.startswith("--"):
break
parts.append(token)
is_beta_command = bool(parts and parts[0] == "beta")
if is_beta_command:
parts = parts[1:]
return (" ".join(parts), is_beta_command)
is_beta_command = bool(tokens and tokens[0] == "beta")
command_index = 1 if is_beta_command else 0
if command_index >= len(tokens) or tokens[command_index].startswith("-"):
return ("", is_beta_command)

# Once command resolution fails, every later token may be an argument value.
# Keep only the unknown subcommand, including when a caller supplied a quoted
# invocation as one token.
command = tokens[command_index].split(maxsplit=1)[0]
return (command, is_beta_command)


# First subcommand token only (alias -> primary name) for stable telemetry.
Expand Down Expand Up @@ -149,18 +150,18 @@ def preparse_tokens(app: App, tokens: list[str]) -> tuple[str, list[str], bool,
argv = list(tokens)
argv = _expand_implicit_retrieve_tokens(app, *argv)
chain, apps, rest_after_chain = app.parse_commands(argv, include_parent_meta=False)
legacy_cmd, legacy_beta = _legacy_command_before_first_option(argv)
fallback_cmd, fallback_beta = _unknown_command_fallback(argv)

if chain:
is_beta_command = chain[0] == "beta"
chain_tail = list(chain[1:] if is_beta_command else chain)
parsed_command = " ".join(chain_tail)
# ``beta`` alone matches first; remaining tokens are not nested beta subcommands (invalid path).
if chain == ("beta",) and rest_after_chain:
parsed_command = legacy_cmd
parsed_command = fallback_cmd
else:
parsed_command = legacy_cmd
is_beta_command = legacy_beta
parsed_command = fallback_cmd
is_beta_command = fallback_beta

# App.default handlers (e.g. beta endpoints retrieve) don't rewrite argv; still tag telemetry.
parsed_command = _telemetry_command_for_default(parsed_command, apps, rest_after_chain)
Expand Down
16 changes: 16 additions & 0 deletions tests/cli/test_command_telemetry.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,22 @@ def test_unknown_option_equals_value_telemetry_strips_value(
assert "hunter2secret" not in failed["error"]


@pytest.mark.usefixtures("isolated_cli_config")
def test_unknown_command_telemetry_omits_argument_values(
track_cli_capture: list[tuple[CliTrackingEvents, dict[str, Any]]],
cli_runner: CliRunner,
) -> None:
r = cli_runner.invoke(["chat", "-m", "private-model", "-p", "private prompt"])
assert r.exit_code == 1
assert _event_kinds(track_cli_capture) == [
CliTrackingEvents.CommandStarted.value,
CliTrackingEvents.CommandFailed.value,
]
for _, payload in track_cli_capture:
assert payload["command"] == "chat"
assert "private" not in repr(payload)


@pytest.mark.usefixtures("isolated_cli_config")
def test_command_keyboard_interrupt_emits_started_then_user_aborted(
track_cli_capture: list[tuple[CliTrackingEvents, dict[str, Any]]],
Expand Down
23 changes: 23 additions & 0 deletions tests/unit/test_cli_telemetry.py
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,29 @@ def test_parse_command_and_flags_positionals_are_argument_names_not_command_toke
assert is_beta is True


@pytest.mark.parametrize(
("tokens", "expected_command", "expected_beta"),
[
(["chat", "-m", "private-model", "-p", "private prompt"], "chat", False),
(["chat", "private-model", "private prompt"], "chat", False),
(["chat -m private-model -p private-prompt"], "chat", False),
(["beta", "unknown", "private-value"], "unknown", True),
],
)
def test_parse_unknown_command_does_not_treat_argument_values_as_command_tokens(
tokens: list[str],
expected_command: str,
expected_beta: bool,
) -> None:
from together.lib.cli import app
from together.lib.cli.utils._preparse_tokens import preparse_tokens

cmd, _, is_beta, _ = preparse_tokens(app, tokens)
assert cmd == expected_command
assert is_beta is expected_beta
assert "private" not in cmd


def test_telemetry_status_no_api_key(monkeypatch: pytest.MonkeyPatch, cli_runner: CliRunner) -> None:
monkeypatch.delenv("TOGETHER_API_KEY", raising=False)
monkeypatch.delenv("TOGETHER_TELEMETRY_DISABLED", raising=False)
Expand Down
Loading