diff --git a/agentscore/types.py b/agentscore/types.py index 670555f..9c99a86 100644 --- a/agentscore/types.py +++ b/agentscore/types.py @@ -8,7 +8,7 @@ class Subject(TypedDict): - chain: str + chains: list[str] address: str @@ -23,12 +23,20 @@ class Classification(TypedDict): class Score(TypedDict): + value: int | None + grade: Grade | None + scored_at: str | None status: ReputationStatus + version: str + + +class ChainScore(TypedDict): value: int | None grade: Grade | None confidence: float | None dimensions: dict[str, float] | None scored_at: str | None + status: ReputationStatus version: str @@ -105,13 +113,19 @@ class AgentSummary(TypedDict): grade: Grade +class ChainEntry(TypedDict): + chain: str + score: ChainScore + classification: Classification + identity: Identity + activity: Activity + evidence_summary: EvidenceSummary + + class ReputationResponse(TypedDict): subject: Subject - classification: Classification score: Score - identity: Identity | None - activity: Activity | None - evidence_summary: EvidenceSummary | None + chains: list[ChainEntry] data_semantics: str caveats: list[str] updated_at: str | None @@ -131,17 +145,17 @@ class DecisionPolicy(TypedDict, total=False): class AssessResponse(TypedDict): subject: Subject - classification: Classification score: Score - identity: Identity | None - activity: Activity | None - evidence_summary: EvidenceSummary | None - data_semantics: str - caveats: list[str] - updated_at: str | None + chains: list[ChainEntry] decision: str | None decision_reasons: list[str] on_the_fly: bool + data_semantics: str + caveats: list[str] + updated_at: str | None + operator_score: OperatorScore | None + reputation: Reputation | None + agents: list[AgentSummary] class AgentRecord(TypedDict): diff --git a/pyproject.toml b/pyproject.toml index e310a0d..00824fd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "agentscore-py" -version = "1.2.0" +version = "1.3.0" description = "Python client for the AgentScore trust and reputation API" readme = "README.md" license = "MIT" diff --git a/tests/test_client.py b/tests/test_client.py index 5975ba4..ec80a04 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -58,28 +58,24 @@ def test_constructor_default_timeout(): # --------------------------------------------------------------------------- REPUTATION_PAYLOAD = { - "subject": {"chain": "base", "address": ADDRESS}, - "classification": { - "entity_type": "wallet", - "confidence": 0.9, - "is_known": True, - "is_known_erc8004_agent": False, - "has_candidate_payment_activity": True, - "has_verified_payment_activity": False, - "reasons": [], - }, + "subject": {"chains": ["base"], "address": ADDRESS}, "score": { - "status": "scored", "value": 75, "grade": "B", - "confidence": 0.85, - "dimensions": None, "scored_at": "2024-01-01T00:00:00Z", + "status": "scored", "version": "1", }, - "identity": None, - "activity": None, - "evidence_summary": None, + "chains": [ + { + "chain": "base", + "score": {"value": 75, "grade": "B"}, + "classification": {"entity_type": "wallet", "confidence": 0.9}, + "identity": {}, + "activity": {}, + "evidence_summary": {}, + }, + ], "data_semantics": "live", "caveats": [], "updated_at": "2024-01-01T00:00:00Z", diff --git a/tests/test_integration.py b/tests/test_integration.py new file mode 100644 index 0000000..4eb2578 --- /dev/null +++ b/tests/test_integration.py @@ -0,0 +1,114 @@ +import os + +import pytest + +from agentscore import AgentScore + +API_KEY = os.environ.get("AGENTSCORE_API_KEY") +BASE_URL = os.environ.get("AGENTSCORE_BASE_URL", "http://api.dev.agentscore.internal") +TEST_ADDRESS = "0x339559a2d1cd15059365fc7bd36b3047bba480e0" + +pytestmark = pytest.mark.skipif(not API_KEY, reason="AGENTSCORE_API_KEY not set") + + +def test_get_reputation_shape(): + client = AgentScore(api_key=API_KEY, base_url=BASE_URL) + rep = client.get_reputation(TEST_ADDRESS) + + assert "chains" in rep["subject"] + assert isinstance(rep["subject"]["chains"], list) + assert len(rep["subject"]["chains"]) > 0 + + assert "value" in rep["score"] + assert "grade" in rep["score"] + assert "scored_at" in rep["score"] + assert "status" in rep["score"] + assert "version" in rep["score"] + assert "confidence" not in rep["score"] + assert "dimensions" not in rep["score"] + + assert "chains" in rep + assert isinstance(rep["chains"], list) + assert len(rep["chains"]) > 0 + + chain = rep["chains"][0] + assert "chain" in chain + assert "score" in chain + assert "classification" in chain + assert "identity" in chain + assert "activity" in chain + assert "evidence_summary" in chain + + assert "agents" in rep + assert isinstance(rep["agents"], list) + + +def test_get_reputation_chain_filter(): + client = AgentScore(api_key=API_KEY, base_url=BASE_URL) + rep = client.get_reputation(TEST_ADDRESS, chain="base") + + assert rep["subject"]["chains"] == ["base"] + assert len(rep["chains"]) == 1 + assert rep["chains"][0]["chain"] == "base" + + +def test_get_reputation_chain_entry_full_fields(): + client = AgentScore(api_key=API_KEY, base_url=BASE_URL) + rep = client.get_reputation(TEST_ADDRESS) + chain = rep["chains"][0] + + assert "confidence" in chain["score"] + assert "dimensions" in chain["score"] + assert "as_verified_payer" in chain["activity"] + assert "active_days" in chain["activity"] + assert "first_candidate_tx_at" in chain["activity"] + + +def test_get_reputation_metadata_fields(): + client = AgentScore(api_key=API_KEY, base_url=BASE_URL) + rep = client.get_reputation(TEST_ADDRESS) + + assert "caveats" in rep + assert "data_semantics" in rep + assert "updated_at" in rep + + +def test_assess_operator_level(): + client = AgentScore(api_key=API_KEY, base_url=BASE_URL) + result = client.assess(TEST_ADDRESS) + + assert "decision" in result + assert isinstance(result["decision_reasons"], list) + assert isinstance(result["chains"], list) + assert isinstance(result["agents"], list) + assert "classification" not in result + + +def test_assess_policy_deny(): + client = AgentScore(api_key=API_KEY, base_url=BASE_URL) + result = client.assess(TEST_ADDRESS, policy={"min_score": 999}) + + assert result["decision"] == "deny" + assert len(result["decision_reasons"]) > 0 + + +def test_get_reputation_operator_score(): + client = AgentScore(api_key=API_KEY, base_url=BASE_URL) + rep = client.get_reputation(TEST_ADDRESS) + op = rep.get("operator_score") + if not op: + pytest.skip("no operator_score on test address") + assert isinstance(op["score"], int) + assert isinstance(op["grade"], str) + assert isinstance(op["agent_count"], int) + assert isinstance(op["chains_active"], list) + + +def test_get_reputation_reputation_field(): + client = AgentScore(api_key=API_KEY, base_url=BASE_URL) + rep = client.get_reputation(TEST_ADDRESS) + r = rep.get("reputation") + if not r: + pytest.skip("no reputation on test address") + assert isinstance(r["feedback_count"], int) + assert isinstance(r["client_count"], int) diff --git a/uv.lock b/uv.lock index c6de32f..9cc246d 100644 --- a/uv.lock +++ b/uv.lock @@ -8,7 +8,7 @@ resolution-markers = [ [[package]] name = "agentscore-py" -version = "1.2.0" +version = "1.3.0" source = { editable = "." } dependencies = [ { name = "httpx" },