agentscore-py 2.7.0__tar.gz → 2.7.1__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.
Files changed (34) hide show
  1. agentscore_py-2.7.1/.claude/CLAUDE.md +76 -0
  2. {agentscore_py-2.7.0 → agentscore_py-2.7.1}/.github/workflows/ci.yml +2 -2
  3. {agentscore_py-2.7.0 → agentscore_py-2.7.1}/.github/workflows/publish.yml +1 -1
  4. {agentscore_py-2.7.0 → agentscore_py-2.7.1}/.github/workflows/security.yml +1 -1
  5. {agentscore_py-2.7.0 → agentscore_py-2.7.1}/PKG-INFO +3 -6
  6. {agentscore_py-2.7.0 → agentscore_py-2.7.1}/README.md +2 -5
  7. {agentscore_py-2.7.0 → agentscore_py-2.7.1}/agentscore/client.py +17 -15
  8. {agentscore_py-2.7.0 → agentscore_py-2.7.1}/agentscore/errors.py +7 -7
  9. {agentscore_py-2.7.0 → agentscore_py-2.7.1}/agentscore/test_mode.py +1 -1
  10. {agentscore_py-2.7.0 → agentscore_py-2.7.1}/agentscore/types.py +24 -24
  11. {agentscore_py-2.7.0 → agentscore_py-2.7.1}/pyproject.toml +6 -5
  12. {agentscore_py-2.7.0 → agentscore_py-2.7.1}/tests/test_client.py +8 -8
  13. {agentscore_py-2.7.0 → agentscore_py-2.7.1}/tests/test_types.py +1 -1
  14. {agentscore_py-2.7.0 → agentscore_py-2.7.1}/uv.lock +1 -4
  15. {agentscore_py-2.7.0 → agentscore_py-2.7.1}/vulture_whitelist.py +1 -1
  16. agentscore_py-2.7.0/.claude/CLAUDE.md +0 -76
  17. {agentscore_py-2.7.0 → agentscore_py-2.7.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  18. {agentscore_py-2.7.0 → agentscore_py-2.7.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  19. {agentscore_py-2.7.0 → agentscore_py-2.7.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  20. {agentscore_py-2.7.0 → agentscore_py-2.7.1}/.github/dependabot.yml +0 -0
  21. {agentscore_py-2.7.0 → agentscore_py-2.7.1}/.gitignore +0 -0
  22. {agentscore_py-2.7.0 → agentscore_py-2.7.1}/CODE_OF_CONDUCT.md +0 -0
  23. {agentscore_py-2.7.0 → agentscore_py-2.7.1}/CONTRIBUTING.md +0 -0
  24. {agentscore_py-2.7.0 → agentscore_py-2.7.1}/LICENSE +0 -0
  25. {agentscore_py-2.7.0 → agentscore_py-2.7.1}/SECURITY.md +0 -0
  26. {agentscore_py-2.7.0 → agentscore_py-2.7.1}/agentscore/__init__.py +0 -0
  27. {agentscore_py-2.7.0 → agentscore_py-2.7.1}/agentscore/py.typed +0 -0
  28. {agentscore_py-2.7.0 → agentscore_py-2.7.1}/lefthook.yml +0 -0
  29. {agentscore_py-2.7.0 → agentscore_py-2.7.1}/ruff.toml +0 -0
  30. {agentscore_py-2.7.0 → agentscore_py-2.7.1}/tests/__init__.py +0 -0
  31. {agentscore_py-2.7.0 → agentscore_py-2.7.1}/tests/conftest.py +0 -0
  32. {agentscore_py-2.7.0 → agentscore_py-2.7.1}/tests/test_errors.py +0 -0
  33. {agentscore_py-2.7.0 → agentscore_py-2.7.1}/tests/test_integration.py +0 -0
  34. {agentscore_py-2.7.0 → agentscore_py-2.7.1}/tests/test_test_mode.py +0 -0
@@ -0,0 +1,76 @@
1
+ # agentscore-py
2
+
3
+ Python client for the AgentScore APIs.
4
+
5
+ ## Identity Model
6
+
7
+ Two identity paths: `X-Wallet-Address` (wallet-based) and `X-Operator-Token` (credential-based). Wallet addresses accept both EVM (`0x...` 40-hex) and Solana (base58, 32–44 chars) formats; network is auto-detected from the address shape. `assess` responses include `resolved_operator` and `linked_wallets[]` (same-operator sibling wallets, normalized per network, EVM lowercased, Solana base58 verbatim; may mix chains for cross-chain operators). `create_session` and `create_credential` responses include an `agent_memory` cross-merchant pattern hint. `create_session` also returns `next_steps.action="deliver_verify_url_and_poll"` + polling instructions. `poll_session` returns `next_steps.action` values: `continue_polling`, `retry_merchant_request_with_operator_token`, `use_stored_operator_token`, `create_new_session`, `verification_failed`, `contact_support`.
8
+
9
+ ## Methods (sync + async)
10
+
11
+ - `assess` / `aassess`: identity gate with policy (paid). Accepts `operator_token` for non-wallet agents. Response includes `linked_wallets[]` and `resolved_operator`. Optional `signer: { address, network }` opts into server-side wallet-signer-match AND OFAC SDN wallet-address screening; the response then carries both a `signer_match` block (wallet-binding verdict: `pass` / `wallet_signer_mismatch` / `wallet_auth_requires_wallet_signing`) and a `signer_sanctions` block (discriminated union: `{status: "clear"}` | `{sanctioned: True, ofac_label, sdn_uid, listed_at}` | `{status: "unavailable"}`). Wallet-OFAC SDN enforcement on the `signer` block is unconditional whenever a signer is supplied, no `policy.require_sanctions_clear` opt-in required. A `sanctioned: True` OR `status: "unavailable"` verdict flips the response `decision` to `deny` with `decision_reasons` including `sanctions_flagged` or `sanctions_check_unavailable` respectively (fail-closed; OFAC strict-liability). `policy.require_sanctions_clear` is the separate NAME-based screen on the resolved operator's KYC identity.
12
+ - `create_session` / `acreate_session`: create verification session. Returns `agent_memory` + `next_steps`.
13
+ - `poll_session` / `apoll_session`: poll session status, returns credential when verified, plus `next_steps.action`.
14
+ - `create_credential` / `acreate_credential`: create operator credential (24h TTL default). Response includes `agent_memory`.
15
+ - `list_credentials` / `alist_credentials`: list active credentials
16
+ - `revoke_credential` / `arevoke_credential`: revoke a credential
17
+ - `associate_wallet` / `aassociate_wallet`: report a signer wallet seen paying under a credential. Accepts optional `idempotency_key` (payment intent id / tx hash) so retries don't inflate transaction_count.
18
+ - `telemetry_signer_match` / `atelemetry_signer_match`: fire-and-forget POST to `/v1/telemetry/signer-match`; commerce gate uses this to report `pass` / `wallet_signer_mismatch` / `wallet_auth_requires_wallet_signing` verdicts.
19
+
20
+ ## Errors + observability
21
+
22
+ Typed error subclasses of `AgentScoreError` so callers can `except` on the specific class without parsing `err.code`: `PaymentRequiredError` (402), `TokenExpiredError` (401 token_expired, exposes parsed `verify_url` / `session_id` / `poll_secret` / `poll_url` / `next_steps` / `agent_memory` instance attributes), `InvalidCredentialError` (401 invalid_credential), `QuotaExceededError` (429 quota_exceeded, don't retry), `RateLimitedError` (429 rate_limited, retry after Retry-After), `TimeoutError` (httpx.TimeoutException, note: subclasses `AgentScoreError`, not the builtin; import explicitly from `agentscore.errors` to disambiguate). All non-timeout `httpx.HTTPError` (ConnectError, ProtocolError, NetworkError, etc.) wrap to `AgentScoreError(code="network_error", status_code=0)` for parity with node-sdk.
23
+
24
+ `assess()` / `aassess()` responses include an optional `quota` field captured from `X-Quota-Limit` / `X-Quota-Used` / `X-Quota-Reset` response headers, so callers can monitor approach-to-cap proactively before hitting 429.
25
+
26
+ ## Architecture
27
+
28
+ Single-package Python library published to PyPI.
29
+
30
+ | File | Purpose |
31
+ |------|---------|
32
+ | `agentscore/` | Source code |
33
+ | `tests/` | pytest tests |
34
+
35
+ ## Tooling
36
+
37
+ - **uv**: package manager. Use `uv sync`, `uv run`.
38
+ - **ruff**: linting + formatting. `uv run ruff check .` and `uv run ruff format --check .`.
39
+ - **ty**: type checker (Astral). `uv run ty check agentscore/`.
40
+ - **vulture**: dead code detection.
41
+ - **pytest**: tests. `uv run pytest tests/`.
42
+ - **Lefthook**: git hooks. Pre-commit: ruff. Pre-push: ty + vulture (parallel).
43
+
44
+ ## Key Commands
45
+
46
+ ```bash
47
+ uv sync --all-extras
48
+ uv run lefthook install # one-time per clone, wires pre-commit + pre-push
49
+ uv run ruff check .
50
+ uv run ruff format .
51
+ uv run ty check agentscore/
52
+ uv run pytest tests/
53
+ ```
54
+
55
+ ## Workflow
56
+
57
+ 1. Create a branch
58
+ 2. Make changes
59
+ 3. Lefthook runs ruff on commit, ty + vulture on push
60
+ 4. Open a PR, CI runs automatically
61
+ 5. Merge (squash)
62
+
63
+ ## Rules
64
+
65
+ - **No silent refactors**
66
+ - **Never commit .env files or secrets**
67
+ - **Use PRs**: never push directly to main
68
+
69
+ ## Releasing
70
+
71
+ 1. Update `version` in `pyproject.toml`
72
+ 2. Commit: `git commit -am "chore: bump to vX.Y.Z"`
73
+ 3. Tag: `git tag vX.Y.Z`
74
+ 4. Push: `git push && git push origin vX.Y.Z`
75
+
76
+ The publish workflow runs on `ubuntu-latest` (required for PyPI trusted publishing), builds, publishes to PyPI via OIDC, and creates a GitHub Release.
@@ -22,7 +22,7 @@ jobs:
22
22
  - uses: useblacksmith/checkout@v1
23
23
  - uses: astral-sh/setup-uv@v10.2.0
24
24
  with:
25
- version: "0.12.22"
25
+ version: "0.12.23"
26
26
  - run: uv python install 3.12
27
27
  - uses: actions/cache@v6
28
28
  with:
@@ -49,7 +49,7 @@ jobs:
49
49
  - uses: useblacksmith/checkout@v1
50
50
  - uses: astral-sh/setup-uv@v10.2.0
51
51
  with:
52
- version: "0.12.22"
52
+ version: "0.12.23"
53
53
  - run: uv python install ${{ matrix.python }}
54
54
  - run: uv sync --frozen --all-extras --all-groups --python ${{ matrix.python }}
55
55
  - run: uv run --python ${{ matrix.python }} pytest tests/ -q
@@ -21,7 +21,7 @@ jobs:
21
21
 
22
22
  - uses: astral-sh/setup-uv@v10.2.0
23
23
  with:
24
- version: "0.12.22"
24
+ version: "0.12.23"
25
25
 
26
26
  - name: Set version from tag
27
27
  run: sed -i "s/^version = .*/version = \"${GITHUB_REF_NAME#v}\"/" pyproject.toml
@@ -38,7 +38,7 @@ jobs:
38
38
  - uses: useblacksmith/checkout@v1
39
39
  - uses: astral-sh/setup-uv@v10.2.0
40
40
  with:
41
- version: "0.12.22"
41
+ version: "0.12.23"
42
42
  - uses: actions/setup-python@v7
43
43
  with:
44
44
  python-version: "3.13"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: agentscore-py
3
- Version: 2.7.0
3
+ Version: 2.7.1
4
4
  Summary: Python client for the AgentScore APIs
5
5
  Project-URL: Homepage, https://www.agentscore.com
6
6
  Project-URL: Repository, https://github.com/agentscore/python-sdk
@@ -86,11 +86,8 @@ status = client.poll_session(session["session_id"], session["poll_secret"])
86
86
  if status["status"] == "verified":
87
87
  print(status["operator_token"]) # "opc_...", use for future requests
88
88
 
89
- # Optional pre-association: attach the session to a known wallet or refresh KYC
90
- # for an existing operator credential.
91
- client.create_session(address="0x...")
92
- client.create_session(operator_token="opc_...") # KYC refresh
93
- client.create_session(kind="sign_in") # registration-only: account sign-in, no identity documents
89
+ # Registration-only session: the buyer signs in with an AgentScore account, no identity documents.
90
+ client.create_session(kind="sign_in")
94
91
  ```
95
92
 
96
93
  ### Wallet resolution
@@ -58,11 +58,8 @@ status = client.poll_session(session["session_id"], session["poll_secret"])
58
58
  if status["status"] == "verified":
59
59
  print(status["operator_token"]) # "opc_...", use for future requests
60
60
 
61
- # Optional pre-association: attach the session to a known wallet or refresh KYC
62
- # for an existing operator credential.
63
- client.create_session(address="0x...")
64
- client.create_session(operator_token="opc_...") # KYC refresh
65
- client.create_session(kind="sign_in") # registration-only: account sign-in, no identity documents
61
+ # Registration-only session: the buyer signs in with an AgentScore account, no identity documents.
62
+ client.create_session(kind="sign_in")
66
63
  ```
67
64
 
68
65
  ### Wallet resolution
@@ -111,7 +111,7 @@ def _build_error_from_response(response: httpx.Response) -> AgentScoreError:
111
111
  # verify_url, linked_wallets, reasons, etc. for granular denial recovery.
112
112
  details = {k: v for k, v in body.items() if k != "error"}
113
113
  except ValueError:
114
- # Body wasn't JSON or didn't have the expected shape — keep defaults.
114
+ # Body wasn't JSON or didn't have the expected shape: keep defaults.
115
115
  pass
116
116
 
117
117
  if response.status_code == 402:
@@ -257,7 +257,9 @@ class AgentScore:
257
257
  aip_token: str | None = None,
258
258
  aip_signature: AipSignatureMaterial | None = None,
259
259
  ) -> AssessResponse:
260
- """Assess a wallet or operator (paid, writes score on-the-fly).
260
+ """Assess a wallet or operator against a compliance policy.
261
+
262
+ ``refresh`` is deprecated: the API ignores it, and every assess is evaluated live.
261
263
 
262
264
  ``signer`` opts into server-side wallet-signer-match: when supplied,
263
265
  the API resolves the signer wallet against the claimed ``address`` and emits
@@ -303,9 +305,8 @@ class AgentScore:
303
305
  ) -> SessionCreateResponse:
304
306
  """Create a verification or sign-in session.
305
307
 
306
- ``address`` pre-associates the session with a known wallet (EVM ``0x...`` or
307
- Solana base58). ``operator_token`` pre-associates with an existing ``opc_...`` —
308
- e.g. refresh KYC for a credential. ``kind`` selects the session kind: ``"kyc"``
308
+ ``address`` and ``operator_token`` are deprecated: the API ignores them, so a
309
+ session is never pre-associated with a wallet or credential. ``kind`` selects the session kind: ``"kyc"``
309
310
  (the API default) runs identity verification; ``"sign_in"`` is registration-only
310
311
  (the buyer signs in with an AgentScore account, no identity documents) and mints a
311
312
  ``sign_in``-scoped credential, for merchants that key durable state on the account
@@ -368,20 +369,20 @@ class AgentScore:
368
369
  ) -> AssociateWalletResponse:
369
370
  """Report that a wallet paid under an operator credential.
370
371
 
371
- ``network`` is the key-derivation family (``"evm"`` or ``"solana"``) — EVM EOAs share
372
+ ``network`` is the key-derivation family (``"evm"`` or ``"solana"``): EVM EOAs share
372
373
  identity across every EVM chain (Base, Tempo, Ethereum, …) so one value covers them all.
373
374
 
374
- ``idempotency_key`` is optional — pass a stable per-payment key (e.g., payment intent id,
375
+ ``idempotency_key`` is optional: pass a stable per-payment key (e.g., payment intent id,
375
376
  x402 tx hash) so agent retries of the same logical payment don't inflate transaction_count.
376
377
 
377
- Fire-and-forget friendly — the returned ``first_seen`` boolean is informational only.
378
+ Fire-and-forget friendly: the returned ``first_seen`` boolean is informational only.
378
379
  """
379
380
  body: dict[str, Any] = {
380
381
  "operator_token": operator_token,
381
382
  "wallet_address": wallet_address,
382
383
  "network": network,
383
384
  }
384
- # Truthy check (not `is not None`) so empty strings don't ship a useless key —
385
+ # Truthy check (not `is not None`) so empty strings don't ship a useless key:
385
386
  # only forward when the key actually has content.
386
387
  if idempotency_key:
387
388
  if len(idempotency_key) > _IDEMPOTENCY_KEY_MAX:
@@ -406,7 +407,9 @@ class AgentScore:
406
407
  aip_token: str | None = None,
407
408
  aip_signature: AipSignatureMaterial | None = None,
408
409
  ) -> AssessResponse:
409
- """Assess a wallet or operator (paid, writes score on-the-fly).
410
+ """Assess a wallet or operator against a compliance policy.
411
+
412
+ ``refresh`` is deprecated: the API ignores it, and every assess is evaluated live.
410
413
 
411
414
  ``signer`` opts into server-side wallet-signer-match; ``aip_token`` (+ ``aip_signature``)
412
415
  supplies an AIP Agent Identity Token. Async mirror of :meth:`assess`.
@@ -445,9 +448,8 @@ class AgentScore:
445
448
  ) -> SessionCreateResponse:
446
449
  """Create a verification or sign-in session.
447
450
 
448
- ``address`` pre-associates the session with a known wallet (EVM ``0x...`` or
449
- Solana base58). ``operator_token`` pre-associates with an existing ``opc_...`` —
450
- e.g. refresh KYC for a credential. ``kind`` selects the session kind: ``"kyc"``
451
+ ``address`` and ``operator_token`` are deprecated: the API ignores them, so a
452
+ session is never pre-associated with a wallet or credential. ``kind`` selects the session kind: ``"kyc"``
451
453
  (the API default) runs identity verification; ``"sign_in"`` is registration-only
452
454
  (the buyer signs in with an AgentScore account, no identity documents) and mints a
453
455
  ``sign_in``-scoped credential, for merchants that key durable state on the account
@@ -514,7 +516,7 @@ class AgentScore:
514
516
  "wallet_address": wallet_address,
515
517
  "network": network,
516
518
  }
517
- # Truthy check (not `is not None`) so empty strings don't ship a useless key —
519
+ # Truthy check (not `is not None`) so empty strings don't ship a useless key:
518
520
  # only forward when the key actually has content.
519
521
  if idempotency_key:
520
522
  if len(idempotency_key) > _IDEMPOTENCY_KEY_MAX:
@@ -527,7 +529,7 @@ class AgentScore:
527
529
  return await self._send_async(lambda: client.post("/v1/credentials/wallets", json=body))
528
530
 
529
531
  def telemetry_signer_match(self, payload: dict[str, Any]) -> None:
530
- """Fire-and-forget telemetry — report a wallet-signer-match verdict.
532
+ """Fire-and-forget telemetry: report a wallet-signer-match verdict.
531
533
 
532
534
  Tracks aggregate signer-binding behavior across merchants. Does not raise;
533
535
  failures are logged at warning level so persistent telemetry outages are visible
@@ -12,7 +12,7 @@ class AgentScoreError(Exception):
12
12
  super().__init__(message)
13
13
  self.code = code
14
14
  self.status_code = status_code
15
- # Response-body fields beyond `error.{code,message}` — e.g. verify_url,
15
+ # Response-body fields beyond `error.{code,message}`: e.g. verify_url,
16
16
  # linked_wallets, claimed_operator, actual_signer, reasons. Consumers
17
17
  # branch on these for granular recovery. Defaults to {} so callers
18
18
  # constructing this error by hand without a body can omit it.
@@ -25,17 +25,17 @@ class AgentScoreError(Exception):
25
25
 
26
26
 
27
27
  class PaymentRequiredError(AgentScoreError):
28
- """HTTP 402 — the endpoint is not enabled for this account."""
28
+ """HTTP 402: the endpoint is not enabled for this account."""
29
29
 
30
30
  def __init__(self, message: str, details: dict[str, Any] | None = None) -> None:
31
31
  super().__init__("payment_required", message, 402, details)
32
32
 
33
33
 
34
34
  class TokenExpiredError(AgentScoreError):
35
- """HTTP 401 with ``error.code = 'token_expired'`` — credential is no longer valid.
35
+ """HTTP 401 with ``error.code = 'token_expired'``: credential is no longer valid.
36
36
 
37
37
  Covers both revoked and TTL-expired credentials; the API does not distinguish
38
- which. Body carries an auto-minted verification session — exposed here so callers recover
38
+ which. Body carries an auto-minted verification session: exposed here so callers recover
39
39
  without re-parsing ``details``.
40
40
  """
41
41
 
@@ -51,7 +51,7 @@ class TokenExpiredError(AgentScoreError):
51
51
 
52
52
 
53
53
  class InvalidCredentialError(AgentScoreError):
54
- """HTTP 401 with ``error.code = 'invalid_credential'`` — operator_token doesn't exist.
54
+ """HTTP 401 with ``error.code = 'invalid_credential'``: operator_token doesn't exist.
55
55
 
56
56
  Permanent: no auto-session is issued. Caller should switch tokens or restart.
57
57
  """
@@ -61,7 +61,7 @@ class InvalidCredentialError(AgentScoreError):
61
61
 
62
62
 
63
63
  class QuotaExceededError(AgentScoreError):
64
- """HTTP 429 with ``error.code = 'quota_exceeded'`` — account-level cap reached.
64
+ """HTTP 429 with ``error.code = 'quota_exceeded'``: account-level cap reached.
65
65
 
66
66
  Don't retry; the cap won't lift through retry alone. Distinct from per-second
67
67
  :class:`RateLimitedError`.
@@ -72,7 +72,7 @@ class QuotaExceededError(AgentScoreError):
72
72
 
73
73
 
74
74
  class RateLimitedError(AgentScoreError):
75
- """HTTP 429 with ``error.code = 'rate_limited'`` — per-second sliding-window cap hit.
75
+ """HTTP 429 with ``error.code = 'rate_limited'``: per-second sliding-window cap hit.
76
76
 
77
77
  Retry after the interval indicated by the ``Retry-After`` header (typically <= 1s).
78
78
  """
@@ -2,7 +2,7 @@
2
2
 
3
3
  AgentScore's ``/v1/assess`` endpoint recognizes seven EVM addresses
4
4
  (``0x0000…0001`` through ``0x0000…0007``) as test fixtures with deterministic
5
- policy outcomes — KYC verified, sanctions clear, age gates passing — so dev/test
5
+ policy outcomes (KYC verified, sanctions clear, age gates passing) so dev/test
6
6
  interactions don't burn real KYC credits and produce predictable results.
7
7
 
8
8
  Use this in test suites and dev/staging tooling to label test-mode interactions
@@ -40,12 +40,12 @@ class Signer(TypedDict):
40
40
  When passed to ``assess()`` / ``aassess()``, the API resolves this signer wallet
41
41
  against the claimed ``address`` and emits a ``signer_match`` block on the response.
42
42
  Lets commerce gates collapse the legacy 2 follow-up assess calls (one per wallet)
43
- into the gate's primary assess call. Strictly additive — old clients that don't
43
+ into the gate's primary assess call. Strictly additive: old clients that don't
44
44
  pass this field see no ``signer_match`` on the response.
45
45
  """
46
46
 
47
47
  # Recovered payment-signer wallet. ``None`` indicates the rail carries no wallet
48
- # signature (Stripe SPT, card) — produces
48
+ # signature (Stripe SPT, card): produces
49
49
  # ``signer_match.kind = "wallet_auth_requires_wallet_signing"``.
50
50
  address: str | None
51
51
  # Key-derivation family of the signer wallet.
@@ -63,24 +63,24 @@ class SignerMatch(TypedDict):
63
63
  ``kind`` is always present; other fields depend on which kind was emitted.
64
64
  """
65
65
 
66
- # ``pass`` — claimed wallet and signer wallet resolve to the same operator (or are
67
- # byte-equal). ``wallet_signer_mismatch`` — operators differ.
68
- # ``wallet_auth_requires_wallet_signing`` — request supplied ``address: None`` (rail
66
+ # ``pass``: claimed wallet and signer wallet resolve to the same operator (or are
67
+ # byte-equal). ``wallet_signer_mismatch``: operators differ.
68
+ # ``wallet_auth_requires_wallet_signing``: request supplied ``address: None`` (rail
69
69
  # has no wallet signer); agent should switch to operator_token auth.
70
70
  kind: Literal["pass", "wallet_signer_mismatch", "wallet_auth_requires_wallet_signing"]
71
71
  # Operator the claimed wallet resolves to. ``None`` if unlinked.
72
72
  claimed_operator: NotRequired[str | None]
73
73
  # Operator the signer wallet resolves to. ``None`` if unlinked.
74
74
  signer_operator: NotRequired[str | None]
75
- # Echoed only on ``wallet_auth_requires_wallet_signing`` — the claimed wallet from
75
+ # Echoed only on ``wallet_auth_requires_wallet_signing``: the claimed wallet from
76
76
  # the request. Helps agents construct the recovery message.
77
77
  claimed_wallet: NotRequired[str]
78
- # Echoed on ``wallet_signer_mismatch`` — the claimed wallet, normalized.
78
+ # Echoed on ``wallet_signer_mismatch``: the claimed wallet, normalized.
79
79
  expected_signer: NotRequired[str]
80
- # Echoed on ``wallet_signer_mismatch`` — the signer wallet, normalized.
80
+ # Echoed on ``wallet_signer_mismatch``: the signer wallet, normalized.
81
81
  actual_signer: NotRequired[str]
82
82
  # Same-operator linked wallets the agent could re-sign from to satisfy the claim.
83
- # Mirrors the top-level ``linked_wallets`` deny-guard — omitted on ``deny`` verdicts.
83
+ # Mirrors the top-level ``linked_wallets`` deny-guard: omitted on ``deny`` verdicts.
84
84
  linked_wallets: NotRequired[list[str]]
85
85
  # JSON-encoded ``{action, steps, user_message}`` envelope for SDK denial bodies.
86
86
  # SDK consumers spread this into their 403
@@ -89,13 +89,13 @@ class SignerMatch(TypedDict):
89
89
 
90
90
 
91
91
  class SignerSanctionsClear(TypedDict):
92
- """Server-side wallet-sanctions verdict — address NOT on the OFAC SDN list."""
92
+ """Server-side wallet-sanctions verdict: address NOT on the OFAC SDN list."""
93
93
 
94
94
  status: Literal["clear"]
95
95
 
96
96
 
97
97
  class SignerSanctionsHit(TypedDict):
98
- """Server-side wallet-sanctions verdict — address IS on the OFAC SDN list.
98
+ """Server-side wallet-sanctions verdict: address IS on the OFAC SDN list.
99
99
 
100
100
  Wallet-OFAC enforcement on the ``signer`` block is unconditional whenever
101
101
  a signer is supplied. This verdict flips the response ``decision`` to
@@ -119,7 +119,7 @@ class SignerSanctionsHit(TypedDict):
119
119
 
120
120
 
121
121
  class SignerSanctionsUnavailable(TypedDict):
122
- """Server-side wallet-sanctions verdict — lookup itself failed.
122
+ """Server-side wallet-sanctions verdict: lookup itself failed.
123
123
 
124
124
  Fail-closed: the gate denies whenever a signer was supplied and the OFAC
125
125
  lookup couldn't resolve, because OFAC sanctions screening is strict-liability.
@@ -190,8 +190,8 @@ class QuotaInfo(TypedDict):
190
190
 
191
191
  limit: int | None
192
192
  used: int | None
193
- # ``X-Quota-Reset`` is an ISO-8601 timestamp, or the literal string "never" for lifetime
194
- # caps. The API emits "never" for tiers without a reset.
193
+ # ``X-Quota-Reset`` is the UTC date (``YYYY-MM-DD``) the calendar-month quota resets,
194
+ # e.g. "2026-06-01", or "never" when the API has no reset date for the account.
195
195
  reset: str | None
196
196
 
197
197
 
@@ -240,7 +240,7 @@ class SessionCreateRequest(TypedDict, total=False):
240
240
  class SessionCreateNextSteps(TypedDict, total=False):
241
241
  """Structured action guidance on POST /v1/sessions success.
242
242
 
243
- action is always ``deliver_verify_url_and_poll`` — tells the agent to share verify_url
243
+ action is always ``deliver_verify_url_and_poll``: tells the agent to share verify_url
244
244
  with the user and poll poll_url with X-Poll-Secret until an operator_token is issued.
245
245
  """
246
246
 
@@ -384,27 +384,27 @@ DenialCode = Literal[
384
384
  # Gate-emitted codes from commerce middleware (canonical 9-element union)
385
385
  "missing_identity",
386
386
  "identity_verification_required",
387
- # Credential is no longer valid (revoked or past its TTL — the two cases share this
387
+ # Credential is no longer valid (revoked or past its TTL: the two cases share this
388
388
  # code deliberately so the API doesn't leak which one). The 401 body carries an
389
389
  # auto-minted session so agents recover without holding an API key.
390
390
  "token_expired",
391
391
  # Credential doesn't exist at all (typo, fabricated, never minted). Permanent state;
392
392
  # no auto-session is issued because the agent may have other valid tokens to try.
393
393
  "invalid_credential",
394
- # Wallet-signer binding — claimed X-Wallet-Address must resolve to the same operator
394
+ # Wallet-signer binding: claimed X-Wallet-Address must resolve to the same operator
395
395
  # as the payment signer; wallet-auth is rejected on rails with no wallet signer.
396
396
  "wallet_signer_mismatch",
397
397
  "wallet_auth_requires_wallet_signing",
398
398
  "wallet_not_trusted",
399
399
  "api_error",
400
400
  "payment_required",
401
- # Merchant-emitted convenience codes — not emitted by the AgentScore API itself,
401
+ # Merchant-emitted convenience codes: not emitted by the AgentScore API itself,
402
402
  # but may appear in 4xx bodies passing through the SDK from merchants whose gate
403
403
  # `on_denied` hook wraps AgentScore denials into business-domain codes.
404
404
  "operator_verification_required",
405
405
  "compliance_denied",
406
406
  "compliance_error",
407
- # Decision-reason code surfaced in error.code by some merchants — kept for back-compat
407
+ # Decision-reason code surfaced in error.code by some merchants: kept for back-compat
408
408
  # with merchants that flatten policy reasons into the error envelope.
409
409
  "kyc_required",
410
410
  ]
@@ -422,15 +422,15 @@ NextStepsAction = Literal[
422
422
  "done",
423
423
  "use_operator_token",
424
424
  "regenerate_payment_from_linked_wallet",
425
- # Gate-emitted probe strategy — try wallet on signing rails, fall back to stored
425
+ # Gate-emitted probe strategy: try wallet on signing rails, fall back to stored
426
426
  # opc_..., fall back to session flow. Emitted on bare missing_identity 403s.
427
427
  "probe_identity_then_session",
428
- # Wallet signer mismatch — re-sign from expected_signer / any linked_wallets entry,
428
+ # Wallet signer mismatch: re-sign from expected_signer / any linked_wallets entry,
429
429
  # or drop X-Wallet-Address and retry with X-Operator-Token.
430
430
  "resign_or_switch_to_operator_token",
431
- # Non-signing rail (Stripe SPT, card) — drop X-Wallet-Address, use X-Operator-Token.
431
+ # Non-signing rail (Stripe SPT, card): drop X-Wallet-Address, use X-Operator-Token.
432
432
  "switch_to_operator_token",
433
- # POST /v1/sessions success — deliver verify_url + poll poll_url.
433
+ # POST /v1/sessions success: deliver verify_url + poll poll_url.
434
434
  "deliver_verify_url_and_poll",
435
435
  # Session poll states.
436
436
  "continue_polling",
@@ -453,7 +453,7 @@ class AgentMemoryHint(TypedDict):
453
453
  """Structured hint emitted on bootstrap denials and POST /v1/sessions success.
454
454
 
455
455
  LLM-hosted agents should save this to their long-term memory so future AgentScore encounters
456
- skip the cold-start session flow. Contents are the pattern, not secrets —
456
+ skip the cold-start session flow. Contents are the pattern, not secrets:
457
457
  do_not_persist_in_memory names bearer credentials that belong in a credential store.
458
458
  """
459
459
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "agentscore-py"
7
- version = "2.7.0"
7
+ version = "2.7.1"
8
8
  description = "Python client for the AgentScore APIs"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -63,7 +63,8 @@ include = ["agentscore"]
63
63
  required-version = ">=0.11.0"
64
64
  # An unbounded transitive requirement lets a routine refresh resolve a BETA and
65
65
  # drag a subtree with it, and the tests pass either way, so nothing reports it.
66
- # That is how python-commerce once resolved web3 8.0.0b3 through three
67
- # unbounded parents. Blocking prereleases outright beats pinning whichever
68
- # package surfaced last, since the same shape exists on several transitives.
69
- prerelease = "disallow"
66
+ # `if-necessary` admits a prerelease only for a package that has never shipped
67
+ # a stable release; a package that reaches for a beta it does not need gets its
68
+ # own constraint (python-commerce pins web3 below 8). A blanket `disallow` also
69
+ # refuses the never-stable packages and silently holds back whatever needs them.
70
+ prerelease = "if-necessary"
@@ -674,7 +674,7 @@ def test_connect_error_raises_agentscore_error():
674
674
  client = AgentScore(api_key=API_KEY)
675
675
  with pytest.raises(AgentScoreError) as exc_info:
676
676
  client.list_credentials()
677
- # All httpx-layer errors (Timeout, Connect, Protocol, Network) are wrapped — parity with node-sdk.
677
+ # All httpx-layer errors (Timeout, Connect, Protocol, Network) are wrapped: parity with node-sdk.
678
678
  # ConnectError specifically maps to network_error; TimeoutException is the only one that becomes TimeoutError.
679
679
  assert exc_info.value.code == "network_error"
680
680
  assert exc_info.value.status_code == 0
@@ -1413,7 +1413,7 @@ def test_associate_wallet_omits_idempotency_key_when_not_provided():
1413
1413
 
1414
1414
  @respx.mock
1415
1415
  def test_associate_wallet_omits_empty_string_idempotency_key():
1416
- """Empty string is not a valid key — match node-sdk behavior and skip forwarding."""
1416
+ """Empty string is not a valid key: match node-sdk behavior and skip forwarding."""
1417
1417
  route = respx.post(f"{BASE_URL}/v1/credentials/wallets").mock(
1418
1418
  return_value=httpx.Response(200, json={"associated": True, "first_seen": True}),
1419
1419
  )
@@ -1800,7 +1800,7 @@ def test_unknown_4xx_falls_through_to_generic_agentscore_error():
1800
1800
  client = AgentScore(api_key=API_KEY)
1801
1801
  with pytest.raises(AgentScoreError) as exc_info:
1802
1802
  client.assess(address=ADDRESS)
1803
- # Not a typed subclass — generic AgentScoreError.
1803
+ # Not a typed subclass: generic AgentScoreError.
1804
1804
  assert type(exc_info.value) is AgentScoreError
1805
1805
  assert exc_info.value.status_code == 403
1806
1806
  assert exc_info.value.code == "account_cancelled"
@@ -2119,7 +2119,7 @@ def test_unknown_400_invalid_request_falls_through_to_generic_agentscore_error()
2119
2119
  assert not isinstance(err, QuotaExceededError)
2120
2120
  assert not isinstance(err, RateLimitedError)
2121
2121
  assert not isinstance(err, AgentScoreTimeoutError)
2122
- # Generic — type is exactly AgentScoreError, not a subclass.
2122
+ # Generic: type is exactly AgentScoreError, not a subclass.
2123
2123
  assert type(err) is AgentScoreError
2124
2124
  assert err.code == "invalid_request"
2125
2125
  assert err.status_code == 400
@@ -2129,7 +2129,7 @@ def test_unknown_400_invalid_request_falls_through_to_generic_agentscore_error()
2129
2129
  @respx.mock
2130
2130
  def test_token_expired_error_fields_undefined_when_api_omits_them():
2131
2131
  """If API returns 401 token_expired with no verify_url / session_id / poll_secret /
2132
- next_steps / agent_memory in the body, the instance fields stay None — error is still
2132
+ next_steps / agent_memory in the body, the instance fields stay None: error is still
2133
2133
  a TokenExpiredError (not falling through to generic)."""
2134
2134
  from agentscore.errors import TokenExpiredError
2135
2135
 
@@ -2169,7 +2169,7 @@ def test_token_expired_error_ignores_wrong_typed_body_fields():
2169
2169
  with pytest.raises(TokenExpiredError) as exc_info:
2170
2170
  client.assess(address=ADDRESS)
2171
2171
  err = exc_info.value
2172
- # Strings only — wrong types ignored, instance fields stay None.
2172
+ # Strings only: wrong types ignored, instance fields stay None.
2173
2173
  assert err.verify_url is None
2174
2174
  assert err.session_id is None
2175
2175
  # Raw values still in details for inspection.
@@ -2339,7 +2339,7 @@ async def test_aassess_forwards_chain_refresh_and_policy():
2339
2339
  @pytest.mark.asyncio
2340
2340
  @respx.mock
2341
2341
  async def test_acreate_credential_omits_unset_fields():
2342
- """Both `label` and `ttl_days` default to None — the body must stay empty (covers the
2342
+ """Both `label` and `ttl_days` default to None: the body must stay empty (covers the
2343
2343
  falsy side of both `is not None` branches)."""
2344
2344
  route = respx.post(f"{BASE_URL}/v1/credentials").mock(
2345
2345
  return_value=httpx.Response(200, json=CREDENTIAL_CREATE_PAYLOAD)
@@ -2354,7 +2354,7 @@ async def test_acreate_credential_omits_unset_fields():
2354
2354
  @pytest.mark.asyncio
2355
2355
  @respx.mock
2356
2356
  async def test_acreate_credential_forwards_label_and_ttl():
2357
- """Truthy side of both branches — label and ttl_days are forwarded."""
2357
+ """Truthy side of both branches: label and ttl_days are forwarded."""
2358
2358
  route = respx.post(f"{BASE_URL}/v1/credentials").mock(
2359
2359
  return_value=httpx.Response(200, json=CREDENTIAL_CREATE_PAYLOAD)
2360
2360
  )
@@ -110,6 +110,6 @@ def test_session_create_response_accepts_agent_memory() -> None:
110
110
 
111
111
 
112
112
  def test_sdk_exports_new_symbols() -> None:
113
- # Importability check — covered above via imports, but also guard the AgentScore class itself.
113
+ # Importability check: covered above via imports, but also guard the AgentScore class itself.
114
114
  assert AgentScore is not None
115
115
  assert AgentScoreError is not None
@@ -2,12 +2,9 @@ version = 1
2
2
  revision = 5
3
3
  requires-python = ">=3.11"
4
4
 
5
- [options]
6
- prerelease-mode = "disallow"
7
-
8
5
  [[package]]
9
6
  name = "agentscore-py"
10
- version = "2.7.0"
7
+ version = "2.7.1"
11
8
  source = { editable = "." }
12
9
  dependencies = [
13
10
  { name = "httpx" },
@@ -1,4 +1,4 @@
1
- # Vulture whitelist — false positives for required function signatures
1
+ # Vulture whitelist: false positives for required function signatures
2
2
 
3
3
  # Context manager __exit__ requires (*args) signature
4
4
  args # noqa: F821
@@ -1,76 +0,0 @@
1
- # agentscore-py
2
-
3
- Python client for the AgentScore APIs.
4
-
5
- ## Identity Model
6
-
7
- Two identity paths: `X-Wallet-Address` (wallet-based) and `X-Operator-Token` (credential-based). Wallet addresses accept both EVM (`0x...` 40-hex) and Solana (base58, 32–44 chars) formats — network is auto-detected from the address shape. `assess` responses include `resolved_operator` and `linked_wallets[]` (same-operator sibling wallets, normalized per network — EVM lowercased, Solana base58 verbatim; may mix chains for cross-chain operators). `create_session` and `create_credential` responses include an `agent_memory` cross-merchant pattern hint. `create_session` also returns `next_steps.action="deliver_verify_url_and_poll"` + polling instructions. `poll_session` returns `next_steps.action` values: `continue_polling`, `retry_merchant_request_with_operator_token`, `use_stored_operator_token`, `create_new_session`, `verification_failed`, `contact_support`.
8
-
9
- ## Methods (sync + async)
10
-
11
- - `assess` / `aassess` — identity gate with policy (paid). Accepts `operator_token` for non-wallet agents. Response includes `linked_wallets[]` and `resolved_operator`. Optional `signer: { address, network }` opts into server-side wallet-signer-match AND OFAC SDN wallet-address screening — the response then carries both a `signer_match` block (wallet-binding verdict: `pass` / `wallet_signer_mismatch` / `wallet_auth_requires_wallet_signing`) and a `signer_sanctions` block (discriminated union: `{status: "clear"}` | `{sanctioned: True, ofac_label, sdn_uid, listed_at}` | `{status: "unavailable"}`). Wallet-OFAC SDN enforcement on the `signer` block is unconditional whenever a signer is supplied — no `policy.require_sanctions_clear` opt-in required. A `sanctioned: True` OR `status: "unavailable"` verdict flips the response `decision` to `deny` with `decision_reasons` including `sanctions_flagged` or `sanctions_check_unavailable` respectively (fail-closed; OFAC strict-liability). `policy.require_sanctions_clear` is the separate NAME-based screen on the resolved operator's KYC identity.
12
- - `create_session` / `acreate_session` — create verification session. Returns `agent_memory` + `next_steps`.
13
- - `poll_session` / `apoll_session` — poll session status, returns credential when verified, plus `next_steps.action`.
14
- - `create_credential` / `acreate_credential` — create operator credential (24h TTL default). Response includes `agent_memory`.
15
- - `list_credentials` / `alist_credentials` — list active credentials
16
- - `revoke_credential` / `arevoke_credential` — revoke a credential
17
- - `associate_wallet` / `aassociate_wallet` — report a signer wallet seen paying under a credential. Accepts optional `idempotency_key` (payment intent id / tx hash) so retries don't inflate transaction_count.
18
- - `telemetry_signer_match` / `atelemetry_signer_match` — fire-and-forget POST to `/v1/telemetry/signer-match`; commerce gate uses this to report `pass` / `wallet_signer_mismatch` / `wallet_auth_requires_wallet_signing` verdicts.
19
-
20
- ## Errors + observability
21
-
22
- Typed error subclasses of `AgentScoreError` so callers can `except` on the specific class without parsing `err.code`: `PaymentRequiredError` (402), `TokenExpiredError` (401 token_expired — exposes parsed `verify_url` / `session_id` / `poll_secret` / `poll_url` / `next_steps` / `agent_memory` instance attributes), `InvalidCredentialError` (401 invalid_credential), `QuotaExceededError` (429 quota_exceeded — don't retry), `RateLimitedError` (429 rate_limited — retry after Retry-After), `TimeoutError` (httpx.TimeoutException — note: subclasses `AgentScoreError`, not the builtin; import explicitly from `agentscore.errors` to disambiguate). All non-timeout `httpx.HTTPError` (ConnectError, ProtocolError, NetworkError, etc.) wrap to `AgentScoreError(code="network_error", status_code=0)` for parity with node-sdk.
23
-
24
- `assess()` / `aassess()` responses include an optional `quota` field captured from `X-Quota-Limit` / `X-Quota-Used` / `X-Quota-Reset` response headers, so callers can monitor approach-to-cap proactively before hitting 429.
25
-
26
- ## Architecture
27
-
28
- Single-package Python library published to PyPI.
29
-
30
- | File | Purpose |
31
- |------|---------|
32
- | `agentscore/` | Source code |
33
- | `tests/` | pytest tests |
34
-
35
- ## Tooling
36
-
37
- - **uv** — package manager. Use `uv sync`, `uv run`.
38
- - **ruff** — linting + formatting. `uv run ruff check .` and `uv run ruff format --check .`.
39
- - **ty** — type checker (Astral). `uv run ty check agentscore/`.
40
- - **vulture** — dead code detection.
41
- - **pytest** — tests. `uv run pytest tests/`.
42
- - **Lefthook** — git hooks. Pre-commit: ruff. Pre-push: ty + vulture (parallel).
43
-
44
- ## Key Commands
45
-
46
- ```bash
47
- uv sync --all-extras
48
- uv run lefthook install # one-time per clone — wires pre-commit + pre-push
49
- uv run ruff check .
50
- uv run ruff format .
51
- uv run ty check agentscore/
52
- uv run pytest tests/
53
- ```
54
-
55
- ## Workflow
56
-
57
- 1. Create a branch
58
- 2. Make changes
59
- 3. Lefthook runs ruff on commit, ty + vulture on push
60
- 4. Open a PR — CI runs automatically
61
- 5. Merge (squash)
62
-
63
- ## Rules
64
-
65
- - **No silent refactors**
66
- - **Never commit .env files or secrets**
67
- - **Use PRs** — never push directly to main
68
-
69
- ## Releasing
70
-
71
- 1. Update `version` in `pyproject.toml`
72
- 2. Commit: `git commit -am "chore: bump to vX.Y.Z"`
73
- 3. Tag: `git tag vX.Y.Z`
74
- 4. Push: `git push && git push origin vX.Y.Z`
75
-
76
- The publish workflow runs on `ubuntu-latest` (required for PyPI trusted publishing), builds, publishes to PyPI via OIDC, and creates a GitHub Release.
File without changes
File without changes
File without changes
File without changes