Metadata-Version: 2.5
Name: neura-agent
Version: 0.2.1
Summary: Agent client for NEURA Compute: OpenAI-compatible inference where the agent pays for itself. HTTP 402 -> wallet sign-in -> USDC on Base -> credited -> retry, as a drop-in http_client for the OpenAI Python SDK.
Project-URL: Homepage, https://app.neuracomputes.com
Project-URL: Docs, https://app.neuracomputes.com/docs
License: MIT
Keywords: 402,agent,base,crewai,inference,langchain,neura,openai,siwe,usdc,x402
Requires-Python: >=3.9
Requires-Dist: eth-account>=0.10
Requires-Dist: httpx>=0.25
Requires-Dist: web3>=6.0
Provides-Extra: examples
Requires-Dist: crewai>=0.30; extra == 'examples'
Requires-Dist: langchain-openai>=0.1; extra == 'examples'
Requires-Dist: openai>=1.0; extra == 'examples'
Provides-Extra: test
Requires-Dist: pytest>=7; extra == 'test'
Description-Content-Type: text/markdown

# neura-agent — the agent client for NEURA Compute (Python)

OpenAI-compatible inference where the agent pays for itself.

1. **Automatic HTTP 402 handling** — when NEURA says *payment required* it
   answers with an x402 envelope. The client settles it from the agent's own
   wallet on Base (USDC, 1 credit = $1), submits the tx hash, waits for the
   credit, and retries. No human, no card.
2. **Wallet-only onboarding (SIWE / EIP-4361)** — an agent holding only a
   wallet key creates its account and self-provisions an API key. If the very
   first request is anonymous, the client does this itself before paying.

```bash
pip install neura-agent
```

## With the official OpenAI SDK

```python
import os
from openai import OpenAI
from neura_agent import NeuraAgent

neura = NeuraAgent(
    wallet_key=os.environ["AGENT_WALLET_KEY"],  # a hot wallet holding a little USDC on Base
    auto_fund=True,                             # 402 -> (login) -> pay USDC -> credited -> retry
    max_usdc_per_fund=5,                        # hard cap per payment
)

client = OpenAI(
    base_url=neura.base_url + "/v1",
    api_key="wallet",                 # placeholder — the client injects the key it provisions
    http_client=neura.http_client(),  # 402s handled transparently
)

r = client.chat.completions.create(
    model="openai/gpt-oss-120b",
    messages=[{"role": "user", "content": "Hello!"}],
)
```

Already have an API key? Pass `api_key=` and the wallet is only used to pay.

## With LangChain

```python
from langchain_openai import ChatOpenAI
from neura_agent import NeuraAgent

neura = NeuraAgent(wallet_key=os.environ["AGENT_WALLET_KEY"], auto_fund=True)
llm = ChatOpenAI(
    model="openai/gpt-oss-120b",
    base_url=neura.base_url + "/v1",
    api_key="wallet",
    http_client=neura.http_client(),
)
print(llm.invoke("Who pays for this call?").content)
```

## With CrewAI

CrewAI routes through LiteLLM, which does not take a custom http client, so
pre-fund before the crew runs and give it the provisioned key:

```python
from crewai import LLM
from neura_agent import NeuraAgent

neura = NeuraAgent(wallet_key=os.environ["AGENT_WALLET_KEY"], auto_fund=True)
neura.login_with_wallet()
if neura.credit_balance() < 2:
    neura.fund(neura.discovery(), credits=2)   # 2 USDC -> 2 credits

llm = LLM(model="openai/openai/gpt-oss-120b", base_url=neura.base_url + "/v1", api_key=neura.api_key)
```

See `examples/` for runnable versions of all three.

## What the platform sends, and what the client does with it

A 402 carries an x402 v1 envelope. Production today:

```json
{
  "code": "PAYMENT_REQUIRED",
  "x402Version": 1,
  "accepts": [{
    "scheme": "exact", "network": "base",
    "maxAmountRequired": "1000000",
    "payTo": "0x404f4E91Fc8Bf0c5fcfC71b4C06E2c0faACdb2c5",
    "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
    "extra": { "settlement": "transfer", "assetSymbol": "USDC", "decimals": 6,
               "creditEndpoint": "/api/depin/credits/topup-usdc" }
  }],
  "authenticate": { "scheme": "EIP-4361", "nonce": "/api/auth/wallet/nonce", "verify": "/api/auth/wallet" }
}
```

