xytara 3.1.13 → 3.1.15
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/.env.example +38 -4
- package/BSV_TERANODE_PLATFORM_DISCONTINUITY_RUNBOOK.md +126 -126
- package/REAL_PAYMENT_SETUP.md +14 -7
- package/RELEASE_NOTES.md +9 -4
- package/fixtures/catalog.js +22 -0
- package/lib/a2a_rpc.js +287 -280
- package/lib/account_auth.js +585 -585
- package/lib/agent_identity.js +162 -162
- package/lib/asyncapi_contract.js +3 -3
- package/lib/bolt_adapter.js +187 -187
- package/lib/bolt_payment_front.js +224 -224
- package/lib/buyer_event_log.js +215 -0
- package/lib/buyer_safety_policy.js +132 -0
- package/lib/checkout_processor_adapter.js +217 -217
- package/lib/command_flow.js +911 -722
- package/lib/commerce_artifacts.js +591 -591
- package/lib/commerce_bridge.js +274 -274
- package/lib/commerce_cases.js +399 -399
- package/lib/commerce_client.js +7694 -7694
- package/lib/commerce_quotes.js +29 -0
- package/lib/commerce_runtime.js +322 -322
- package/lib/execution_dedupe.js +86 -86
- package/lib/external_credit_bridge.js +142 -142
- package/lib/http_transport.js +99 -81
- package/lib/l402_adapter.js +241 -241
- package/lib/l402_payment_front.js +265 -265
- package/lib/mpp_adapter.js +237 -237
- package/lib/openapi_contract.js +4 -0
- package/lib/payment_config.js +257 -253
- package/lib/payment_verification.js +357 -357
- package/lib/provider_payout_execution.js +155 -155
- package/lib/provider_registry.js +852 -842
- package/lib/rate_limiter.js +81 -81
- package/lib/runtime_state_store.js +495 -369
- package/lib/settlement_bsv_live.js +600 -600
- package/lib/settlement_bsv_teranode.js +117 -117
- package/lib/stripe_mpp_payment_front.js +258 -258
- package/lib/x402_facilitator.js +198 -191
- package/lib/x402_lane_contract.js +5 -3
- package/lib/x402_settlement_recovery.js +175 -0
- package/package.json +9 -3
- package/scripts/buyer_events_daily_report.js +79 -0
- package/scripts/state_backup_offsite.js +196 -0
- package/scripts/verify_a2a_rpc.js +131 -105
- package/scripts/verify_agent_identity.js +153 -153
- package/scripts/verify_all.js +1 -1
- package/scripts/verify_bolt_adapter.js +128 -128
- package/scripts/verify_buyer_event_log.js +123 -0
- package/scripts/verify_buyer_safety.js +141 -0
- package/scripts/verify_cdp_facilitator.js +110 -110
- package/scripts/verify_checkout_processor.js +109 -109
- package/scripts/verify_commerce_bridge.js +177 -177
- package/scripts/verify_execution_dedupe.js +189 -189
- package/scripts/verify_execution_surfacing.js +76 -76
- package/scripts/verify_final_activation.js +191 -191
- package/scripts/verify_l402_adapter.js +86 -86
- package/scripts/verify_lifecycle_spend_binding.js +88 -0
- package/scripts/verify_mpp_adapter.js +128 -128
- package/scripts/verify_production_readiness.js +268 -268
- package/scripts/verify_rate_limiter.js +52 -52
- package/scripts/verify_refund_credit.js +106 -106
- package/scripts/verify_release_candidate.js +81 -81
- package/scripts/verify_runtime_state_guard.js +117 -0
- package/scripts/verify_service.js +15421 -15421
- package/scripts/verify_verify_execute_settle.js +209 -208
- package/scripts/verify_x402_facilitator.js +307 -286
- package/scripts/verify_x402_settlement_recovery.js +153 -0
- package/server.js +12242 -11974
package/lib/a2a_rpc.js
CHANGED
|
@@ -1,280 +1,287 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
// A2A JSON-RPC endpoint with the x402 payment extension (in-band).
|
|
4
|
-
//
|
|
5
|
-
// Spec shape (A2A + x402 extension): payment travels inside the A2A message
|
|
6
|
-
// metadata, not an HTTP header. An unpaid `message/send` returns a Task in
|
|
7
|
-
// state `input-required` whose status message metadata carries
|
|
8
|
-
// `x402.payment.status: "payment-required"` and `x402.payment.required` (the
|
|
9
|
-
// advertised x402 accepts). The caller re-sends `message/send` with the signed
|
|
10
|
-
// x402 authorization in `x402.payment.payload`; we verify + settle it through
|
|
11
|
-
// the same proven facilitator path used by the HTTP x402 routes, run the
|
|
12
|
-
// requested task, and return a `completed` Task carrying the settlement in
|
|
13
|
-
// `x402.payment.receipts` (plus a PAYMENT-RESPONSE header for header-aware
|
|
14
|
-
// clients).
|
|
15
|
-
//
|
|
16
|
-
// This module is dependency-injected so it can be unit-tested without a live
|
|
17
|
-
// facilitator or network: see verify.js.
|
|
18
|
-
|
|
19
|
-
const crypto = require("crypto");
|
|
20
|
-
|
|
21
|
-
// x402 A2A extension URI (declared in the agent card capabilities.extensions).
|
|
22
|
-
const X402_EXTENSION_URI = "https://github.com/google-a2a/a2a-x402/tree/main/spec/v0.1";
|
|
23
|
-
|
|
24
|
-
// Metadata keys defined by the x402 A2A extension.
|
|
25
|
-
const META_STATUS = "x402.payment.status";
|
|
26
|
-
const META_REQUIRED = "x402.payment.required";
|
|
27
|
-
const META_PAYLOAD = "x402.payment.payload";
|
|
28
|
-
const META_RECEIPTS = "x402.payment.receipts";
|
|
29
|
-
const META_ERROR = "x402.payment.error";
|
|
30
|
-
|
|
31
|
-
function uuid() {
|
|
32
|
-
return crypto.randomUUID();
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function jsonrpcResult(id, result) {
|
|
36
|
-
return { jsonrpc: "2.0", id: typeof id === "undefined" ? null : id, result };
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function jsonrpcError(id, code, message, data) {
|
|
40
|
-
return {
|
|
41
|
-
jsonrpc: "2.0",
|
|
42
|
-
id: typeof id === "undefined" ? null : id,
|
|
43
|
-
error: { code, message, ...(typeof data === "undefined" ? {} : { data }) }
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function agentMessage(text, metadata) {
|
|
48
|
-
return {
|
|
49
|
-
role: "agent",
|
|
50
|
-
kind: "message",
|
|
51
|
-
messageId: uuid(),
|
|
52
|
-
parts: [{ kind: "text", text }],
|
|
53
|
-
...(metadata ? { metadata } : {})
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function paymentRequiredTask({ taskId, contextId, accepts, skillTaskRef }) {
|
|
58
|
-
return {
|
|
59
|
-
id: taskId,
|
|
60
|
-
contextId,
|
|
61
|
-
kind: "task",
|
|
62
|
-
status: {
|
|
63
|
-
state: "input-required",
|
|
64
|
-
message: agentMessage(
|
|
65
|
-
`Payment required to run "${skillTaskRef}". Re-send message/send with the signed x402 authorization in metadata["${META_PAYLOAD}"].`,
|
|
66
|
-
{
|
|
67
|
-
[META_STATUS]: "payment-required",
|
|
68
|
-
[META_REQUIRED]: { x402Version: 2, scheme: "exact", accepts }
|
|
69
|
-
}
|
|
70
|
-
),
|
|
71
|
-
timestamp: new Date().toISOString()
|
|
72
|
-
},
|
|
73
|
-
artifacts: [],
|
|
74
|
-
history: []
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function failedTask({ taskId, contextId, paymentStatus, error }) {
|
|
79
|
-
return {
|
|
80
|
-
id: taskId,
|
|
81
|
-
contextId,
|
|
82
|
-
kind: "task",
|
|
83
|
-
status: {
|
|
84
|
-
state: "failed",
|
|
85
|
-
message: agentMessage(`Payment ${paymentStatus}: ${error || "error"}`, {
|
|
86
|
-
[META_STATUS]: paymentStatus,
|
|
87
|
-
[META_ERROR]: error || null
|
|
88
|
-
}),
|
|
89
|
-
timestamp: new Date().toISOString()
|
|
90
|
-
},
|
|
91
|
-
artifacts: [],
|
|
92
|
-
history: []
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function completedTask({ taskId, contextId, resultPayload, settlement }) {
|
|
97
|
-
return {
|
|
98
|
-
id: taskId,
|
|
99
|
-
contextId,
|
|
100
|
-
kind: "task",
|
|
101
|
-
status: {
|
|
102
|
-
state: "completed",
|
|
103
|
-
message: agentMessage("Completed and settled.", {
|
|
104
|
-
[META_STATUS]: "payment-completed",
|
|
105
|
-
[META_RECEIPTS]: [settlement]
|
|
106
|
-
}),
|
|
107
|
-
timestamp: new Date().toISOString()
|
|
108
|
-
},
|
|
109
|
-
artifacts: [
|
|
110
|
-
{
|
|
111
|
-
artifactId: uuid(),
|
|
112
|
-
name: "result",
|
|
113
|
-
parts: [{ kind: "data", data: resultPayload }]
|
|
114
|
-
}
|
|
115
|
-
],
|
|
116
|
-
history: []
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Pull the task reference + body an agent wants to run out of the A2A message.
|
|
121
|
-
// Accepts an explicit `task_ref`/`skill_id`/`skill` in message.metadata, plus a
|
|
122
|
-
// params/body object (metadata.params | metadata.body | a DataPart's data).
|
|
123
|
-
function resolveSkill(message) {
|
|
124
|
-
const meta = (message && message.metadata) || {};
|
|
125
|
-
const skillTaskRef = String(meta.task_ref || meta.skill_id || meta.skill || "a2a.negotiate");
|
|
126
|
-
let body = meta.params || meta.body || null;
|
|
127
|
-
if (!body && message && Array.isArray(message.parts)) {
|
|
128
|
-
const dataPart = message.parts.find((p) => p && p.kind === "data" && p.data && typeof p.data === "object");
|
|
129
|
-
if (dataPart) body = dataPart.data;
|
|
130
|
-
}
|
|
131
|
-
return { skillTaskRef, body: body && typeof body === "object" ? body : {} };
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Handle a single `message/send` call. Returns { http, headers, body }.
|
|
135
|
-
// `ctx` = { config, resourceUrl, deps } where deps has the injected runtime
|
|
136
|
-
// functions (facilitator factory, verify/settle, receipt encoder, authorized
|
|
137
|
-
// executor, task-payload builder, and `state`).
|
|
138
|
-
async function handleMessageSend(rpc, ctx) {
|
|
139
|
-
const id = rpc.id;
|
|
140
|
-
const params = rpc.params || {};
|
|
141
|
-
const message = params.message || {};
|
|
142
|
-
const meta = message.metadata || {};
|
|
143
|
-
|
|
144
|
-
const config = ctx.config || {};
|
|
145
|
-
const x402 = config.x402 || {};
|
|
146
|
-
const accepts = Array.isArray(x402.accepts) ? x402.accepts : [];
|
|
147
|
-
|
|
148
|
-
if (config.verification_mode !== "x402_facilitator") {
|
|
149
|
-
return { http: 200, body: jsonrpcError(id, -32010, "x402 settlement is not enabled on this runtime") };
|
|
150
|
-
}
|
|
151
|
-
if (!accepts.length) {
|
|
152
|
-
return { http: 200, body: jsonrpcError(id, -32011, "x402 accepts are not configured") };
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const taskId = uuid();
|
|
156
|
-
const contextId = message.contextId || message.taskId || uuid();
|
|
157
|
-
const { skillTaskRef, body } = resolveSkill(message);
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// A2A JSON-RPC endpoint with the x402 payment extension (in-band).
|
|
4
|
+
//
|
|
5
|
+
// Spec shape (A2A + x402 extension): payment travels inside the A2A message
|
|
6
|
+
// metadata, not an HTTP header. An unpaid `message/send` returns a Task in
|
|
7
|
+
// state `input-required` whose status message metadata carries
|
|
8
|
+
// `x402.payment.status: "payment-required"` and `x402.payment.required` (the
|
|
9
|
+
// advertised x402 accepts). The caller re-sends `message/send` with the signed
|
|
10
|
+
// x402 authorization in `x402.payment.payload`; we verify + settle it through
|
|
11
|
+
// the same proven facilitator path used by the HTTP x402 routes, run the
|
|
12
|
+
// requested task, and return a `completed` Task carrying the settlement in
|
|
13
|
+
// `x402.payment.receipts` (plus a PAYMENT-RESPONSE header for header-aware
|
|
14
|
+
// clients).
|
|
15
|
+
//
|
|
16
|
+
// This module is dependency-injected so it can be unit-tested without a live
|
|
17
|
+
// facilitator or network: see verify.js.
|
|
18
|
+
|
|
19
|
+
const crypto = require("crypto");
|
|
20
|
+
|
|
21
|
+
// x402 A2A extension URI (declared in the agent card capabilities.extensions).
|
|
22
|
+
const X402_EXTENSION_URI = "https://github.com/google-a2a/a2a-x402/tree/main/spec/v0.1";
|
|
23
|
+
|
|
24
|
+
// Metadata keys defined by the x402 A2A extension.
|
|
25
|
+
const META_STATUS = "x402.payment.status";
|
|
26
|
+
const META_REQUIRED = "x402.payment.required";
|
|
27
|
+
const META_PAYLOAD = "x402.payment.payload";
|
|
28
|
+
const META_RECEIPTS = "x402.payment.receipts";
|
|
29
|
+
const META_ERROR = "x402.payment.error";
|
|
30
|
+
|
|
31
|
+
function uuid() {
|
|
32
|
+
return crypto.randomUUID();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function jsonrpcResult(id, result) {
|
|
36
|
+
return { jsonrpc: "2.0", id: typeof id === "undefined" ? null : id, result };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function jsonrpcError(id, code, message, data) {
|
|
40
|
+
return {
|
|
41
|
+
jsonrpc: "2.0",
|
|
42
|
+
id: typeof id === "undefined" ? null : id,
|
|
43
|
+
error: { code, message, ...(typeof data === "undefined" ? {} : { data }) }
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function agentMessage(text, metadata) {
|
|
48
|
+
return {
|
|
49
|
+
role: "agent",
|
|
50
|
+
kind: "message",
|
|
51
|
+
messageId: uuid(),
|
|
52
|
+
parts: [{ kind: "text", text }],
|
|
53
|
+
...(metadata ? { metadata } : {})
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function paymentRequiredTask({ taskId, contextId, accepts, skillTaskRef }) {
|
|
58
|
+
return {
|
|
59
|
+
id: taskId,
|
|
60
|
+
contextId,
|
|
61
|
+
kind: "task",
|
|
62
|
+
status: {
|
|
63
|
+
state: "input-required",
|
|
64
|
+
message: agentMessage(
|
|
65
|
+
`Payment required to run "${skillTaskRef}". Re-send message/send with the signed x402 authorization in metadata["${META_PAYLOAD}"].`,
|
|
66
|
+
{
|
|
67
|
+
[META_STATUS]: "payment-required",
|
|
68
|
+
[META_REQUIRED]: { x402Version: 2, scheme: "exact", accepts }
|
|
69
|
+
}
|
|
70
|
+
),
|
|
71
|
+
timestamp: new Date().toISOString()
|
|
72
|
+
},
|
|
73
|
+
artifacts: [],
|
|
74
|
+
history: []
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function failedTask({ taskId, contextId, paymentStatus, error }) {
|
|
79
|
+
return {
|
|
80
|
+
id: taskId,
|
|
81
|
+
contextId,
|
|
82
|
+
kind: "task",
|
|
83
|
+
status: {
|
|
84
|
+
state: "failed",
|
|
85
|
+
message: agentMessage(`Payment ${paymentStatus}: ${error || "error"}`, {
|
|
86
|
+
[META_STATUS]: paymentStatus,
|
|
87
|
+
[META_ERROR]: error || null
|
|
88
|
+
}),
|
|
89
|
+
timestamp: new Date().toISOString()
|
|
90
|
+
},
|
|
91
|
+
artifacts: [],
|
|
92
|
+
history: []
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function completedTask({ taskId, contextId, resultPayload, settlement }) {
|
|
97
|
+
return {
|
|
98
|
+
id: taskId,
|
|
99
|
+
contextId,
|
|
100
|
+
kind: "task",
|
|
101
|
+
status: {
|
|
102
|
+
state: "completed",
|
|
103
|
+
message: agentMessage("Completed and settled.", {
|
|
104
|
+
[META_STATUS]: "payment-completed",
|
|
105
|
+
[META_RECEIPTS]: [settlement]
|
|
106
|
+
}),
|
|
107
|
+
timestamp: new Date().toISOString()
|
|
108
|
+
},
|
|
109
|
+
artifacts: [
|
|
110
|
+
{
|
|
111
|
+
artifactId: uuid(),
|
|
112
|
+
name: "result",
|
|
113
|
+
parts: [{ kind: "data", data: resultPayload }]
|
|
114
|
+
}
|
|
115
|
+
],
|
|
116
|
+
history: []
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Pull the task reference + body an agent wants to run out of the A2A message.
|
|
121
|
+
// Accepts an explicit `task_ref`/`skill_id`/`skill` in message.metadata, plus a
|
|
122
|
+
// params/body object (metadata.params | metadata.body | a DataPart's data).
|
|
123
|
+
function resolveSkill(message) {
|
|
124
|
+
const meta = (message && message.metadata) || {};
|
|
125
|
+
const skillTaskRef = String(meta.task_ref || meta.skill_id || meta.skill || "a2a.negotiate");
|
|
126
|
+
let body = meta.params || meta.body || null;
|
|
127
|
+
if (!body && message && Array.isArray(message.parts)) {
|
|
128
|
+
const dataPart = message.parts.find((p) => p && p.kind === "data" && p.data && typeof p.data === "object");
|
|
129
|
+
if (dataPart) body = dataPart.data;
|
|
130
|
+
}
|
|
131
|
+
return { skillTaskRef, body: body && typeof body === "object" ? body : {} };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Handle a single `message/send` call. Returns { http, headers, body }.
|
|
135
|
+
// `ctx` = { config, resourceUrl, deps } where deps has the injected runtime
|
|
136
|
+
// functions (facilitator factory, verify/settle, receipt encoder, authorized
|
|
137
|
+
// executor, task-payload builder, and `state`).
|
|
138
|
+
async function handleMessageSend(rpc, ctx) {
|
|
139
|
+
const id = rpc.id;
|
|
140
|
+
const params = rpc.params || {};
|
|
141
|
+
const message = params.message || {};
|
|
142
|
+
const meta = message.metadata || {};
|
|
143
|
+
|
|
144
|
+
const config = ctx.config || {};
|
|
145
|
+
const x402 = config.x402 || {};
|
|
146
|
+
const accepts = Array.isArray(x402.accepts) ? x402.accepts : [];
|
|
147
|
+
|
|
148
|
+
if (config.verification_mode !== "x402_facilitator") {
|
|
149
|
+
return { http: 200, body: jsonrpcError(id, -32010, "x402 settlement is not enabled on this runtime") };
|
|
150
|
+
}
|
|
151
|
+
if (!accepts.length) {
|
|
152
|
+
return { http: 200, body: jsonrpcError(id, -32011, "x402 accepts are not configured") };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const taskId = uuid();
|
|
156
|
+
const contextId = message.contextId || message.taskId || uuid();
|
|
157
|
+
const { skillTaskRef, body } = resolveSkill(message);
|
|
158
|
+
|
|
159
|
+
// Never ask for (or take) payment for a skill nothing can execute.
|
|
160
|
+
const taskPayloadDraft = ctx.deps.buildNamedTaskPayload(skillTaskRef, body, skillTaskRef);
|
|
161
|
+
const executability = typeof ctx.deps.assessExecutability === "function" ? ctx.deps.assessExecutability(taskPayloadDraft) : { ok: true };
|
|
162
|
+
if (!executability.ok) {
|
|
163
|
+
const refusal = executability.payload || {};
|
|
164
|
+
return {
|
|
165
|
+
http: 200,
|
|
166
|
+
body: jsonrpcResult(id, failedTask({
|
|
167
|
+
taskId,
|
|
168
|
+
contextId,
|
|
169
|
+
paymentStatus: "not-required",
|
|
170
|
+
error: `${refusal.reason || "capability_not_executable"}: ${refusal.detail || "this skill cannot run here"}${Array.isArray(refusal.executable_task_refs) ? ` Executable: ${refusal.executable_task_refs.join(", ")}.` : ""}`
|
|
171
|
+
}))
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const payload = meta[META_PAYLOAD];
|
|
176
|
+
if (!payload) {
|
|
177
|
+
// In-band 402: Task(input-required) carrying the x402 requirements.
|
|
178
|
+
return {
|
|
179
|
+
http: 200,
|
|
180
|
+
body: jsonrpcResult(id, paymentRequiredTask({ taskId, contextId, accepts, skillTaskRef }))
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Payment present: verify + settle via the same facilitator path as the HTTP routes.
|
|
185
|
+
const facilitator = ctx.deps.createFacilitator({
|
|
186
|
+
url: x402.facilitator_url,
|
|
187
|
+
headers: x402.facilitator_headers,
|
|
188
|
+
timeoutMs: x402.facilitator_timeout_ms,
|
|
189
|
+
cdp: x402.cdp
|
|
190
|
+
});
|
|
191
|
+
const paymentHeader =
|
|
192
|
+
typeof payload === "string" ? payload : Buffer.from(JSON.stringify(payload), "utf8").toString("base64");
|
|
193
|
+
|
|
194
|
+
const auth = await ctx.deps.verifyX402Payment({
|
|
195
|
+
paymentHeader,
|
|
196
|
+
accepts,
|
|
197
|
+
resourceUrl: ctx.resourceUrl || null,
|
|
198
|
+
facilitator
|
|
199
|
+
});
|
|
200
|
+
if (!auth.ok) {
|
|
201
|
+
return {
|
|
202
|
+
http: 200,
|
|
203
|
+
body: jsonrpcResult(id, failedTask({ taskId, contextId, paymentStatus: "payment-rejected", error: auth.detail || auth.reason }))
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const requirements = (auth.authorization && auth.authorization.paymentRequirements) || {};
|
|
208
|
+
const requestPayload = {
|
|
209
|
+
...ctx.deps.buildNamedTaskPayload(skillTaskRef, body, skillTaskRef),
|
|
210
|
+
payment_protocol: "x402"
|
|
211
|
+
};
|
|
212
|
+
const authorizationContext = {
|
|
213
|
+
verification_mode: "x402_facilitator",
|
|
214
|
+
wallet_id: (auth.authorization && auth.authorization.payer) || "x402_payer",
|
|
215
|
+
payment_payload: {
|
|
216
|
+
x402_payer: (auth.authorization && auth.authorization.payer) || null,
|
|
217
|
+
x402_network: requirements.network || null,
|
|
218
|
+
x402_asset: requirements.asset || null,
|
|
219
|
+
x402_amount: requirements.amount || null
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
// Verify -> execute -> settle: the payment is only settled once the skill has produced its
|
|
224
|
+
// result, so a failed skill never charges the paying agent.
|
|
225
|
+
const exec = await ctx.deps.executeAuthorized(ctx.deps.state, requestPayload, authorizationContext);
|
|
226
|
+
const execOk = Boolean(exec) && exec.status === 200 && (!exec.payload || exec.payload.ok !== false);
|
|
227
|
+
if (!execOk) {
|
|
228
|
+
const reason = (exec && exec.payload && exec.payload.reason) || "execution_failed";
|
|
229
|
+
return {
|
|
230
|
+
http: 200,
|
|
231
|
+
body: jsonrpcResult(id, failedTask({ taskId, contextId, paymentStatus: "not-charged", error: `${reason}: the skill did not run, so the payment was not settled — retry` }))
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
let settled = await ctx.deps.settleX402Payment({ authorization: auth.authorization, facilitator, settle: x402.settle });
|
|
236
|
+
if (!settled.ok && settled.reason === "facilitator_unreachable" && typeof ctx.deps.confirmAuthorizationUsed === "function") {
|
|
237
|
+
// The facilitator may have broadcast before timing out: if the authorization is used on-chain, it settled.
|
|
238
|
+
const signed = auth.authorization && auth.authorization.paymentPayload && auth.authorization.paymentPayload.payload
|
|
239
|
+
&& auth.authorization.paymentPayload.payload.authorization;
|
|
240
|
+
const used = signed ? await ctx.deps.confirmAuthorizationUsed({ network: requirements.network, asset: requirements.asset, authorizer: signed.from, nonce: signed.nonce }) : null;
|
|
241
|
+
if (used === true) {
|
|
242
|
+
settled = { ok: true, settlement: { success: true, transaction: null, network: requirements.network, payer: signed.from, recovered_from: "authorization_state" } };
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
if (!settled.ok) {
|
|
246
|
+
return {
|
|
247
|
+
http: 200,
|
|
248
|
+
body: jsonrpcResult(id, failedTask({ taskId, contextId, paymentStatus: "payment-failed", error: `${settled.detail || settled.reason} (the result was withheld; if this payment later shows as settled on-chain, contact us with the payment nonce)` }))
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
const receipt = ctx.deps.encodeReceipt(settled.settlement);
|
|
252
|
+
const task = completedTask({ taskId, contextId, resultPayload: exec.payload, settlement: settled.settlement });
|
|
253
|
+
return {
|
|
254
|
+
http: 200,
|
|
255
|
+
headers: { "PAYMENT-RESPONSE": receipt, "Cache-Control": "no-store" },
|
|
256
|
+
body: jsonrpcResult(id, task)
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Top-level JSON-RPC dispatcher for POST /v1/a2a.
|
|
261
|
+
async function handleA2ARpc(rpc, ctx) {
|
|
262
|
+
if (!rpc || typeof rpc !== "object" || Array.isArray(rpc)) {
|
|
263
|
+
return { http: 200, body: jsonrpcError(null, -32600, "Invalid Request: expected a single JSON-RPC 2.0 object") };
|
|
264
|
+
}
|
|
265
|
+
if (rpc.jsonrpc !== "2.0") {
|
|
266
|
+
return { http: 200, body: jsonrpcError(rpc.id, -32600, "Invalid Request: jsonrpc must be \"2.0\"") };
|
|
267
|
+
}
|
|
268
|
+
if (rpc.method === "message/send") {
|
|
269
|
+
return handleMessageSend(rpc, ctx);
|
|
270
|
+
}
|
|
271
|
+
if (rpc.method === "message/stream") {
|
|
272
|
+
return { http: 200, body: jsonrpcError(rpc.id, -32004, "Streaming is not supported by this agent (capabilities.streaming=false)") };
|
|
273
|
+
}
|
|
274
|
+
return { http: 200, body: jsonrpcError(rpc.id, -32601, `Method not found: ${rpc.method}`) };
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
module.exports = {
|
|
278
|
+
handleA2ARpc,
|
|
279
|
+
handleMessageSend,
|
|
280
|
+
resolveSkill,
|
|
281
|
+
X402_EXTENSION_URI,
|
|
282
|
+
META_STATUS,
|
|
283
|
+
META_REQUIRED,
|
|
284
|
+
META_PAYLOAD,
|
|
285
|
+
META_RECEIPTS,
|
|
286
|
+
META_ERROR
|
|
287
|
+
};
|