@key0ai/key0 0.1.0 → 0.2.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 (54) hide show
  1. package/README.md +66 -21
  2. package/dist/__tests__/e2e.test.js +1 -1
  3. package/dist/__tests__/e2e.test.js.map +1 -1
  4. package/dist/__tests__/x402-http-middleware.test.js +4 -207
  5. package/dist/__tests__/x402-http-middleware.test.js.map +1 -1
  6. package/dist/core/__tests__/agent-card.test.js +14 -12
  7. package/dist/core/__tests__/agent-card.test.js.map +1 -1
  8. package/dist/core/__tests__/storage-postgres.test.js +0 -1
  9. package/dist/core/__tests__/storage-postgres.test.js.map +1 -1
  10. package/dist/core/agent-card.d.ts.map +1 -1
  11. package/dist/core/agent-card.js +25 -10
  12. package/dist/core/agent-card.js.map +1 -1
  13. package/dist/core/challenge-engine.js +1 -1
  14. package/dist/core/challenge-engine.js.map +1 -1
  15. package/dist/core/index.d.ts +1 -1
  16. package/dist/core/index.d.ts.map +1 -1
  17. package/dist/core/index.js +1 -1
  18. package/dist/core/index.js.map +1 -1
  19. package/dist/integrations/express.d.ts +6 -4
  20. package/dist/integrations/express.d.ts.map +1 -1
  21. package/dist/integrations/express.js +27 -29
  22. package/dist/integrations/express.js.map +1 -1
  23. package/dist/integrations/fastify.d.ts +5 -1
  24. package/dist/integrations/fastify.d.ts.map +1 -1
  25. package/dist/integrations/fastify.js +117 -8
  26. package/dist/integrations/fastify.js.map +1 -1
  27. package/dist/integrations/hono.d.ts +8 -2
  28. package/dist/integrations/hono.d.ts.map +1 -1
  29. package/dist/integrations/hono.js +116 -12
  30. package/dist/integrations/hono.js.map +1 -1
  31. package/dist/integrations/settlement.d.ts.map +1 -1
  32. package/dist/integrations/settlement.js +1 -3
  33. package/dist/integrations/settlement.js.map +1 -1
  34. package/dist/types/agent-card.d.ts +16 -0
  35. package/dist/types/agent-card.d.ts.map +1 -1
  36. package/package.json +3 -1
  37. package/src/__tests__/e2e.test.ts +1 -1
  38. package/src/__tests__/x402-http-middleware.test.ts +4 -256
  39. package/src/core/__tests__/agent-card.test.ts +15 -12
  40. package/src/core/__tests__/storage-postgres.test.ts +0 -2
  41. package/src/core/agent-card.ts +26 -10
  42. package/src/core/challenge-engine.ts +1 -1
  43. package/src/core/index.ts +1 -1
  44. package/src/integrations/express.ts +221 -235
  45. package/src/integrations/fastify.ts +160 -8
  46. package/src/integrations/hono.ts +168 -12
  47. package/src/integrations/settlement.ts +1 -3
  48. package/src/types/agent-card.ts +13 -2
  49. package/src/types/config.ts +1 -1
  50. package/dist/integrations/x402-http-middleware.d.ts +0 -15
  51. package/dist/integrations/x402-http-middleware.d.ts.map +0 -1
  52. package/dist/integrations/x402-http-middleware.js +0 -171
  53. package/dist/integrations/x402-http-middleware.js.map +0 -1
  54. package/src/integrations/x402-http-middleware.ts +0 -246
