@aeon-ai-pay/aigateway 0.2.2 → 0.2.4

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.
@@ -1,6 +1,6 @@
1
1
  # Output Schema
2
2
 
3
- Every `aigateway` command emits **exactly one line of JSON** to **stdout** —— the *envelope*. Human-readable progress logs go to **stderr** and can be safely ignored by programmatic consumers.
3
+ Every `aigateway` command emits **exactly one line of JSON** to **stdout** the *envelope*. Human-readable progress logs go to **stderr** and can be safely ignored by programmatic consumers.
4
4
 
5
5
  > Pass `--quiet` to suppress non-error stderr. Pass `--legacy-output` to fall back to the pre-envelope shape (see [Legacy mode](#legacy-mode)).
6
6
 
@@ -11,8 +11,8 @@ Every `aigateway` command emits **exactly one line of JSON** to **stdout** —
11
11
  ```json
12
12
  {
13
13
  "ok": true,
14
- "command": "create",
15
- "version": "0.9.0",
14
+ "command": "sb-invoke",
15
+ "version": "x.y.z",
16
16
  "data": { /* command-specific payload */ }
17
17
  }
18
18
  ```
@@ -22,20 +22,21 @@ Every `aigateway` command emits **exactly one line of JSON** to **stdout** —
22
22
  ```json
23
23
  {
24
24
  "ok": false,
25
- "command": "create",
26
- "version": "0.9.0",
25
+ "command": "sb-invoke",
26
+ "version": "x.y.z",
27
27
  "error": {
28
- "code": "AMOUNT_OUT_OF_RANGE",
29
- "message": "Amount must be at least $0.6. Allowed range: $0.6 ~ $800 USD.",
30
- "min": 0.6,
31
- "max": 800
28
+ "code": "MISSING_INPUTS",
29
+ "message": "Inputs validation failed for ...",
30
+ "errors": [{ "field": "prompt", "kind": "missing", "message": "..." }],
31
+ "required": ["prompt"],
32
+ "properties": ["prompt", "aspect_ratio", "num_outputs"]
32
33
  }
33
34
  }
34
35
  ```
35
36
 
36
37
  - `error.code` is a stable identifier from [`src/error-codes.mjs`](../src/error-codes.mjs) — see [exit-codes.md](./exit-codes.md) for the full list.
37
38
  - `error.message` is human-readable and may change between versions; **do not** match on it for control flow.
38
- - Additional fields under `error` are command-specific context (e.g. `min` / `max` / `address` / `required` / `available`).
39
+ - Additional fields under `error` are command-specific context (e.g. `min` / `max` / `address` / `required` / `available` / `errors[]` / `presets`).
39
40
 
40
41
  ## Per-Command `data` Payloads
41
42
 
@@ -49,8 +50,14 @@ Every `aigateway` command emits **exactly one line of JSON** to **stdout** —
49
50
  "mode": "private-key",
50
51
  "address": "0x...",
51
52
  "mainWallet": "0x..." | null,
52
- "serviceUrl": "https://...",
53
- "amountLimits": { "min": 0.6, "max": 800 }
53
+ "usdt": "5.0",
54
+ "bnb": "0.0003",
55
+ "allowance": "115792...max" | "0",
56
+ "needsTopup": false,
57
+ "topupReason": null | "first_time" | "low_balance" | "no_approve" | "chain_check_failed",
58
+ "minTopup": 5,
59
+ "presets": [5, 10, 20, 50],
60
+ "serviceUrl": "https://..."
54
61
  }
55
62
  ```
56
63
 
@@ -70,72 +77,80 @@ Every `aigateway` command emits **exactly one line of JSON** to **stdout** —
70
77
  }
71
78
  ```
72
79
 
73
- ### `create-card`
80
+ ### `sb invoke`
74
81
 
75
82
  ```json
76
83
  {
77
- "appId": "TEST000001",
78
- "orderNo": "...",
79
- "data": { /* sanitized server response */ },
80
- "paymentResponse": { /* decoded PAYMENT-RESPONSE header */ },
81
- "pollResult": { /* present when --poll succeeds */ }
84
+ "model": "replicate/black-forest-labs/flux-schnell",
85
+ "inputs": { "prompt": "...", "aspect_ratio": "1:1" },
86
+ "transaction": "0x..." | null,
87
+ "downloaded": [
88
+ {
89
+ "url": "https://...",
90
+ "localPath": "/home/.../aigateway-images/...png",
91
+ "format": "png",
92
+ "width": 1024,
93
+ "height": 1024,
94
+ "sizeBytes": 412345,
95
+ "sizeHuman": "402.7 KB"
96
+ }
97
+ ],
98
+ "raw": { /* upstream vendor response, unwrapped from { payer, transaction, data } */ },
99
+ "paymentResponse": { "txHash": "0x...", "payer": "0x...", "...": "..." },
100
+ "balance": {
101
+ "initial": "5.0",
102
+ "before": "5.0",
103
+ "after": "4.99",
104
+ "charged": 0.01,
105
+ "topup": null | "5"
106
+ }
82
107
  }
83
108
  ```
84
109
 
85
- ### `create-image`
110
+ - **Binary outputs** (image / video / audio) populate `downloaded[]`. With `--raw` the auto-download is skipped and `downloaded[]` stays empty; the URLs live in `raw`.
111
+ - **JSON-only outputs** (search, scraper, social_data, email, etc.) leave `downloaded` empty; consumers read `raw`.
112
+ - `balance.charged` is the live USDT amount taken for this call (computed server-side as `priceUnit × inputs usage`).
113
+
114
+ Failure shapes carry extra fields per code, e.g.:
86
115
 
87
116
  ```json
88
117
  {
89
- "appId": "TEST000001",
90
- "prompt": "...",
91
- "aspectRatio": "16:9",
92
- "outputFormat": "png",
93
- "model": "...",
94
- "transaction": "0x...",
95
- "images": [
96
- { "url": "https://...", "localPath": "/.../...png", "format": "png", "width": 1024, "height": 576, "sizeBytes": 123456, "sizeHuman": "120.6 KB" }
97
- ],
98
- "balance": { "initial": "5", "before": "5", "after": "4.99", "charged": 0.01, "topup": null }
118
+ "ok": false,
119
+ "command": "sb-invoke",
120
+ "error": {
121
+ "code": "TOPUP_REQUIRED",
122
+ "message": "USDT balance is below the 5 USDT minimum...",
123
+ "minTopup": 5,
124
+ "required": 0.01,
125
+ "currentBalance": "0",
126
+ "address": "0x...",
127
+ "presets": [5, 10, 20, 50],
128
+ "hint": "Rerun: aigateway wallet-topup --amount <usdt> --app-id ..."
129
+ }
99
130
  }
100
131
  ```
101
132
 
102
- **Dry-run (`--dry-run`)** — preflight checks complete but no signing/transaction occurs:
133
+ ### `sb tools`
103
134
 
104
- ```json
105
- {
106
- "dryRun": true,
107
- "url": "...",
108
- "paymentRequirements": { "amountUsdt": 0.66, "amountWei": "660000000000000000", "asset": "0x...", "payTo": "0x...", "orderNo": "..." },
109
- "wallet": { "address": "0x..." },
110
- "decision": { "needTopup": true, "needGas": false, "topupAmount": "0.660000" },
111
- "will": ["fund_usdt_via_walletconnect", "approve_or_skip", "sign_payment_eip712", "submit_to_facilitator", "poll_status"]
112
- }
113
- ```
135
+ `data` shape depends on the filters supplied:
114
136
 
115
- ### `create-card-status`
137
+ | Invocation | `data.mode` | Shape |
138
+ | --- | --- | --- |
139
+ | `sb tools` | (absent) | Full catalog: `{ categories: [{ key, agentTrigger, defaultInputsSchema, models: [...] }], version, ... }` |
140
+ | `sb tools --model <id>` | `"single-model"` | `{ category: "<key>", model: { id, vendor, useCase, price, priceUnit, tier, inputsOverride? }, effectiveSchema: { ... } }` |
141
+ | `sb tools --category <key>` | `"single-category"` | `{ category: { key, agentTrigger, defaultInputsSchema, models: [...] } }` |
142
+ | `sb tools --tier <t>` (alone) | `"tier-filtered"` | Full catalog with each category's `models[]` filtered to `tier === t`, plus `tier: "<t>"` |
116
143
 
117
- ```json
118
- {
119
- "success": true,
120
- "model": {
121
- "orderNo": "...",
122
- "orderStatus": "SUCCESS" | "FAIL" | "PROCESSING" | "...",
123
- "channelStatus": "...",
124
- "cardStatus": "ACTIVE" | "PENDING" | "...",
125
- "cardScheme": "VISA" | "MASTERCARD",
126
- "cardNumber": "•••• 1234",
127
- "...": "(sensitive fields like CVV / expiry are stripped)"
128
- }
129
- }
130
- ```
144
+ `effectiveSchema` = `model.inputsOverride ?? category.defaultInputsSchema` — the JSON-schema-shaped object that `sb invoke` validates against client-side.
131
145
 
132
- ### `wallet`
146
+ ### `wallet-balance`
133
147
 
134
148
  ```json
135
149
  {
136
150
  "mode": "private-key",
137
151
  "address": "0x...",
138
152
  "usdt": "12.34",
153
+ "bnb": "0.0003",
139
154
  "network": "BSC Mainnet (Chain ID: 56)",
140
155
  "mainWallet": { "address": "0x...", "usdt": "..." }
141
156
  }
@@ -182,7 +197,7 @@ Every `aigateway` command emits **exactly one line of JSON** to **stdout** —
182
197
  For consumers still parsing the pre-envelope JSON shape, pass `--legacy-output` to get the old format on stdout (and errors on **stderr** as before):
183
198
 
184
199
  ```bash
185
- aigateway --legacy-output create-card --amount 5
200
+ aigateway --legacy-output wallet-balance
186
201
  ```
187
202
 
188
- Legacy mode is kept for one or two minor releases as a migration aid. New integrations should use the envelope.
203
+ Legacy mode is kept as a migration aid. New integrations should use the envelope.
@@ -1,31 +1,38 @@
1
- # Recipe — Schedule Recurring Card Issuance
1
+ # Recipe — Schedule Recurring Paid Invocations
2
2
 
3
- Use this when an agent needs to issue cards on a schedule for example, "create a $5 card every Monday at 09:00 to seed an autonomous shopping flow."
3
+ > Filename kept for backwards compatibility. The earlier "issue card" workflow has been folded into the unified `aigateway sb invoke` entry point; this recipe now covers any scheduled paid call.
4
+
5
+ Use this when an agent or automation needs to invoke a paid AI tool on a schedule — for example, "generate a marketing image every Monday at 09:00", "transcribe yesterday's recordings nightly", "run a search summarisation every hour".
4
6
 
5
7
  ## Prerequisites
6
8
 
7
9
  1. **Pre-funded session wallet.** WalletConnect requires a browser; cron jobs run headless. Top up the local wallet manually first:
8
10
  ```bash
9
- aigateway wallet-topup --amount 50 # adds USDT + a tiny amount of BNB for approve gas
11
+ aigateway wallet-topup --amount 50 # adds USDT + a tiny amount of BNB for the one-time approve
10
12
  ```
11
- 2. **One-time `approve`.** Run `aigateway create-card --amount 0.6 --poll` once interactively so the approve transaction is on-chain. Subsequent creates won't need WalletConnect again until USDT is depleted.
13
+ 2. **Approve already broadcast.** `wallet-topup` performs the one-time `ERC20.approve(facilitator, MaxUint256)`. After it succeeds, all subsequent `sb invoke` calls are gasless EIP-712 signing no WalletConnect needed until USDT is depleted.
14
+ 3. **Catalog sanity-check.** Before scheduling, run `aigateway sb tools --model <id>` once to confirm the model id is valid and to learn its `effectiveSchema` (so your wrapper builds the correct `--inputs`).
12
15
 
13
16
  ## A Minimal Wrapper Script
14
17
 
15
- `~/bin/issue-card.sh`:
18
+ `~/bin/invoke-tool.sh`:
16
19
 
17
20
  ```bash
18
21
  #!/usr/bin/env bash
19
22
  set -euo pipefail
20
23
 
21
- AMOUNT="${1:-5}"
24
+ MODEL="${1:?model id required}"
25
+ INPUTS_FILE="${2:?path to inputs JSON required}" # e.g. ~/aigateway/jobs/daily-image.json
22
26
  LOG_DIR="${HOME}/.aigateway/logs"
23
27
  mkdir -p "$LOG_DIR"
24
28
  TS=$(date -u +"%Y-%m-%dT%H-%M-%SZ")
25
- LOG="$LOG_DIR/issue-${TS}.log"
29
+ LOG="$LOG_DIR/invoke-${TS}.log"
26
30
 
27
31
  # Capture envelope on stdout; quiet stderr noise
28
- ENVELOPE=$(aigateway --quiet create-card --amount "$AMOUNT" --poll 2>"$LOG")
32
+ ENVELOPE=$(aigateway --quiet sb invoke \
33
+ --model "$MODEL" \
34
+ --inputs "@${INPUTS_FILE}" \
35
+ 2>"$LOG")
29
36
  EXIT=$?
30
37
 
31
38
  echo "$ENVELOPE" > "${LOG%.log}.json"
@@ -37,19 +44,26 @@ if [ "$EXIT" -ne 0 ]; then
37
44
  exit "$EXIT"
38
45
  fi
39
46
 
40
- ORDER=$(echo "$ENVELOPE" | jq -r '.data.orderNo')
41
- echo "[$(date -u)] OK orderNo=$ORDER amount=$AMOUNT"
47
+ TX=$(echo "$ENVELOPE" | jq -r '.data.transaction // "—"')
48
+ FIRST=$(echo "$ENVELOPE" | jq -r '.data.downloaded[0].localPath // empty')
49
+ echo "[$(date -u)] OK tx=$TX file=${FIRST:-n/a} model=$MODEL"
42
50
  ```
43
51
 
44
52
  ```bash
45
- chmod +x ~/bin/issue-card.sh
53
+ chmod +x ~/bin/invoke-tool.sh
46
54
  ```
47
55
 
48
56
  ## Cron Entry
49
57
 
50
58
  ```cron
51
59
  # minute hour dom mon dow command
52
- 0 9 * * 1 /Users/me/bin/issue-card.sh 5 >> /Users/me/.aigateway/logs/cron.log 2>&1
60
+ 0 9 * * 1 /Users/me/bin/invoke-tool.sh replicate/black-forest-labs/flux-schnell /Users/me/aigateway/jobs/weekly-image.json >> /Users/me/.aigateway/logs/cron.log 2>&1
61
+ ```
62
+
63
+ Example `weekly-image.json`:
64
+
65
+ ```json
66
+ { "prompt": "neon city skyline at dusk, hyper-detailed", "aspect_ratio": "16:9" }
53
67
  ```
54
68
 
55
69
  > ⚠️ On macOS, `cron` may lack PATH access to `aigateway`. Use an absolute path (`/Users/me/.nvm/versions/node/v25/bin/aigateway`) or source your shell rc inside the wrapper.
@@ -58,12 +72,16 @@ chmod +x ~/bin/issue-card.sh
58
72
 
59
73
  | Code | Likely cause in cron context | Fix |
60
74
  | ---- | ---------------------------- | --- |
61
- | `INSUFFICIENT_USDT` | Wallet ran dry. | Top up via `aigateway wallet-init` on a workstation. |
62
- | `INSUFFICIENT_BNB` | Approve allowance expired or BNB depleted by retries. | Run `aigateway wallet-gas` to add a small amount. |
63
- | `PAYMENT_TIMEOUT` | WalletConnect was triggered (unexpected should be fully approved). | Run an interactive `aigateway wallet-init` once to refresh allowance. |
64
- | `SERVICE_UNAVAILABLE` | Upstream outage. | Cron will retry next tick. Alert if 3+ consecutive failures. |
75
+ | `INSUFFICIENT_USDT` / `TOPUP_REQUIRED` | Wallet ran dry. Headless cron cannot scan a QR. | Top up via `aigateway wallet-topup --amount <n>` on a workstation. |
76
+ | `INSUFFICIENT_BNB` | BNB depleted (only matters for `wallet-withdraw` / re-approve). | Run `aigateway wallet-gas` interactively on a workstation. |
77
+ | `INVALID_MODEL_ID` | The model id was renamed or removed upstream. | Run `aigateway sb tools` and update the wrapper's `--model`. |
78
+ | `MISSING_INPUTS` / `INVALID_INPUTS` | Inputs file drifted from the schema (e.g. missing `duration_seconds` for video / `duration_minutes` for STT). | Re-pull `sb tools --model <id>` and fix the JSON. |
79
+ | `MODEL_PRICING_NOT_CONFIGURED` | Upstream removed the model from the pricing config. | Pick another model. |
80
+ | `SERVICE_UNAVAILABLE` / `PAYMENT_FETCH_FAILED` | Upstream outage. | Cron will retry next tick. Alert if 3+ consecutive failures. |
81
+ | `PAYMENT_TIMEOUT` | WalletConnect was unexpectedly triggered (should not happen once approve is on-chain). | Run an interactive `aigateway wallet-topup` once to re-fund / re-approve. |
65
82
 
66
83
  ## See Also
67
84
 
68
85
  - [integrate-in-agent.md](./integrate-in-agent.md) — Node.js / Python subprocess wrapper.
69
86
  - [error-recovery.md](./error-recovery.md) — Full code-by-code recovery table.
87
+ - [../output-schema.md](../output-schema.md) — `sb invoke` envelope fields used above (`transaction`, `downloaded[]`, `balance`).
@@ -6,25 +6,40 @@ Map each `error.code` returned by the envelope to a concrete recovery action. Us
6
6
  | ------------ | :--: | -------------------- |
7
7
  | `WALLET_NOT_CONFIGURED` | 1 | Run `aigateway wallet-init` once (auto-creates a local session wallet). |
8
8
  | `SERVICE_URL_MISSING` | 1 | Override via env `AIGATEWAY_SERVICE_URL`. The default service URL is wired into the CLI for production; this should never trigger in normal use. |
9
- | `AMOUNT_INVALID` | 1 | Caller bug — input must be a numeric string. |
10
- | `AMOUNT_OUT_OF_RANGE` | 1 | Re-prompt user with `error.min` ~ `error.max`. Do **not** silently clamp. |
11
- | `AMOUNT_EXCEEDS_BALANCE` | 1 | Use the smaller of requested vs. `error.available`. |
12
- | `INSUFFICIENT_USDT` | 1 | Top-up failed or partial. Surface `error.required` / `error.available` to user, ask whether to retry with a new amount. |
13
- | `INSUFFICIENT_BNB` | 1 | Run `aigateway wallet-gas` to top up a small amount of BNB via WalletConnect, then retry. |
14
- | `NO_FUNDS` | 1 | Nothing to withdraw. Inform the user, possibly suggest `wallet-topup`. |
15
- | `NO_MAIN_WALLET` | 1 | Caller must pass `--to <address>`. |
9
+ | `AMOUNT_INVALID` | 1 | Caller bug — input must be a positive numeric string (used by `wallet-topup`, `wallet-gas`, `wallet-withdraw`, `--topup-amount`). |
10
+ | `AMOUNT_EXCEEDS_BALANCE` | 1 | `wallet-withdraw --amount` exceeded available USDT. Use the smaller of requested vs. `error.available`. |
11
+ | `INSUFFICIENT_USDT` | 1 | Top-up failed or the chosen `--topup-amount` was still below the call's required USDT. Surface `error.required` / `error.available`; ask the user to retry with a larger top-up. |
12
+ | `INSUFFICIENT_BNB` | 1 | Approve / withdraw needs BNB for gas. Run `aigateway wallet-gas` (WalletConnect, must be interactive), then retry. |
13
+ | `NO_FUNDS` | 1 | Nothing to withdraw. Inform the user; suggest `wallet-topup` if relevant. |
14
+ | `NO_MAIN_WALLET` | 1 | `wallet-withdraw` invoked with no `mainWallet` saved. Re-run with `--to <address>`. |
15
+ | `MISSING_MODEL` | 1 | `sb invoke --model` is required. Run `aigateway sb tools` to pick one. |
16
+ | `MISSING_INPUTS` | 1 | `sb invoke --inputs` is required, or the JSON is missing required fields. `error.errors[]` lists which fields are missing; `error.required[]` lists all required keys for the model. |
17
+ | `INVALID_INPUTS` | 1 | Inputs failed schema validation. `error.errors[]` items carry `{ field, kind, message }` where `kind ∈ {enum, type, range}`. Fix and retry. |
18
+ | `INVALID_INPUTS_JSON` | 1 | `--inputs` could not be parsed as JSON. Check quoting / escaping; on shells use `JSON.stringify(...)` from your wrapper. |
19
+ | `INPUTS_FILE_NOT_FOUND` | 1 | `--inputs @path` file does not exist. Resolve to an absolute path or correct the caller. |
20
+ | `INVALID_MODEL_ID` | 1 | Server / catalog rejected the model id. Run `aigateway sb tools --category <key>` to find a valid one. |
21
+ | `CATEGORY_NOT_FOUND` | 1 | `sb tools --category` argument is not in the live catalog. Run `aigateway sb tools` (no filter) to list categories. |
22
+ | `MODEL_PRICING_NOT_CONFIGURED` | 1 | The catalog lists the model but the gateway has no price entry yet. Pick another model or ask the operator to add it. |
23
+ | `INVALID_BODY` | 1 | Server rejected the request body shape. Usually a CLI / catalog drift; file a bug. |
24
+ | `TOPUP_REQUIRED` | 1 | Non-TTY context, USDT below the per-call minimum. Choose from `error.presets` (filtered to ≥ `error.minTopup`) and rerun the failing command with `--topup-amount <n>`. |
25
+ | `TOPUP_AMOUNT_TOO_SMALL` | 1 | `--topup-amount` (or `topup --amount`) below `error.minTopup`. Rerun with a larger value. |
16
26
  | `PAYMENT_REJECTED` | 1 | User cancelled in their wallet. **Do not auto-retry** — ask user first. |
17
- | `PAYMENT_TIMEOUT` | 2 | WalletConnect approval expired (5 min). Ask user whether to retry. **Do not auto-retry.** |
18
- | `WC_SESSION_EXPIRED` | 2 | Reconnect required. Re-run the original command. |
19
- | `POLL_TIMEOUT` | 2 | Card may still provision. Surface `error.orderNo` and query later with `aigateway create-card-status --order-no <n>`. |
20
- | `TX_TIMEOUT` | 2 | The on-chain transfer is likely still pending query the chain or retry the status command. |
27
+ | `PAYMENT_TIMEOUT` | 2 | WalletConnect approval window expired (5 min). Ask user whether to retry. **Do not auto-retry.** |
28
+ | `WC_SESSION_EXPIRED` | 2 | WalletConnect relay dropped the session. Re-run the original command. |
29
+ | `TX_TIMEOUT` | 2 | The on-chain transfer is likely still pending. Wait, then re-check with `wallet-balance`. |
30
+ | `UPDATE_APPLIED` | 2 | CLI just upgraded itself synchronously. The previous command was **not executed**. Surface `error.from` `error.to` and **rerun the same command verbatim** on the new version. |
21
31
  | `SERVICE_UNAVAILABLE` | 3 | Exponential backoff: 1 s → 4 s → 16 s, max 3 attempts. |
22
- | `PAYMENT_FETCH_FAILED` | 3 | Same as above. Check network connectivity. |
32
+ | `PAYMENT_FETCH_FAILED` | 3 | First 402 probe failed. Backoff + retry; check network connectivity. |
33
+ | `CATALOG_FETCH_FAILED` | 3 | `sb tools` could not reach the server. Retry once; if it persists, `sb invoke` still runs (server-side validation is the safety net). |
23
34
  | `BALANCE_CHECK_FAILED` | 3 | BSC RPC hiccup. Retry once after 2 s. |
35
+ | `ALLOWANCE_CHECK_FAILED` | 3 | BSC RPC hiccup. Retry once after 2 s. |
24
36
  | `TX_REVERTED` | 3 | On-chain failure. Capture `error.message` for diagnosis; do not retry blindly. |
25
37
  | `WITHDRAW_FAILED` | 3 | Withdraw transaction failed. Check `aigateway wallet-balance` and retry. |
38
+ | `APPROVE_FAILED` | 3 | One-time facilitator approve failed during `wallet-topup`. Inspect the tx; retry the top-up. |
26
39
  | `INVALID_PAYMENT_AMOUNT` | 3 | Server-side issue (returned amount = 0). Retry after a short delay. |
27
- | `PAYMENT_FAILED` | 3 | Service rejected the signed request. Surface `error.data` to the user / log. |
40
+ | `PAYMENT_FAILED` | 3 | Service rejected the signed payment request. Surface `error.data` / `error.status` to the user / log. On 5xx, retry once. |
41
+ | `IMAGE_DOWNLOAD_FAILED` / `DOWNLOAD_FAILED` | 3 | The paid call succeeded but the local download failed. The original URL is still in the upstream response — surface it from `data.downloaded[].url` (when available) or re-fetch via `--raw`. **Do not re-invoke the model** (you'd pay again). |
42
+ | `FUNDING_FAILED` | 3 | Non-timeout / non-reject failure in the WalletConnect funding flow. Re-run `wallet-topup`. |
28
43
  | `INTERNAL_ERROR` | 4 | File a bug. Don't retry. |
29
44
  | `WALLET_ERROR` | 1 | Generic wallet failure. Surface to user, ask whether to retry. |
30
45
 
@@ -41,13 +56,29 @@ async function withRetry(fn, { codes, attempts = 3, baseDelayMs = 1000 } = {}) {
41
56
  }
42
57
 
43
58
  // Only retry transient service/network errors
44
- await withRetry(() => runAEON AI Gateway(["create", "--amount", "5", "--poll"]), {
45
- codes: ["SERVICE_UNAVAILABLE", "PAYMENT_FETCH_FAILED", "BALANCE_CHECK_FAILED", "INVALID_PAYMENT_AMOUNT"],
46
- });
59
+ await withRetry(
60
+ () => runAigateway([
61
+ "sb", "invoke",
62
+ "--model", "replicate/black-forest-labs/flux-schnell",
63
+ "--inputs", JSON.stringify({ prompt: "a cyberpunk fox" }),
64
+ ]),
65
+ {
66
+ codes: [
67
+ "SERVICE_UNAVAILABLE",
68
+ "PAYMENT_FETCH_FAILED",
69
+ "CATALOG_FETCH_FAILED",
70
+ "BALANCE_CHECK_FAILED",
71
+ "ALLOWANCE_CHECK_FAILED",
72
+ "INVALID_PAYMENT_AMOUNT",
73
+ ],
74
+ },
75
+ );
47
76
  ```
48
77
 
49
78
  ## Anti-patterns
50
79
 
51
80
  - ❌ Don't retry `PAYMENT_REJECTED` or `PAYMENT_TIMEOUT` automatically — the user actively cancelled or walked away.
81
+ - ❌ Don't retry `IMAGE_DOWNLOAD_FAILED` / `DOWNLOAD_FAILED` by re-invoking the model — the paid call already settled, you'd be charged again. Re-fetch the URL or re-run with `--raw`.
52
82
  - ❌ Don't match on `error.message` text — messages may change between versions. Match on `error.code`.
53
83
  - ❌ Don't ignore exit code in favour of envelope. Stack-level proxies sometimes mangle stdout; the exit code is a redundant safety net.
84
+ - ❌ Don't paper over `UPDATE_APPLIED` by treating it as a generic timeout — it's a "new binary; rerun me" signal, not a failure.
@@ -6,15 +6,16 @@ This recipe shows how to invoke `aigateway` from inside an agent product (Node.j
6
6
 
7
7
  - `@aeon-ai-pay/aigateway` installed (globally for system-wide use, or as a dependency in your project).
8
8
  - A working session wallet — run `aigateway wallet-init` once on the host.
9
+ - First-time funding done with `aigateway wallet-topup --amount 5` (one-time WalletConnect flow + facilitator `approve`). After this, all `sb invoke` calls are gasless and headless-friendly.
9
10
 
10
- > ⚠️ The CLI uses **WalletConnect for funding**, which opens a browser window with a QR code. If your agent runs headless or in containers without a display, fund the session wallet ahead of time (`aigateway wallet-init`) on a workstation, then ship the `~/.aigateway/config.json` to the runtime host. Agents should never embed user main-wallet private keys.
11
+ > ⚠️ `wallet-topup` opens a browser window with a WalletConnect QR code. If your agent runs headless or in containers without a display, fund the session wallet ahead of time on a workstation, then ship the `~/.aigateway/config.json` (or its `privateKey`) to the runtime host. Agents should never embed user main-wallet private keys.
11
12
 
12
13
  ## Node.js — Spawn & Parse Envelope
13
14
 
14
15
  ```js
15
16
  import { spawn } from "node:child_process";
16
17
 
17
- function runAEON AI Gateway(args) {
18
+ function runAigateway(args) {
18
19
  return new Promise((resolve, reject) => {
19
20
  const child = spawn("aigateway", args, { stdio: ["ignore", "pipe", "pipe"] });
20
21
  let stdout = "";
@@ -33,18 +34,20 @@ function runAEON AI Gateway(args) {
33
34
  });
34
35
  }
35
36
 
36
- // Example: create a $5 card and poll until terminal
37
- const { envelope, exitCode } = await runAEON AI Gateway([
37
+ // Example: invoke an AI tool (image generation) via the x402 paid entry point
38
+ const { envelope, exitCode } = await runAigateway([
38
39
  "--quiet",
39
- "create",
40
- "--amount", "5",
40
+ "sb", "invoke",
41
+ "--model", "replicate/black-forest-labs/flux-schnell",
42
+ "--inputs", JSON.stringify({ prompt: "a cyberpunk fox", aspect_ratio: "1:1" }),
41
43
  "--app-id", "MY_AGENT_001",
42
- "--poll",
43
44
  ]);
44
45
 
45
46
  if (envelope.ok) {
46
- const { orderNo, data } = envelope.data;
47
- console.log("Card ready:", data.model?.cardNumber, "order:", orderNo);
47
+ const { model, transaction, downloaded, balance } = envelope.data;
48
+ console.log(`Done (${model}) tx=${transaction}`);
49
+ for (const d of downloaded) console.log(`Saved: ${d.localPath} (${d.sizeHuman})`);
50
+ console.log(`Charged ${balance.charged} USDT, remaining ${balance.after}`);
48
51
  } else {
49
52
  // See docs/recipes/error-recovery.md for code-by-code guidance
50
53
  console.error(`Failed [${envelope.error.code}] (exit ${exitCode}):`, envelope.error.message);
@@ -74,22 +77,40 @@ def run_aigateway(args):
74
77
  envelope = json.loads(result.stdout.strip().splitlines()[-1])
75
78
  return result.returncode, envelope
76
79
 
77
- exit_code, env = run_aigateway(["create", "--amount", "5", "--poll"])
80
+ exit_code, env = run_aigateway([
81
+ "sb", "invoke",
82
+ "--model", "replicate/black-forest-labs/flux-schnell",
83
+ "--inputs", json.dumps({"prompt": "a cyberpunk fox"}),
84
+ ])
78
85
  if env["ok"]:
79
- print("Card ready:", env["data"]["data"]["model"]["cardNumber"])
86
+ for d in env["data"]["downloaded"]:
87
+ print("Saved:", d["localPath"])
80
88
  else:
81
89
  print(f"Failed [{env['error']['code']}] exit={exit_code}: {env['error']['message']}")
82
90
  ```
83
91
 
84
- ## Probing Without Cost `--dry-run`
92
+ ## Discovering Models — `sb tools`
85
93
 
86
- To validate inputs, balances, and allowance **without** signing or transacting, use `--dry-run` on `create-card`:
94
+ Before calling `sb invoke`, fetch the live catalog with `sb tools` to pick a valid `model` and learn the expected `inputs` schema:
87
95
 
88
96
  ```bash
89
- aigateway --quiet create-card --amount 5 --dry-run | jq '.data.will, .data.decision'
97
+ # Single model + effectiveSchema (most useful for agents)
98
+ aigateway --quiet sb tools --model replicate/black-forest-labs/flux-schnell | jq '.data'
99
+
100
+ # All models in a category
101
+ aigateway --quiet sb tools --category image | jq '.data.categories[0].models[].id'
102
+
103
+ # Cheapest tier across all categories
104
+ aigateway --quiet sb tools --tier price
90
105
  ```
91
106
 
92
- This is ideal for integration tests, configuration smoke checks, and "is everything ready?" probes.
107
+ The catalog is fetched live from the server each call (no local cache). Each model carries `price`, `priceUnit`, `tier`, and an optional `inputsOverride`; each category carries `defaultInputsSchema`.
108
+
109
+ ## Probing Without Cost
110
+
111
+ `sb invoke` performs client-side validation of `--model` and `--inputs` against the live catalog **before** any x402 round-trip. Invalid model ids and missing/invalid fields return `INVALID_MODEL_ID` / `MISSING_INPUTS` / `INVALID_INPUTS` locally, with zero USDT spent.
112
+
113
+ For wallet-only smoke tests, `aigateway wallet-balance` is read-only and never signs anything.
93
114
 
94
115
  ## Exit Code Strategy
95
116
 
@@ -99,7 +120,7 @@ Treat exit codes as a fast filter, then branch on `error.code` for nuance:
99
120
  switch (exitCode) {
100
121
  case 0: /* success */ break;
101
122
  case 1: /* user / config — surface to caller for correction */ break;
102
- case 2: /* timeout — safe to retry; card may still be provisioning */ break;
123
+ case 2: /* timeout — safe to retry the same command */ break;
103
124
  case 3: /* network / service — exponential backoff retry */ break;
104
125
  case 4: /* internal — log + fail loud */ break;
105
126
  }