Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Every helper is extracted from a real consumer, not speculated.
|---|---|
| `agentscore_commerce.identity.{fastapi,flask,django,aiohttp,sanic,middleware}` | Trust gate middleware (KYC, age, sanctions, jurisdiction) |
| `agentscore_commerce.payment` | Networks/USDC/rails registries, paymentauth.org directive builders, `create_x402_server` (wraps official `x402[evm,svm]>=2.8` peer dep with v1+v2 dual-register + bazaar extension), `create_mppx_server` (wraps `pympp[server,tempo,stripe]>=0.6` peer dep with Tempo charge/session + Stripe SPT helpers), dispatch-by-network, signer extraction, WWW-Authenticate header, Settlement-Overrides header |
| `agentscore_commerce.discovery` | Discovery probe, Bazaar wrapper, `/.well-known/mpp.json`, `llms.txt` builder, OpenAPI snippets, `NoindexNonDiscoveryMiddleware` ASGI middleware |
| `agentscore_commerce.discovery` | Discovery probe, Bazaar wrapper, `/.well-known/mpp.json`, `llms.txt` builder, `skill.md` builder (Claude-Skill-compatible agent-discovery manifest), OpenAPI snippets, `NoindexNonDiscoveryMiddleware` ASGI middleware |
| `agentscore_commerce.challenge` | 402-body builders: accepted_methods, identity_metadata, how_to_pay, agent_instructions, build_402_body, `build_validation_error` (4xx body builder) |
| `agentscore_commerce.stripe_multichain` | Multichain PaymentIntent helper, deposit-address lookup, testnet simulator, mppx Stripe wrapper |
| `agentscore_commerce.api` | Re-exports `AgentScore` from `agentscore` SDK |
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ pip install agentscore-commerce[fastapi] # or [flask], [django], [aiohttp], [s
| `agentscore_commerce.identity.{fastapi,flask,django,aiohttp,sanic,middleware}` | Trust gate middleware: KYC, sanctions, age, jurisdiction. `AgentScoreGate(...)` (or `agentscore_gate(app, ...)` on Flask/Sanic), `get_assess_data(...)`, `capture_wallet(...)`, `verify_wallet_signer_match(...)`. |
| `agentscore_commerce.identity` (package level) | Re-exports the denial helpers: `denial_reason_status`, `denial_reason_to_body`, `build_signer_mismatch_body`, `build_contact_support_next_steps`, `verification_agent_instructions`, `is_fixable_denial`, `FIXABLE_DENIAL_REASONS`. Also re-exports the per-product policy helpers: `PolicyBlock`, `GateResult`, `EnforcementMode`, `IdentityStatus`, `build_gate_from_policy`, `run_gate_with_enforcement`, `shipping_country_allowed`, `shipping_state_allowed` — for multi-product merchants where each product carries its own compliance config (hard gate vs soft vs none, per-product shipping allowlists). |
| `agentscore_commerce.payment` | `networks`, `USDC`, `rails` registries; `payment_directive`, `build_payment_directive`, `www_authenticate_header`, `payment_required_header`, `alias_amount_fields` (v1↔v2 amount field shim — emits both `amount` and `maxAmountRequired` so v1-only x402 parsers like Coinbase awal can read v2 bodies), `settlement_override_header`, `dispatch_settlement_by_network`, `extract_payment_signer` (returns `PaymentSigner({address, network})`), `register_x402_schemes_v1_v2`; drop-in x402 helpers: `validate_x402_network_config` (boot-time guard), `verify_x402_request` (parse + validate inbound X-Payment), `process_x402_settle` (verify-then-settle with one call). |
| `agentscore_commerce.discovery` | `is_discovery_probe_request`, `build_discovery_probe_response` (with optional `x402_sample` for x402-aware crawlers — `awal x402 details` etc.), `sample_x402_accept_for_network` (USDC sample-accept builder for known CAIP-2 networks), `build_well_known_mpp`, `build_llms_txt` + `llms_txt_identity_section` + `llms_txt_payment_section` (compact + verbose modes), `agentscore_openapi_snippets`, `build_bazaar_discovery_payload`, `NoindexNonDiscoveryMiddleware` (ASGI middleware that emits `X-Robots-Tag: noindex` on every path except the agent-discovery surfaces — defaults cover `/openapi.json`, `/llms.txt`, `/.well-known/{mpp.json,agent-card.json,ucp}`, `/favicon.{png,ico}`; pure helpers `is_discovery_path` + `DEFAULT_DISCOVERY_PATHS` for non-ASGI frameworks). |
| `agentscore_commerce.discovery` | `is_discovery_probe_request`, `build_discovery_probe_response` (with optional `x402_sample` for x402-aware crawlers — `awal x402 details` etc.), `sample_x402_accept_for_network` (USDC sample-accept builder for known CAIP-2 networks), `build_well_known_mpp`, `build_llms_txt` + `llms_txt_identity_section` + `llms_txt_payment_section` (compact + verbose modes), `build_skill_md` (Claude-Skill-compatible `/skill.md` agent-discovery manifest — strictly agent-facing data only, no internal posture), `agentscore_openapi_snippets`, `build_bazaar_discovery_payload`, `NoindexNonDiscoveryMiddleware` (ASGI middleware that emits `X-Robots-Tag: noindex` on every path except the agent-discovery surfaces — defaults cover `/openapi.json`, `/llms.txt`, `/skill.md`, `/.well-known/{mpp.json,agent-card.json,ucp}`, `/favicon.{png,ico}`; pure helpers `is_discovery_path` + `DEFAULT_DISCOVERY_PATHS` for non-ASGI frameworks). |
| `agentscore_commerce.challenge` | `build_402_body`, `build_accepted_methods`, `build_identity_metadata`, `build_how_to_pay`, `build_agent_instructions` (auto-emits per-rail `compatible_clients` — smoke-verified CLIs the agent should use; vendor override supported), `build_pricing_block` (cents → dollar-string with optional shipping/tax), `first_encounter_agent_memory` (cross-merchant hint, returns the canonical block or `None` based on a per-merchant first-seen flag), `OrderReceipt` (dataclass for the post-settlement 200 response shape); `respond_402` — drop-in 402 emit that preserves pympp's `WWW-Authenticate` and layers x402's `PAYMENT-REQUIRED`. `build_validation_error` — structured 4xx body builder (`{error: {code, message}, required_fields?, example_body?, next_steps?, ...extra}`) so vendors compose body shapes by name instead of inlining at every validation site. |
| `agentscore_commerce.stripe_multichain` | `create_multichain_payment_intent`, `get_deposit_address`, `simulate_crypto_deposit`; `create_pi_cache` (TTL'd PI / deposit-address cache, Redis-backed when `redis_url` set, in-memory otherwise), `simulate_deposit_if_test_mode` (gates on `sk_test_` and looks up the PI for you), `STRIPE_TEST_TX_HASH_SUCCESS` / `STRIPE_TEST_TX_HASH_FAILED` constants. Peer dep on `stripe`. |
| `agentscore_commerce.api` | Everything from `agentscore-py` re-exported in one place: `AgentScore` + `AgentScoreError`, `AGENTSCORE_TEST_ADDRESSES` + `is_agentscore_test_address`. **Don't add `agentscore-py` as a separate dep** — the two can drift versions and cause subtle type mismatches. |
Expand Down
49 changes: 35 additions & 14 deletions agentscore_commerce/challenge/agent_instructions.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
"""agent_instructions block builder for the 402 body."""

from collections.abc import Iterable
from dataclasses import dataclass, field
from typing import Any
from typing import Any, Literal

_TEMPO_WARNING = (
"Do NOT use `tempo wallet transfer` to pay to the address above. That moves USDC on-chain but does not "
Expand Down Expand Up @@ -46,28 +47,48 @@ def _default_warnings(how_to_pay: dict[str, Any]) -> list[str]:
return w


RailKey = Literal["tempo_mpp", "x402_base", "x402_solana", "stripe"]

_RAIL_CLIENTS: dict[str, list[str]] = {
"tempo_mpp": ["agentscore-pay", "tempo request", "x402-proxy"],
"x402_base": ["agentscore-pay", "x402-proxy", "purl (omit --network flag)"],
"x402_solana": ["agentscore-pay"],
"stripe": ["link-cli"],
}


def compatible_clients_by_rails(rails: Iterable[str]) -> dict[str, list[str]] | None:
"""Smoke-verified client list for a set of rail keys.

The single source of truth for "which CLIs we've verified end-to-end on each rail" —
consumed both by the 402-body builder (``build_agent_instructions``) and by discovery
surfaces (skill.md, llms.txt, etc.). Update here, every surface inherits.
"""
out: dict[str, list[str]] = {}
for r in rails:
clients = _RAIL_CLIENTS.get(r)
if clients is not None:
out[r] = list(clients)
return out or None


def _default_compatible_clients(how_to_pay: dict[str, Any]) -> dict[str, list[str]] | None:
"""Default ``compatible_clients`` derived from the rails declared in ``how_to_pay``.

Lists clients the AgentScore team has smoke-verified end-to-end against an
``agentscore-commerce`` merchant; entries appear only for rails the vendor actually
offers. Vendors override this in ``BuildAgentInstructionsInput(compatible_clients=...)``
Vendors override this in ``BuildAgentInstructionsInput(compatible_clients=...)``
to add their own tested clients or remove entries that don't fit their endpoint.

Verified state as of the SDK release. The same data is also published as a docs page
for humans (rationale, per-rail commands, why some clients don't fully work, last
verified date) — this default keeps the merchant-side surface in sync.
Verified state as of the SDK release.
"""
out: dict[str, list[str]] = {}
rails: list[str] = []
if "tempo" in how_to_pay:
out["tempo_mpp"] = ["agentscore-pay", "tempo request", "x402-proxy"]
rails.append("tempo_mpp")
if "x402_base" in how_to_pay:
out["x402_base"] = ["agentscore-pay", "x402-proxy", "purl (omit --network flag)"]
rails.append("x402_base")
if "x402_solana" in how_to_pay:
out["x402_solana"] = ["agentscore-pay"]
rails.append("x402_solana")
if "stripe" in how_to_pay:
out["stripe"] = ["link-cli"]
return out or None
rails.append("stripe")
return compatible_clients_by_rails(rails)


@dataclass
Expand Down
18 changes: 18 additions & 0 deletions agentscore_commerce/discovery/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,16 @@
install_flask_noindex,
is_discovery_path,
)
from agentscore_commerce.discovery.skill_md import (
BuildSkillMdInput,
RailKey,
SkillMdEndpoint,
SkillMdIdentityRequirements,
SkillMdLink,
SkillMdShippingPolicy,
build_skill_md,
compatible_clients_by_rails,
)
from agentscore_commerce.discovery.well_known_mpp import (
PaymentMethodConfig,
WellKnownMppInput,
Expand All @@ -45,6 +55,7 @@
"BazaarDiscoveryConfig",
"BuildAgentScoreOpenApiSnippetsInput",
"BuildLlmsTxtInput",
"BuildSkillMdInput",
"DiscoveryProbeOptions",
"DiscoveryProbeResponse",
"DjangoNoindexMiddleware",
Expand All @@ -53,6 +64,11 @@
"LlmsTxtSection",
"NoindexNonDiscoveryMiddleware",
"PaymentMethodConfig",
"RailKey",
"SkillMdEndpoint",
"SkillMdIdentityRequirements",
"SkillMdLink",
"SkillMdShippingPolicy",
"WellKnownMppInput",
"X402SampleProbe",
"agentscore_denial_schemas",
Expand All @@ -62,7 +78,9 @@
"build_bazaar_discovery_payload",
"build_discovery_probe_response",
"build_llms_txt",
"build_skill_md",
"build_well_known_mpp",
"compatible_clients_by_rails",
"install_flask_noindex",
"is_discovery_path",
"is_discovery_probe_request",
Expand Down
2 changes: 2 additions & 0 deletions agentscore_commerce/discovery/robots_tag.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
{
"/openapi.json",
"/llms.txt",
"/skill.md",
"/SKILL.md",
"/.well-known/mpp.json",
"/.well-known/agent-card.json",
"/.well-known/ucp",
Expand Down
Loading
Loading