@@ -1,246 +0,0 @@
1
- import type { NextFunction, Request, Response } from "express";
2
- import type { ChallengeEngine } from "../core/index.js";
3
- import { CHAIN_CONFIGS } from "../types/config-shared.js";
4
- import type {
5
- AccessGrant,
6
- AccessRequest,
7
- SellerConfig,
8
- X402SettleResponse,
9
- } from "../types/index.js";
10
- import { Key0Error } from "../types/index.js";
11
- import {
12
- buildDiscoveryResponse as buildDiscoveryResponseImpl,
13
- buildHttpPaymentRequirements,
14
- decodePaymentSignature,
15
- settlePayment,
16
- } from "./settlement.js";
17
-
18
- // x402 v2 headers
19
- const PAYMENT_SIGNATURE_HEADER = "payment-signature";
20
- const PAYMENT_REQUIRED_HEADER = "payment-required";
21
- const PAYMENT_RESPONSE_HEADER = "payment-response";
22
- const X_A2A_EXTENSIONS_HEADER = "x-a2a-extensions";
23
-
24
- // Re-export shared settlement utilities so callers can import from a single place
25
- export {
26
- buildDiscoveryResponse,
27
- buildHttpPaymentRequirements,
28
- decodePaymentSignature,
29
- settlePayment,
30
- settleViaFacilitator,
31
- settleViaGasWallet,
32
- } from "./settlement.js";
33
-
34
- /**
35
- * Express middleware that intercepts AccessRequest calls on the JSON-RPC endpoint
36
- * and implements the x402 HTTP flow for clients that do NOT send X-A2A-Extensions.
37
- *
38
- * Routing:
39
- * X-A2A-Extensions present → A2A-native client → pass through to A2A JSON-RPC handler
40
- * message/send + no payment-signature → HTTP 402 with PaymentRequirements
41
- * message/send + payment-signature → settle → HTTP 200 with AccessGrant
42
- */
43
- export function createX402HttpMiddleware(engine: ChallengeEngine, config: SellerConfig) {
44
- const networkConfig = CHAIN_CONFIGS[config.network];
45
-
46
- return async (req: Request, res: Response, next: NextFunction) => {
47
- console.log("\n[x402-http-middleware] ========== NEW REQUEST ==========");
48
- console.log("[x402-http-middleware] Method:", req.method);
49
- console.log("[x402-http-middleware] Path:", req.path);
50
- console.log("[x402-http-middleware] Headers:", JSON.stringify(req.headers, null, 2));
51
-
52
- // Intercept response to log the body
53
- const originalJson = res.json.bind(res);
54
- const originalSend = res.send.bind(res);
55
-
56
- res.json = (body: any) => {
57
- console.log("[x402-http-middleware] Response Status:", res.statusCode);
58
- console.log("[x402-http-middleware] Response Body:", JSON.stringify(body, null, 2));
59
- return originalJson(body);
60
- };
61
-
62
- res.send = (body: any) => {
63
- console.log("[x402-http-middleware] Response Status:", res.statusCode);
64
- console.log(
65
- "[x402-http-middleware] Response Body:",
66
- typeof body === "string" ? body : JSON.stringify(body, null, 2),
67
- );
68
- return originalSend(body);
69
- };
70
-
71
- try {
72
- // 1. If X-A2A-Extensions header present, this is an A2A client → pass through
73
- const hasA2AExtensions = req.headers[X_A2A_EXTENSIONS_HEADER];
74
- console.log(`[x402-http-middleware] X-A2A-Extensions header present: ${!!hasA2AExtensions}`);
75
- if (hasA2AExtensions) {
76
- console.log("[x402-http-middleware] → Passing through to A2A JSON-RPC handler");
77
- return next();
78
- }
79
-
80
- // 2. Parse JSON-RPC body
81
- const body = req.body;
82
- console.log("[x402-http-middleware] Body:", JSON.stringify(body, null, 2));
83
- if (!body || typeof body !== "object") {
84
- console.log("[x402-http-middleware] → No valid body, passing through");
85
- return next();
86
- }
87
-
88
- // 3. Only intercept message/send
89
- console.log(`[x402-http-middleware] Method in body: ${body.method}`);
90
- if (body.method !== "message/send") {
91
- console.log("[x402-http-middleware] → Not a message/send call, passing through");
92
- return next();
93
- }
94
-
95
- // 4. Extract AccessRequest from message parts
96
- // Route on tierId presence, not on type field
97
- const params = body.params;
98
- if (!params || !params.message || !params.message.parts) {
99
- console.log("[x402-http-middleware] → No valid message parts, passing through");
100
- return next();
101
- }
102
-
103
- let accessRequest: AccessRequest | null = null;
104
-
105
- console.log(`[x402-http-middleware] Parsing ${params.message.parts.length} message parts...`);
106
- for (const part of params.message.parts) {
107
- console.log(`[x402-http-middleware] - Part kind: ${part.kind}`);
108
- if (part.kind === "data" && part.data && part.data.type === "AccessRequest") {
109
- accessRequest = part.data as AccessRequest;
110
- console.log("[x402-http-middleware] ✓ Found AccessRequest data part");
111
- break;
112
- }
113
- if (part.kind === "text") {
114
- try {
115
- const parsed = JSON.parse(part.text);
116
- if (parsed.type === "AccessRequest") {
117
- accessRequest = parsed as AccessRequest;
118
- console.log("[x402-http-middleware] ✓ Found AccessRequest in text part");
119
- break;
120
- }
121
- } catch {
122
- continue;
123
- }
124
- }
125
- }
126
-
127
- if (!accessRequest) {
128
- console.log("[x402-http-middleware] → No data found in message parts, passing through");
129
- return next();
130
- }
131
-
132
- const resourceId = accessRequest.resourceId || "default";
133
- const planId = accessRequest.planId;
134
- const requestId = accessRequest.requestId || `http-${crypto.randomUUID()}`;
135
- console.log(
136
- `[x402-http-middleware] AccessRequest: planId=${planId}, resourceId=${resourceId}, requestId=${requestId}`,
137
- );
138
-
139
- // 5a. Discovery case: no planId → return 402 with full product catalog
140
- if (!planId) {
141
- console.log("[x402-http-middleware] → No planId provided, returning discovery 402");
142
- const discoveryResponse = buildDiscoveryResponseImpl(config, networkConfig);
143
- const encoded = Buffer.from(JSON.stringify(discoveryResponse)).toString("base64");
144
- res.setHeader(PAYMENT_REQUIRED_HEADER, encoded);
145
- return res.status(402).json({
146
- ...discoveryResponse,
147
- error: "Payment required",
148
- });
149
- }
150
-
151
- // 5b. Check for PAYMENT-SIGNATURE header
152
- const paymentSignatureRaw = req.headers[PAYMENT_SIGNATURE_HEADER] as string | undefined;
153
- console.log(
154
- `[x402-http-middleware] PAYMENT-SIGNATURE header present: ${!!paymentSignatureRaw}`,
155
- );
156
-
157
- if (!paymentSignatureRaw) {
158
- // ===== STEP 1: No payment → create PENDING record and return HTTP 402 =====
159
- console.log("[x402-http-middleware] → STEP 1: Issuing 402 challenge");
160
-
161
- const { challengeId } = await engine.requestHttpAccess(requestId, planId, resourceId);
162
- console.log(`[x402-http-middleware] ✓ PENDING record created, challengeId=${challengeId}`);
163
-
164
- const requirements = buildHttpPaymentRequirements(
165
- planId,
166
- resourceId,
167
- config,
168
- networkConfig,
169
- );
170
- console.log(
171
- "[x402-http-middleware] Payment requirements:",
172
- JSON.stringify(requirements, null, 2),
173
- );
174
-
175
- const base64Requirements = Buffer.from(JSON.stringify(requirements)).toString("base64");
176
- res.setHeader(PAYMENT_REQUIRED_HEADER, base64Requirements);
177
-
178
- return res.status(402).json({
179
- ...requirements,
180
- challengeId,
181
- error: "PAYMENT-SIGNATURE header is required",
182
- });
183
- }
184
-
185
- // ===== STEP 2: Has PAYMENT-SIGNATURE → settle and return access grant =====
186
- console.log("[x402-http-middleware] → STEP 2: Processing payment");
187
- console.log(
188
- `[x402-http-middleware] PAYMENT-SIGNATURE (first 50 chars): ${paymentSignatureRaw.substring(0, 50)}...`,
189
- );
190
-
191
- // Pre-settlement check: avoid burning USDC if already delivered/expired/cancelled
192
- const existingGrant = await engine.preSettlementCheck(requestId);
193
- if (existingGrant) {
194
- console.log("[x402-http-middleware] ✓ Already delivered, returning cached grant");
195
- return res.status(200).json(existingGrant);
196
- }
197
-
198
- // Decode the header then settle via shared settlement layer
199
- const paymentPayload = decodePaymentSignature(paymentSignatureRaw);
200
- const {
201
- txHash,
202
- settleResponse: _settleResponse,
203
- payer,
204
- } = await settlePayment(paymentPayload, config, networkConfig);
205
-
206
- console.log(`[x402-http-middleware] ✓ Payment settled, txHash: ${txHash}`);
207
-
208
- const grant: AccessGrant = await engine.processHttpPayment(
209
- requestId,
210
- planId,
211
- resourceId,
212
- txHash,
213
- payer as `0x${string}` | undefined,
214
- );
215
- console.log("[x402-http-middleware] ✓ Access grant issued:", JSON.stringify(grant, null, 2));
216
-
217
- const settlementResponse: X402SettleResponse = {
218
- success: true,
219
- transaction: txHash,
220
- network: `eip155:${networkConfig.chainId}`,
221
- ...(payer && { payer }),
222
- };
223
- res.setHeader(
224
- PAYMENT_RESPONSE_HEADER,
225
- Buffer.from(JSON.stringify(settlementResponse)).toString("base64"),
226
- );
227
-
228
- return res.status(200).json(grant);
229
- } catch (err: unknown) {
230
- console.error("[x402-http-middleware] ✗ ERROR:", err);
231
- if (err instanceof Key0Error) {
232
- // Return the grant directly for PROOF_ALREADY_REDEEMED (status 200)
233
- if (err.code === "PROOF_ALREADY_REDEEMED" && err.details?.["grant"]) {
234
- return res.status(200).json(err.details["grant"]);
235
- }
236
- return res.status(err.httpStatus).json(err.toJSON());
237
- }
238
- return res.status(500).json({
239
- error: "INTERNAL_ERROR",
240
- message: err instanceof Error ? err.message : "Internal server error",
241
- });
242
- } finally {
243
- console.log("[x402-http-middleware] ========== REQUEST COMPLETE ==========\n");
244
- }
245
- };
246
- }