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.
- agentscore_py-2.7.1/.claude/CLAUDE.md +76 -0
- {agentscore_py-2.7.0 → agentscore_py-2.7.1}/.github/workflows/ci.yml +2 -2
- {agentscore_py-2.7.0 → agentscore_py-2.7.1}/.github/workflows/publish.yml +1 -1
- {agentscore_py-2.7.0 → agentscore_py-2.7.1}/.github/workflows/security.yml +1 -1
- {agentscore_py-2.7.0 → agentscore_py-2.7.1}/PKG-INFO +3 -6
- {agentscore_py-2.7.0 → agentscore_py-2.7.1}/README.md +2 -5
- {agentscore_py-2.7.0 → agentscore_py-2.7.1}/agentscore/client.py +17 -15
- {agentscore_py-2.7.0 → agentscore_py-2.7.1}/agentscore/errors.py +7 -7
- {agentscore_py-2.7.0 → agentscore_py-2.7.1}/agentscore/test_mode.py +1 -1
- {agentscore_py-2.7.0 → agentscore_py-2.7.1}/agentscore/types.py +24 -24
- {agentscore_py-2.7.0 → agentscore_py-2.7.1}/pyproject.toml +6 -5
- {agentscore_py-2.7.0 → agentscore_py-2.7.1}/tests/test_client.py +8 -8
- {agentscore_py-2.7.0 → agentscore_py-2.7.1}/tests/test_types.py +1 -1
- {agentscore_py-2.7.0 → agentscore_py-2.7.1}/uv.lock +1 -4
- {agentscore_py-2.7.0 → agentscore_py-2.7.1}/vulture_whitelist.py +1 -1
- agentscore_py-2.7.0/.claude/CLAUDE.md +0 -76
- {agentscore_py-2.7.0 → agentscore_py-2.7.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {agentscore_py-2.7.0 → agentscore_py-2.7.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {agentscore_py-2.7.0 → agentscore_py-2.7.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {agentscore_py-2.7.0 → agentscore_py-2.7.1}/.github/dependabot.yml +0 -0
- {agentscore_py-2.7.0 → agentscore_py-2.7.1}/.gitignore +0 -0
- {agentscore_py-2.7.0 → agentscore_py-2.7.1}/CODE_OF_CONDUCT.md +0 -0
- {agentscore_py-2.7.0 → agentscore_py-2.7.1}/CONTRIBUTING.md +0 -0
- {agentscore_py-2.7.0 → agentscore_py-2.7.1}/LICENSE +0 -0
- {agentscore_py-2.7.0 → agentscore_py-2.7.1}/SECURITY.md +0 -0
- {agentscore_py-2.7.0 → agentscore_py-2.7.1}/agentscore/__init__.py +0 -0
- {agentscore_py-2.7.0 → agentscore_py-2.7.1}/agentscore/py.typed +0 -0
- {agentscore_py-2.7.0 → agentscore_py-2.7.1}/lefthook.yml +0 -0
- {agentscore_py-2.7.0 → agentscore_py-2.7.1}/ruff.toml +0 -0
- {agentscore_py-2.7.0 → agentscore_py-2.7.1}/tests/__init__.py +0 -0
- {agentscore_py-2.7.0 → agentscore_py-2.7.1}/tests/conftest.py +0 -0
- {agentscore_py-2.7.0 → agentscore_py-2.7.1}/tests/test_errors.py +0 -0
- {agentscore_py-2.7.0 → agentscore_py-2.7.1}/tests/test_integration.py +0 -0
- {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.
|
|
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.
|
|
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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: agentscore-py
|
|
3
|
-
Version: 2.7.
|
|
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
|
-
#
|
|
90
|
-
|
|
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
|
-
#
|
|
62
|
-
|
|
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
|
|
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
|
|
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``
|
|
307
|
-
|
|
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"``)
|
|
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
|
|
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
|
|
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
|
|
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``
|
|
449
|
-
|
|
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
|
|
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}
|
|
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
|
|
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'
|
|
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
|
|
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'
|
|
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'
|
|
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'
|
|
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
|
|
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
|
|
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)
|
|
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
|
|
67
|
-
# byte-equal). ``wallet_signer_mismatch
|
|
68
|
-
# ``wallet_auth_requires_wallet_signing
|
|
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
|
|
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
|
|
78
|
+
# Echoed on ``wallet_signer_mismatch``: the claimed wallet, normalized.
|
|
79
79
|
expected_signer: NotRequired[str]
|
|
80
|
-
# Echoed on ``wallet_signer_mismatch
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
194
|
-
#
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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)
|
|
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
|
|
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.
|
|
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
|
-
#
|
|
67
|
-
#
|
|
68
|
-
#
|
|
69
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
7
|
+
version = "2.7.1"
|
|
11
8
|
source = { editable = "." }
|
|
12
9
|
dependencies = [
|
|
13
10
|
{ name = "httpx" },
|
|
@@ -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
|
|
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
|