`choose_offer(body)` picks the rail: the USDC `exact` offer whenever present;
a `neura-burn` offer only when advertised with a live rate. If the platform
offers nothing the client can settle, it raises instead of guessing.

## Safety model

- Nothing moves on-chain unless you pass `wallet_key` **and** `auto_fund=True`
  (or call `fund()` yourself).
- A returning wallet pays nothing to resume: after the SIWE login the client
  retries with the tenant's key first, and funds only if the platform still
  answers 402 for that tenant. Restarting an agent never buys credits it has.
- `max_usdc_per_fund` (default 5) caps any single payment; `max_neura_per_burn`
  (default 100) caps any burn.
- `pin_pay_to` refuses to send USDC to any receiver other than the one you pin.
- On 402 without auto-funding, `PaymentRequiredError` carries `remediation`,
  `x402` and the full `body` so your own logic can decide.
- Use a dedicated hot wallet with a small balance — never a treasury key.

## Allowances: the owner's leash, honoured by the client

The platform's owner-set allowances answer with `403`/`429` and a code. The
client reads them before it ever thinks about paying:

- `AGENT_PAUSED` (the owner's kill switch) raises `AgentPausedError`. The
  client never funds, never retries: only the owner can resume the key.
- `ALLOWANCE_DAILY`, `ALLOWANCE_MONTHLY`, `OUTSIDE_ALLOWED_HOURS`,
  `SELF_LIMIT_EXCEEDED` are retryable and come with `Retry-After`. By default
  the client raises `AllowanceError` with `retry_after_seconds` so your loop
  can decide. Set `max_retry_after_seconds` and, when the wait fits, the
  client sleeps exactly that long and retries **once**.
- `MODEL_NOT_ALLOWED`, `ALLOWANCE_PER_CALL` are not retryable and raise
  `AllowanceError` with `retryable=False` even when waiting is allowed.
- Any other 403/429 passes through untouched.

The agent's own key can only tighten its allowance (`PUT /api/agent/allowance`).
A **wallet tenant** has no console, so its owner acts by signature:
`agent.owner_allowance(api_key_id, allowance={...})`, `action="kill"` or
`action="resume"` signs a fresh SIWE message with `wallet_key` and posts it to
`/api/agent/allowance/owner`. Run that where the wallet lives, not inside the
agent: whoever can sign can loosen the leash.

## API

| Member | Purpose |
|---|---|
| `NeuraAgent(...)` | `api_key`, `base_url`, `wallet_key`, `auto_fund`, `credits_per_fund`, `max_usdc_per_fund`, `max_neura_per_burn`, `confirmations`, `rpc_url`, `pin_pay_to`, `max_retry_after_seconds` |
| `agent.owner_allowance(api_key_id, allowance=None, action=None, reason=None)` | Owner-by-signature: set any allowance field, or `action="kill"` / `"resume"` |
| `AgentPausedError` / `AllowanceError` | Raised on `AGENT_PAUSED` / on an allowance refusal (`code`, `retryable`, `retry_after_seconds`, `body`) |
| `agent.http_client()` | An `httpx.Client` whose `send` settles 402s — pass to OpenAI / LangChain |
| `agent.chat(model, messages, **params)` | Plain chat completion with the same 402 handling |
| `agent.login_with_wallet()` | SIWE login → `{tenantId, address, apiKey}` |
| `agent.fund(body, credits=None)` | Settle a 402 body (or a single offer) on the rail it advertises |
| `agent.fund_usdc(offer, credits)` / `agent.fund_burn(remediation)` | The rails individually |
| `agent.credit_balance()` | Current credits (1 = $1) |
| `agent.discovery()` | `/.well-known/neura.json` |
| `choose_offer(body)` | Which rail a 402 lets the client settle |
| `PaymentRequiredError` | Raised on unhandled 402 — carries `remediation`, `x402`, `body` |

## Develop / publish

```bash
cd packages/neura-agent-py
pip install -e ".[test]"
pytest
python -m build && twine upload dist/*
```
