feat: unify agent status schema

This commit is contained in:
jackwener
2026-03-10 21:02:08 +08:00
parent 32d074dc9f
commit 642ffe84a8
4 changed files with 71 additions and 8 deletions

View File

@@ -106,7 +106,9 @@ def test_cli_whoami_command(monkeypatch) -> None:
result_json = runner.invoke(cli, ["whoami", "--json"]) result_json = runner.invoke(cli, ["whoami", "--json"])
assert result_json.exit_code == 0 assert result_json.exit_code == 0
assert '"screenName": "testuser"' in result_json.output payload = yaml.safe_load(runner.invoke(cli, ["whoami", "--yaml"]).output)
assert payload["ok"] is True
assert payload["data"]["user"]["username"] == "testuser"
def test_cli_whoami_auto_yaml(monkeypatch) -> None: def test_cli_whoami_auto_yaml(monkeypatch) -> None:
@@ -122,7 +124,9 @@ def test_cli_whoami_auto_yaml(monkeypatch) -> None:
assert result.exit_code == 0 assert result.exit_code == 0
payload = yaml.safe_load(result.output) payload = yaml.safe_load(result.output)
assert payload["screenName"] == "testuser" assert payload["ok"] is True
assert payload["schema_version"] == "1"
assert payload["data"]["user"]["username"] == "testuser"
def test_cli_status_auto_yaml(monkeypatch) -> None: def test_cli_status_auto_yaml(monkeypatch) -> None:
@@ -138,8 +142,10 @@ def test_cli_status_auto_yaml(monkeypatch) -> None:
assert result.exit_code == 0 assert result.exit_code == 0
payload = yaml.safe_load(result.output) payload = yaml.safe_load(result.output)
assert payload["authenticated"] is True assert payload["ok"] is True
assert payload["user"]["screenName"] == "testuser" assert payload["schema_version"] == "1"
assert payload["data"]["authenticated"] is True
assert payload["data"]["user"]["username"] == "testuser"
def test_cli_reply_command(monkeypatch) -> None: def test_cli_reply_command(monkeypatch) -> None:

View File

@@ -49,7 +49,15 @@ from .formatter import (
print_user_profile, print_user_profile,
print_user_table, print_user_table,
) )
from .output import default_structured_format, emit_structured, structured_output_options, use_rich_output from .models import UserProfile
from .output import (
default_structured_format,
emit_structured,
error_payload,
structured_output_options,
success_payload,
use_rich_output,
)
from .serialization import ( from .serialization import (
tweets_from_json, tweets_from_json,
tweets_to_data, tweets_to_data,
@@ -65,6 +73,27 @@ FEED_TYPES = ["for-you", "following"]
SEARCH_PRODUCTS = ["Top", "Latest", "Photos", "Videos"] SEARCH_PRODUCTS = ["Top", "Latest", "Photos", "Videos"]
def _agent_user_profile(profile: UserProfile) -> dict:
"""Normalize a Twitter/X profile for structured agent output."""
data = user_profile_to_dict(profile)
return {
"id": data["id"],
"name": data["name"],
"username": data["screenName"],
"screenName": data["screenName"],
"bio": data["bio"],
"location": data["location"],
"url": data["url"],
"followers": data["followers"],
"following": data["following"],
"tweets": data["tweets"],
"likes": data["likes"],
"verified": data["verified"],
"profileImageUrl": data["profileImageUrl"],
"createdAt": data["createdAt"],
}
def _setup_logging(verbose): def _setup_logging(verbose):
# type: (bool) -> None # type: (bool) -> None
level = logging.DEBUG if verbose else logging.WARNING level = logging.DEBUG if verbose else logging.WARNING
@@ -684,13 +713,13 @@ def status(as_json, as_yaml):
client = _get_client_for_output(config, quiet=not rich_output) client = _get_client_for_output(config, quiet=not rich_output)
profile = client.fetch_me() profile = client.fetch_me()
except RuntimeError as exc: except RuntimeError as exc:
payload = {"authenticated": False, "error": str(exc)} payload = error_payload("not_authenticated", str(exc))
if emit_structured(payload, as_json=as_json, as_yaml=as_yaml): if emit_structured(payload, as_json=as_json, as_yaml=as_yaml):
sys.exit(1) sys.exit(1)
_exit_with_error(exc) _exit_with_error(exc)
return return
payload = {"authenticated": True, "user": user_profile_to_dict(profile)} payload = success_payload({"authenticated": True, "user": _agent_user_profile(profile)})
if emit_structured(payload, as_json=as_json, as_yaml=as_yaml): if emit_structured(payload, as_json=as_json, as_yaml=as_yaml):
return return
@@ -711,9 +740,11 @@ def whoami(as_json, as_yaml):
console.print("👤 Fetching current user...") console.print("👤 Fetching current user...")
profile = client.fetch_me() profile = client.fetch_me()
except RuntimeError as exc: except RuntimeError as exc:
if emit_structured(error_payload("not_authenticated", str(exc)), as_json=as_json, as_yaml=as_yaml):
raise SystemExit(1) from None
_exit_with_error(exc) _exit_with_error(exc)
if not emit_structured(user_profile_to_dict(profile), as_json=as_json, as_yaml=as_yaml): if not emit_structured(success_payload({"user": _agent_user_profile(profile)}), as_json=as_json, as_yaml=as_yaml):
console.print() console.print()
print_user_profile(profile, console) print_user_profile(profile, console)

View File

@@ -5,6 +5,7 @@ from __future__ import annotations
import json import json
import logging import logging
import math import math
import os
import random import random
import re import re
import time import time

View File

@@ -11,6 +11,7 @@ import click
import yaml import yaml
_OUTPUT_ENV = "OUTPUT" _OUTPUT_ENV = "OUTPUT"
_SCHEMA_VERSION = "1"
def default_structured_format(*, as_json: bool, as_yaml: bool) -> str | None: def default_structured_format(*, as_json: bool, as_yaml: bool) -> str | None:
@@ -66,3 +67,27 @@ def emit_structured(data: Any, *, as_json: bool, as_yaml: bool) -> bool:
) )
) )
return True return True
def success_payload(data: Any) -> dict[str, Any]:
"""Wrap structured success data in the shared agent schema."""
return {
"ok": True,
"schema_version": _SCHEMA_VERSION,
"data": data,
}
def error_payload(code: str, message: str, *, details: Any | None = None) -> dict[str, Any]:
"""Wrap structured error data in the shared agent schema."""
error = {
"code": code,
"message": message,
}
if details is not None:
error["details"] = details
return {
"ok": False,
"schema_version": _SCHEMA_VERSION,
"error": error,
}