@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.
- package/README.md +236 -66
- package/bin/cli.mjs +1 -0
- package/docs/env-vars.md +12 -14
- package/docs/exit-codes.md +25 -15
- package/docs/ide-setup.md +2 -2
- package/docs/output-schema.md +73 -58
- package/docs/recipes/cron-issue-cards.md +34 -16
- package/docs/recipes/error-recovery.md +47 -16
- package/docs/recipes/integrate-in-agent.md +37 -16
- package/docs/recipes/merchant-integration.md +61 -32
- package/docs/troubleshooting.md +61 -13
- package/package.json +11 -5
- package/skills/aigateway/SKILL.md +47 -25
- package/src/catalog.mjs +3 -2
- package/src/commands/sb-invoke.mjs +14 -2
- package/src/commands/sb-tools.mjs +4 -2
- package/src/commands/wallet-init.mjs +27 -1
- package/src/config.mjs +13 -0
- package/src/constants.mjs +3 -0
- package/src/coupon.mjs +62 -0
- package/templates/cline/.clinerules +36 -18
- package/templates/codex/AGENTS.md +48 -16
- package/templates/cursor/.cursor/rules/aigateway.mdc +27 -14
- package/templates/windsurf/.windsurfrules +24 -13
package/docs/output-schema.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Output Schema
|
|
2
2
|
|
|
3
|
-
Every `aigateway` command emits **exactly one line of JSON** to **stdout**
|
|
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": "
|
|
15
|
-
"version": "
|
|
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": "
|
|
26
|
-
"version": "
|
|
25
|
+
"command": "sb-invoke",
|
|
26
|
+
"version": "x.y.z",
|
|
27
27
|
"error": {
|
|
28
|
-
"code": "
|
|
29
|
-
"message": "
|
|
30
|
-
"
|
|
31
|
-
"
|
|
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
|
-
"
|
|
53
|
-
"
|
|
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
|
-
### `
|
|
80
|
+
### `sb invoke`
|
|
74
81
|
|
|
75
82
|
```json
|
|
76
83
|
{
|
|
77
|
-
"
|
|
78
|
-
"
|
|
79
|
-
"
|
|
80
|
-
"
|
|
81
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
90
|
-
"
|
|
91
|
-
"
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
133
|
+
### `sb tools`
|
|
103
134
|
|
|
104
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
200
|
+
aigateway --legacy-output wallet-balance
|
|
186
201
|
```
|
|
187
202
|
|
|
188
|
-
Legacy mode is kept
|
|
203
|
+
Legacy mode is kept as a migration aid. New integrations should use the envelope.
|
|
@@ -1,31 +1,38 @@
|
|
|
1
|
-
# Recipe — Schedule Recurring
|
|
1
|
+
# Recipe — Schedule Recurring Paid Invocations
|
|
2
2
|
|
|
3
|
-
|
|
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
|
|
11
|
+
aigateway wallet-topup --amount 50 # adds USDT + a tiny amount of BNB for the one-time approve
|
|
10
12
|
```
|
|
11
|
-
2. **
|
|
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/
|
|
18
|
+
`~/bin/invoke-tool.sh`:
|
|
16
19
|
|
|
17
20
|
```bash
|
|
18
21
|
#!/usr/bin/env bash
|
|
19
22
|
set -euo pipefail
|
|
20
23
|
|
|
21
|
-
|
|
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/
|
|
29
|
+
LOG="$LOG_DIR/invoke-${TS}.log"
|
|
26
30
|
|
|
27
31
|
# Capture envelope on stdout; quiet stderr noise
|
|
28
|
-
ENVELOPE=$(aigateway --quiet
|
|
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
|
-
|
|
41
|
-
echo "
|
|
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/
|
|
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/
|
|
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-
|
|
62
|
-
| `INSUFFICIENT_BNB` |
|
|
63
|
-
| `
|
|
64
|
-
| `
|
|
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
|
-
| `
|
|
11
|
-
| `
|
|
12
|
-
| `
|
|
13
|
-
| `
|
|
14
|
-
| `
|
|
15
|
-
| `
|
|
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 |
|
|
19
|
-
| `
|
|
20
|
-
| `
|
|
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 |
|
|
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(
|
|
45
|
-
|
|
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
|
-
> ⚠️
|
|
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
|
|
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:
|
|
37
|
-
const { envelope, exitCode } = await
|
|
37
|
+
// Example: invoke an AI tool (image generation) via the x402 paid entry point
|
|
38
|
+
const { envelope, exitCode } = await runAigateway([
|
|
38
39
|
"--quiet",
|
|
39
|
-
"
|
|
40
|
-
"--
|
|
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 {
|
|
47
|
-
console.log(
|
|
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([
|
|
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
|
-
|
|
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
|
-
##
|
|
92
|
+
## Discovering Models — `sb tools`
|
|
85
93
|
|
|
86
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
}
|