x402-agent-gateway 0.1.0

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.
Files changed (4) hide show
  1. package/README.md +119 -0
  2. package/bin/cli.js +116 -0
  3. package/package.json +51 -0
  4. package/src/index.js +128 -0
package/README.md ADDED
@@ -0,0 +1,119 @@
1
+ # x402 Agent Gateway
2
+
3
+ Pay-per-call APIs for autonomous agents, settled in USDC over [x402](https://x402.org).
4
+ No account, no API key, no subscription. An agent discovers the endpoint, gets a `402`
5
+ with the exact amount, pays, and gets the result.
6
+
7
+ Live API: `https://api.x-402.online`
8
+
9
+ ## Start here: the free quote
10
+
11
+ Nothing below costs anything until you choose to pay. Two endpoints are free and require
12
+ no wallet at all.
13
+
14
+ ```bash
15
+ npx x402-agent-gateway capabilities # what can be solved
16
+ npx x402-agent-gateway preview "summarise ethereum etf news" # the plan and the price
17
+ ```
18
+
19
+ The quote tells you which capability was chosen, why, what it costs, **and when a direct
20
+ route would be cheaper than the gateway**. The gateway has a single price, so it overcharges
21
+ for cheap capabilities. You should know that before you pay, not after.
22
+
23
+ ## The flagship product
24
+
25
+ **`POST /v1/us/brief` — $0.040004**
26
+
27
+ A decision-grade briefing on a US public company, from SEC EDGAR, in one call:
28
+
29
+ - a summary where **every claim cites its origin**: `[identity]`, `[financials]`, `[filings]`
30
+ - key points and the open questions the data does not settle
31
+ - **the underlying facts alongside**: identity, three years of annual figures, recent filings
32
+
33
+ Buying the three underlying routes separately costs **$0.090024**. This replaces them with a
34
+ synthesised answer for **56% less**, and you can still verify every claim because the raw
35
+ facts come back with it.
36
+
37
+ ```js
38
+ import { Gateway } from "x402-agent-gateway";
39
+
40
+ const g = new Gateway({ fetch: myX402Fetch }); // any x402-capable fetch
41
+ const r = await g.usBrief("AAPL");
42
+
43
+ r.brief.summary // "Apple Inc. (CIK 0000320193) is a Nasdaq-listed … [identity]"
44
+ r.brief.key_points // each one cites its source
45
+ r.financials.revenue // the raw figures, to check the summary against
46
+ r.filings // the recent SEC filings
47
+ ```
48
+
49
+ ```python
50
+ from x402_gateway import Gateway
51
+ g = Gateway()
52
+ r = g.us_brief("AAPL")
53
+ print(r["brief"]["summary"], r["financials"]["revenue"])
54
+ ```
55
+
56
+ Without a payment-capable fetch, `usBrief` raises `PaymentRequired` **carrying the x402
57
+ challenge** — amount, network and address. Pay it with whatever tooling you already use.
58
+ This SDK does not impose its own.
59
+
60
+ ## Capabilities behind one endpoint
61
+
62
+ `POST /v1/solve` — $0.012002. Send a task in plain words, or name a capability and its
63
+ input. The gateway resolves which capability is needed, ranks providers by expected net
64
+ margin and observed success rate, executes, falls back to the next provider on failure, and
65
+ returns the result with the routing it used.
66
+
67
+ | capability | what it does |
68
+ |---|---|
69
+ | `company_brief_us` | the SEC briefing above |
70
+ | `web_search` | ranked web results |
71
+ | `news_search` | fresh headlines |
72
+ | `news_brief` | headlines plus a sourced summary |
73
+ | `llm_generate` | text generation, two providers, one engine |
74
+ | `web_extract` | a page as clean markdown, French residential IP, real browser |
75
+ | `web_render` | full HTML after JavaScript execution |
76
+
77
+ `GET /v1/capabilities` returns the input and output schemas, and lists only what is actually
78
+ servable right now.
79
+
80
+ ## Discovery, for machines
81
+
82
+ | surface | what it carries |
83
+ |---|---|
84
+ | `/openapi.json` | the full OpenAPI description |
85
+ | `/llms.txt` | a plain-text summary for language models |
86
+ | `/.well-known/x402` | the x402 payment metadata |
87
+ | `/.well-known/agent-card.json` | the A2A Agent Card, protocol 0.3.0 |
88
+ | `/mcp` | an MCP server, 63 tools, `tools/list` over JSON-RPC |
89
+ | `/v1/capabilities` | the gateway's capability registry |
90
+
91
+ ## Payment
92
+
93
+ x402, `exact` scheme, USDC on Base (`eip155:8453`) and Solana. Each route has a **unique**
94
+ amount, so every settlement is attributable on-chain by the amount alone. `/v1/us/brief`
95
+ is `40004` micro-USDC, `/v1/solve` is `12002`.
96
+
97
+ ## Install
98
+
99
+ ```bash
100
+ npm install x402-agent-gateway # Node 20+
101
+ pip install x402-agent-gateway # Python 3.10+, no dependencies
102
+ ```
103
+
104
+ Both are in this repository under `node/` and `python/`. If the published packages are not
105
+ up yet, point the client at the API directly — the SDK is a thin, optional convenience:
106
+
107
+ ```bash
108
+ curl -s -X POST https://api.x-402.online/v1/us/brief \
109
+ -H 'content-type: application/json' -d '{"ticker":"AAPL"}'
110
+ # → 402 with the exact amount to pay
111
+ ```
112
+
113
+ ## What this is not
114
+
115
+ Not investment advice. `/v1/us/brief` reports what SEC EDGAR contains and cites it; it never
116
+ recommends buying or selling anything, and it says in `open_questions` what the filings do
117
+ not answer rather than filling the gap with a guess.
118
+
119
+ MIT licensed.
package/bin/cli.js ADDED
@@ -0,0 +1,116 @@
1
+ #!/usr/bin/env node
2
+ // CLI du gateway. Trois verbes, et le devis est celui qu'on tape en premier parce qu'il
3
+ // est gratuit : on voit le plan et le prix avant de sortir un portefeuille.
4
+
5
+ import { Gateway, PaymentRequired, GatewayError } from "../src/index.js";
6
+
7
+ const args = process.argv.slice(2);
8
+ const verbe = args[0];
9
+ const reste = args.slice(1);
10
+
11
+ function opt(nom, defaut) {
12
+ const i = reste.indexOf(`--${nom}`);
13
+ if (i === -1) return defaut;
14
+ const v = reste[i + 1];
15
+ reste.splice(i, 2);
16
+ return v;
17
+ }
18
+
19
+ const base = opt("url", process.env.GATEWAY_URL);
20
+ const cle = opt("key", process.env.GATEWAY_API_KEY);
21
+ const json = reste.includes("--json") && reste.splice(reste.indexOf("--json"), 1);
22
+ const g = new Gateway({ baseUrl: base, apiKey: cle });
23
+
24
+ const AIDE = `agent-gateway — une intégration pour toutes les capacités
25
+
26
+ agent-gateway capabilities ce que le gateway sait faire (gratuit)
27
+ agent-gateway preview "<tâche>" le plan et le prix, sans exécuter (gratuit)
28
+ agent-gateway solve "<tâche>" exécute et livre (x402)
29
+
30
+ Options
31
+ --capability <nom> impose la capacité au lieu de la deviner
32
+ --input '<json>' l'entrée, quand on impose la capacité
33
+ --url <base> défaut https://api.x-402.online (ou GATEWAY_URL)
34
+ --key <clé> clé interne, pour un accès sans x402 (ou GATEWAY_API_KEY)
35
+ --json sortie brute, pour un script`;
36
+
37
+ function demande() {
38
+ const cap = opt("capability", null);
39
+ if (cap) {
40
+ const brut = opt("input", "{}");
41
+ let input;
42
+ try { input = JSON.parse(brut); }
43
+ catch { sortir(`--input n'est pas du JSON valide : ${brut}`); }
44
+ return { capability: cap, input };
45
+ }
46
+ const tache = reste.filter((a) => !a.startsWith("--")).join(" ").trim();
47
+ if (!tache) sortir("il faut une tâche, ou --capability avec --input");
48
+ return { task: tache };
49
+ }
50
+
51
+ function sortir(msg, code = 1) {
52
+ console.error(`erreur : ${msg}`);
53
+ process.exit(code);
54
+ }
55
+
56
+ try {
57
+ if (!verbe || verbe === "help" || verbe === "--help" || verbe === "-h") {
58
+ console.log(AIDE);
59
+ process.exit(0);
60
+ }
61
+
62
+ if (verbe === "capabilities") {
63
+ const r = await g.capabilities();
64
+ if (json) { console.log(JSON.stringify(r, null, 2)); process.exit(0); }
65
+ console.log(`prix par solve : ${r.price_per_solve_usd} $ · paiement ${r.payment.protocol} ${r.payment.asset}\n`);
66
+ for (const c of r.capabilities) {
67
+ console.log(` ${c.capability.padEnd(20)} ${String(c.providers).padStart(2)} provider(s) · ~${c.latency_ms_estimee} ms`);
68
+ console.log(` ${" ".repeat(20)} ${c.description}`);
69
+ }
70
+ process.exit(0);
71
+ }
72
+
73
+ if (verbe === "preview") {
74
+ const r = await g.preview(demande());
75
+ if (json) { console.log(JSON.stringify(r, null, 2)); process.exit(0); }
76
+ const q = r.quote;
77
+ console.log(`capacité ${q.capability}`);
78
+ console.log(`pourquoi ${q.why_this_capability}`);
79
+ console.log(`entrée ${JSON.stringify(q.normalized_input)}`);
80
+ console.log(`prix ${q.price_usd} $ (${q.payment.amount_micro} micro-USDC)`);
81
+ console.log(`providers ${q.providers_in_order.join(" puis ")}`);
82
+ console.log(`exécutera ${q.will_execute}${q.refused_because ? ` — ${q.refused_because}` : ""}`);
83
+ if (q.cheaper_direct) {
84
+ console.log(`\nmoins cher en direct : ${q.cheaper_direct.route} à ${q.cheaper_direct.price_usd} $ (${q.cheaper_direct.you_save_usd} $ économisés)`);
85
+ console.log(` ${q.cheaper_direct.note}`);
86
+ }
87
+ process.exit(0);
88
+ }
89
+
90
+ if (verbe === "solve") {
91
+ const r = await g.solve(demande());
92
+ if (json) { console.log(JSON.stringify(r, null, 2)); process.exit(0); }
93
+ console.log(`capacité ${r.capability} · ${r.routing.provider} · ${r.routing.latency_ms} ms`);
94
+ if (r.routing.attempts.length > 1) {
95
+ console.log(`tentatives : ${r.routing.attempts.map((a) => `${a.provider}${a.ok ? " ok" : " échec"}`).join(" | ")}`);
96
+ }
97
+ console.log("");
98
+ console.log(JSON.stringify(r.result, null, 2));
99
+ process.exit(0);
100
+ }
101
+
102
+ sortir(`verbe inconnu « ${verbe} »\n\n${AIDE}`);
103
+ } catch (e) {
104
+ if (e instanceof PaymentRequired) {
105
+ console.error("paiement requis. Le défi x402 :");
106
+ for (const a of e.accepts) console.error(` ${a.network} · ${a.amount} (micro-USDC) · vers ${a.payTo}`);
107
+ console.error("\nUtilise « preview » pour voir le plan sans payer, ou passe un fetch payeur au SDK.");
108
+ process.exit(2);
109
+ }
110
+ if (e instanceof GatewayError) {
111
+ console.error(`erreur : ${e.message}`);
112
+ if (e.body) console.error(JSON.stringify(e.body, null, 2));
113
+ process.exit(1);
114
+ }
115
+ throw e;
116
+ }
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "x402-agent-gateway",
3
+ "version": "0.1.0",
4
+ "description": "One endpoint for any agent capability: resolve a task, route it to the best provider, pay per call in USDC over x402.",
5
+ "type": "module",
6
+ "main": "src/index.js",
7
+ "exports": {
8
+ ".": "./src/index.js"
9
+ },
10
+ "bin": {
11
+ "agent-gateway": "bin/cli.js"
12
+ },
13
+ "files": [
14
+ "src",
15
+ "bin",
16
+ "README.md"
17
+ ],
18
+ "scripts": {
19
+ "test": "node --test test/"
20
+ },
21
+ "keywords": [
22
+ "x402",
23
+ "agent",
24
+ "gateway",
25
+ "mcp",
26
+ "a2a",
27
+ "usdc",
28
+ "ai",
29
+ "ai-agents",
30
+ "pay-per-call",
31
+ "sec-edgar",
32
+ "agentic-commerce"
33
+ ],
34
+ "license": "MIT",
35
+ "engines": {
36
+ "node": ">=20"
37
+ },
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "git+https://github.com/laurenthalbrun/x402-agent-gateway.git",
41
+ "directory": "node"
42
+ },
43
+ "homepage": "https://github.com/laurenthalbrun/x402-agent-gateway#readme",
44
+ "bugs": {
45
+ "url": "https://github.com/laurenthalbrun/x402-agent-gateway/issues"
46
+ },
47
+ "author": "laurenthalbrun",
48
+ "publishConfig": {
49
+ "access": "public"
50
+ }
51
+ }
package/src/index.js ADDED
@@ -0,0 +1,128 @@
1
+ // SDK Node du Universal Agent Gateway.
2
+ //
3
+ // Objectif : première intégration en moins de cinq minutes. Donc trois fonctions, aucune
4
+ // dépendance obligatoire, et le paiement optionnel.
5
+ //
6
+ // Choix qui compte : `preview` et `capabilities` sont GRATUITS et ne demandent aucun
7
+ // portefeuille. Un développeur doit pouvoir installer, appeler, voir le plan et le prix,
8
+ // et décider ensuite. Un SDK qui exige une clé ou un portefeuille avant de montrer quoi
9
+ // que ce soit se fait désinstaller.
10
+
11
+ const DEFAUT = "https://api.x-402.online";
12
+
13
+ export class GatewayError extends Error {
14
+ constructor(message, { status, body } = {}) {
15
+ super(message);
16
+ this.name = "GatewayError";
17
+ this.status = status;
18
+ this.body = body;
19
+ }
20
+ }
21
+
22
+ /** Levée quand la route répond 402 et qu'aucun moyen de paiement n'a été fourni.
23
+ * Elle porte le défi tel quel : le montant, le réseau et l'adresse sont dedans, donc un
24
+ * agent peut payer avec son propre outillage sans que ce SDK impose le sien. */
25
+ export class PaymentRequired extends GatewayError {
26
+ constructor(challenge) {
27
+ super("payment required", { status: 402, body: challenge });
28
+ this.name = "PaymentRequired";
29
+ this.challenge = challenge;
30
+ this.accepts = challenge && challenge.accepts ? challenge.accepts : [];
31
+ }
32
+ }
33
+
34
+ export class Gateway {
35
+ /**
36
+ * @param {object} [opts]
37
+ * @param {string} [opts.baseUrl] par défaut https://api.x-402.online
38
+ * @param {Function} [opts.fetch] un fetch qui sait payer x402 (par ex. @x402/fetch).
39
+ * Sans lui, `solve` lève PaymentRequired avec le défi.
40
+ * @param {string} [opts.apiKey] clé interne, pour les appels qui ne passent pas par x402
41
+ * @param {number} [opts.timeoutMs] 60 s par défaut ; certaines capacités prennent 12 s
42
+ */
43
+ constructor(opts = {}) {
44
+ this.baseUrl = (opts.baseUrl || DEFAUT).replace(/\/+$/, "");
45
+ this.fetch = opts.fetch || globalThis.fetch;
46
+ this.apiKey = opts.apiKey || null;
47
+ this.timeoutMs = opts.timeoutMs || 60000;
48
+ }
49
+
50
+ async #appel(chemin, { method = "POST", body } = {}) {
51
+ const headers = { "content-type": "application/json" };
52
+ if (this.apiKey) headers["x-api-key"] = this.apiKey;
53
+ const r = await this.fetch(`${this.baseUrl}${chemin}`, {
54
+ method,
55
+ headers,
56
+ body: body === undefined ? undefined : JSON.stringify(body),
57
+ signal: AbortSignal.timeout(this.timeoutMs),
58
+ });
59
+ const txt = await r.text();
60
+ let j = null;
61
+ try { j = JSON.parse(txt); } catch { /* laisse j a null */ }
62
+ if (r.status === 402) throw new PaymentRequired(j);
63
+ if (!r.ok) {
64
+ throw new GatewayError(
65
+ (j && (j.error || j.erreur)) || `HTTP ${r.status}`,
66
+ { status: r.status, body: j ?? txt.slice(0, 400) },
67
+ );
68
+ }
69
+ return j;
70
+ }
71
+
72
+ // Les trois méthodes sont `async` pour une raison précise, trouvée par un test : sans
73
+ // ça, une demande malformée levait de façon SYNCHRONE pendant que tout le reste
74
+ // rejetait une promesse. Un appelant qui fait `.catch()` n'attrape pas un throw
75
+ // synchrone, et son programme s'arrête là où il croyait avoir géré l'erreur. Une API
76
+ // qui échoue de deux façons différentes selon l'erreur est un piège.
77
+
78
+ /** Le registre : ce que le gateway sait faire, avec les schémas et le prix. Gratuit. */
79
+ async capabilities() {
80
+ return this.#appel("/v1/capabilities", { method: "GET" });
81
+ }
82
+
83
+ /** Le devis : quelle capacité, quelle entrée normalisée, quel prix, et si une route
84
+ * directe est moins chère. Gratuit, n'exécute rien, ne consomme aucun amont. */
85
+ async preview(demande) {
86
+ return this.#appel("/v1/solve/preview", { body: normaliser(demande) });
87
+ }
88
+
89
+ /** L'exécution. Sans fetch payeur, lève PaymentRequired en portant le défi x402. */
90
+ async solve(demande) {
91
+ return this.#appel("/v1/solve", { body: normaliser(demande) });
92
+ }
93
+
94
+ // ===== Appels directs, pour qui sait déjà ce qu'il veut =====================
95
+ //
96
+ // Le gateway devine la capacité à partir d'une tâche en mots simples, ce qui est
97
+ // pratique quand on ne sait pas. Quand on sait, deviner est du travail inutile et,
98
+ // à prix unique, le gateway coûte plus cher que la route directe. Ces méthodes
99
+ // appellent donc la route, et le SDK le dit plutôt que de pousser vers le gateway.
100
+
101
+ /** Identité d'une société cotée américaine, depuis SEC EDGAR. 0,02001 $. */
102
+ async usCompany(tickerOuCik) {
103
+ if (!tickerOuCik) throw new GatewayError("usCompany attend un ticker ou un CIK");
104
+ return this.#appel("/v1/us/company", { body: { ticker: String(tickerOuCik) } });
105
+ }
106
+
107
+ /** Briefing sourcé sur une société cotée américaine : synthèse dont chaque affirmation
108
+ * cite son origine, plus l'identité, les comptes annuels et les dépôts récents en clair
109
+ * pour vérification. 0,040004 $, contre 0,090024 $ si on achète les trois séparément. */
110
+ async usBrief(tickerOuCik) {
111
+ if (!tickerOuCik) throw new GatewayError("usBrief attend un ticker ou un CIK");
112
+ return this.#appel("/v1/us/brief", { body: { ticker: String(tickerOuCik) } });
113
+ }
114
+ }
115
+
116
+ /** Accepte `solve("une tâche")` aussi bien que `solve({ capability, input })`. */
117
+ function normaliser(d) {
118
+ if (typeof d === "string") return { task: d };
119
+ if (d && typeof d === "object") return d;
120
+ throw new GatewayError("demande invalide : une chaîne ou { task } ou { capability, input }");
121
+ }
122
+
123
+ /** Raccourci sans instanciation, pour le cas le plus courant. */
124
+ export function gateway(opts) {
125
+ return new Gateway(opts);
126
+ }
127
+
128
+ export default Gateway;